Skip to content

Commit

Permalink
Early work on Teeny Auto configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-cova committed Feb 21, 2024
1 parent cbf6f50 commit e508e2f
Show file tree
Hide file tree
Showing 21 changed files with 922 additions and 155 deletions.
5 changes: 5 additions & 0 deletions http/Client.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
http://localhost:80/todos2/todos

###

http://localhost:80/todos/todos
1 change: 1 addition & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
exports net.jonathangiles.tools.teenyhttpd.annotations;

requires java.logging;
requires java.base;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package net.jonathangiles.tools.teenyhttpd;

import net.jonathangiles.tools.teenyhttpd.annotations.*;
import net.jonathangiles.tools.teenyhttpd.implementation.AnnotationScanner;
import net.jonathangiles.tools.teenyhttpd.implementation.DefaultMessageConverter;
import net.jonathangiles.tools.teenyhttpd.implementation.ResourceManager;
import net.jonathangiles.tools.teenyhttpd.model.MessageConverter;
import net.jonathangiles.tools.teenyhttpd.model.ServerSentEventHandler;

Expand Down Expand Up @@ -33,7 +35,7 @@ public static TeenyApplication start() {
public static TeenyApplication start(Class<?> clazz) {
start();

instance.register(clazz);
instance.scan(clazz);

return instance;
}
Expand All @@ -45,21 +47,39 @@ public static void stop() {
}

private final TeenyHttpd server;
private final AtomicBoolean started = new AtomicBoolean(false);
private final AtomicBoolean started;
private final Map<String, MessageConverter> messageConverterMap;
private final Map<String, ServerSentEventHandler> eventMap = new HashMap<>();
private final Map<String, ServerSentEventHandler> eventMap;
private ResourceManager resourceManager;

private TeenyApplication() {
server = new TeenyHttpd(Integer.parseInt(System.getProperty("server.port", "8080")));
this.messageConverterMap = new HashMap<>();
this.messageConverterMap.put(DefaultMessageConverter.INSTANCE.getContentType(), DefaultMessageConverter.INSTANCE);

eventMap = new HashMap<>();
started = new AtomicBoolean(false);
}

public TeenyApplication registerMessageConverter(MessageConverter messageConverter) {
messageConverterMap.put(messageConverter.getContentType(), messageConverter);
return this;
}

private void scan(Class<?> clazz) {
Set<Class<?>> classList = AnnotationScanner.scan(clazz);

resourceManager = new ResourceManager(classList);
resourceManager.initialize();

resourceManager.findInstancesOf(MessageConverter.class)
.forEach(this::registerMessageConverter);

resourceManager.findControllers()
.forEach(this::register);

}

public TeenyApplication register(Class<?> controller) {

Objects.requireNonNull(controller, "Controller cannot be null");
Expand Down Expand Up @@ -92,17 +112,13 @@ public TeenyApplication register(Object controller) {

private void addController(Object controller) {

System.out.println("Adding controller: " + controller.getClass().getSimpleName());

Objects.requireNonNull(controller, "Controller cannot be null");

Method[] methods = controller.getClass().getDeclaredMethods();


for (Method method : methods) {
if (method.isAnnotationPresent(Configuration.class)) {
handleConfiguration(controller, method);
continue;
}

if (method.isAnnotationPresent(ServerEvent.class)
&& ServerSentEventHandler.class.isAssignableFrom(method.getReturnType())) {
addServerEvent(controller, method);
Expand All @@ -111,36 +127,14 @@ private void addController(Object controller) {

for (Method method : methods) {

if (method.isAnnotationPresent(Get.class) ||
method.isAnnotationPresent(Post.class) ||
method.isAnnotationPresent(Delete.class) ||
method.isAnnotationPresent(Put.class) ||
method.isAnnotationPresent(Patch.class)) {
if (AnnotationScanner.isEndpoint(method)) {

addEndpoint(controller, method);

}
}
}

private void handleConfiguration(Object controller, Method method) {

if (Void.class.isAssignableFrom(method.getReturnType())) {
return;
}

try {
method.setAccessible(true);
Object configuration = method.invoke(controller);

if (configuration instanceof MessageConverter) {
registerMessageConverter((MessageConverter) configuration);
}

} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}

private void addServerEvent(Object controller, Method method) {

Expand All @@ -165,7 +159,8 @@ private void addServerEvent(Object controller, Method method) {
eventMap.put(name, result);
server.addServerSentEventRoute(route, result);

Logger.getLogger(TeenyApplication.class.getName()).log(Level.INFO, "Added server event: " + name + " at route " + route);
Logger.getLogger(TeenyApplication.class.getName())
.log(Level.INFO, "Added server event: " + name + " at route " + route);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
Expand All @@ -189,6 +184,9 @@ private void addEndpoint(Object controller, Method method) {
server.addRoute(getMethod(method), getRoute(controller, method),
new EndpointHandler(method, controller, messageConverterMap, eventMap));

Logger.getLogger(TeenyApplication.class.getName())
.log(Level.INFO, "Added route: " + getRoute(controller, method));

}

private void validateMethod(Object controller, Method method) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Target(ElementType.TYPE)
public @interface Configuration {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package net.jonathangiles.tools.teenyhttpd.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface Inject {

String name() default "";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package net.jonathangiles.tools.teenyhttpd.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PostConstruct {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package net.jonathangiles.tools.teenyhttpd.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Resource {

String name() default "";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package net.jonathangiles.tools.teenyhttpd.implementation;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.jonathangiles.tools.teenyhttpd.annotations.*;

public class AnnotationScanner {

public static Set<Class<?>> scan(Class<?> target) {

Set<Class<?>> classList = new HashSet<>();

String packageName = target.getPackageName();

List<String> packages = getPackages(packageName);
packages.add(packageName);

for (String aPackage : packages) {
try {
List<Class<?>> classes = getClasses(aPackage);

for (Class<?> clazz : classes) {
if (hasAnnotations(clazz)) {
classList.add(clazz);
}
}

} catch (ClassNotFoundException | IOException e) {
Logger.getLogger(AnnotationScanner.class.getName())
.log(Level.SEVERE, null, e);
}
}

if (hasEndpoints(target)) {
classList.add(target);
}

return classList;
}

private static boolean hasAnnotations(Class<?> clazz) {

if (clazz.isInterface()) return false;

if (clazz.isAnnotationPresent(Resource.class)) {
return true;
}

if (clazz.isAnnotationPresent(Configuration.class)) {
return true;
}

if (!clazz.isAnnotationPresent(Path.class)) {
return false;
}

return hasEndpoints(clazz);
}

private static boolean hasEndpoints(Class<?> clazz) {


for (Method method : clazz.getDeclaredMethods()) {

if (method.isAnnotationPresent(ServerEvent.class)) {
return true;
}

if (isEndpoint(method)) {
return true;
}

}

return false;
}

public static boolean isEndpoint(Method method) {


if (method.isAnnotationPresent(Get.class)) {
return true;
}

if (method.isAnnotationPresent(Delete.class)) {
return true;
}

if (method.isAnnotationPresent(Put.class)) {
return true;
}

if (method.isAnnotationPresent(Post.class)) {
return true;
}

return method.isAnnotationPresent(Patch.class);
}


private static List<String> getPackages(String packageName) {
List<String> packages = new ArrayList<>();
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
File directory = new File(resource.getFile());
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
packages.add(packageName + "." + file.getName());
}
}
}
}
} catch (Exception e) {
Logger.getLogger(AnnotationScanner.class.getName())
.log(Level.SEVERE, null, e);
}
return packages;
}

private static List<Class<?>> getClasses(String packageName) throws ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
List<File> dirs = new ArrayList<>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
List<Class<?>> classes = new ArrayList<>();

for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}

return classes;
}

private static List<Class<?>> findClasses(File directory, String packageName) throws ClassNotFoundException {
List<Class<?>> classes = new ArrayList<>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
assert files != null;
for (File file : files) {
if (file.isDirectory()) {
assert !file.getName().contains(".");
classes.addAll(findClasses(file, packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
}
}
return classes;
}
}
Loading

0 comments on commit e508e2f

Please sign in to comment.