From fa3183513989378b5e2f63c269597133b445a9b6 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:30:05 -0500 Subject: [PATCH 1/3] refactor internals --- .gitignore | 1 + .../render/freemarker/FreeMarkerRender.java | 2 +- .../jex/render/mustache/MustacheRender.java | 2 +- .../src/main/java/io/avaje/jex/DJex.java | 4 +- .../main/java/io/avaje/jex/DJexConfig.java | 1 + avaje-jex/src/main/java/io/avaje/jex/Jex.java | 3 +- .../src/main/java/io/avaje/jex/JexConfig.java | 1 + .../avaje/jex/{Plugin.java => JexPlugin.java} | 2 +- .../java/io/avaje/jex/core/HealthPlugin.java | 4 +- .../io/avaje/jex/core/TemplateManager.java | 2 +- .../jex/core/internal/CoreServiceLoader.java | 2 +- .../jex/core/internal/CoreServiceManager.java | 4 +- .../internal}/SpiServiceManager.java | 6 ++- .../avaje/jex/http/HttpResponseException.java | 4 ++ .../CtxServiceManager.java} | 38 ++++++++++++++----- .../java/io/avaje/jex/jdk/JdkContext.java | 10 ++--- .../java/io/avaje/jex/jdk/JdkServerStart.java | 10 +++-- .../java/io/avaje/jex/jdk/RoutingFilter.java | 4 +- .../java/io/avaje/jex/jdk/ServiceManager.java | 34 ----------------- .../java/io/avaje/jex/spi/JexExtension.java | 1 - .../avaje/jex/{ => spi}/TemplateRender.java | 4 +- 21 files changed, 67 insertions(+), 72 deletions(-) rename avaje-jex/src/main/java/io/avaje/jex/{Plugin.java => JexPlugin.java} (86%) rename avaje-jex/src/main/java/io/avaje/jex/{spi => core/internal}/SpiServiceManager.java (88%) rename avaje-jex/src/main/java/io/avaje/jex/{spi/ProxyServiceManager.java => jdk/CtxServiceManager.java} (67%) delete mode 100644 avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java rename avaje-jex/src/main/java/io/avaje/jex/{ => spi}/TemplateRender.java (92%) diff --git a/.gitignore b/.gitignore index 3097fb34..f4aa06b9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ build/ *.prefs *.classpath *.prefs +*.factorypath bin/ diff --git a/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java b/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java index b9648228..67d4370a 100644 --- a/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java +++ b/avaje-jex-freemarker/src/main/java/io/avaje/jex/render/freemarker/FreeMarkerRender.java @@ -10,7 +10,7 @@ import freemarker.template.TemplateException; import freemarker.template.Version; import io.avaje.jex.Context; -import io.avaje.jex.TemplateRender; +import io.avaje.jex.spi.TemplateRender; import io.avaje.spi.ServiceProvider; @ServiceProvider diff --git a/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java b/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java index 21d93700..f2aaf27c 100644 --- a/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java +++ b/avaje-jex-mustache/src/main/java/io/avaje/jex/render/mustache/MustacheRender.java @@ -3,7 +3,7 @@ import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.MustacheFactory; import io.avaje.jex.Context; -import io.avaje.jex.TemplateRender; +import io.avaje.jex.spi.TemplateRender; import io.avaje.spi.ServiceProvider; import java.io.IOException; diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJex.java b/avaje-jex/src/main/java/io/avaje/jex/DJex.java index 59fbfd20..3cd3a211 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJex.java @@ -89,7 +89,7 @@ public Jex jsonService(JsonService jsonService) { } @Override - public Jex plugin(Plugin plugin) { + public Jex plugin(JexPlugin plugin) { plugin.apply(this); return this; } @@ -97,7 +97,7 @@ public Jex plugin(Plugin plugin) { @Override public Jex configureWith(BeanScope beanScope) { lifecycle.onShutdown(beanScope::close); - for (Plugin plugin : beanScope.list(Plugin.class)) { + for (JexPlugin plugin : beanScope.list(JexPlugin.class)) { plugin.apply(this); } for (ErrorHandling.Service service : beanScope.list(ErrorHandling.Service.class)) { diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 536380c9..51fe8356 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -7,6 +7,7 @@ import javax.net.ssl.SSLContext; import io.avaje.jex.spi.JsonService; +import io.avaje.jex.spi.TemplateRender; class DJexConfig implements JexConfig { diff --git a/avaje-jex/src/main/java/io/avaje/jex/Jex.java b/avaje-jex/src/main/java/io/avaje/jex/Jex.java index ecb5fef6..a64824ba 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Jex.java +++ b/avaje-jex/src/main/java/io/avaje/jex/Jex.java @@ -2,6 +2,7 @@ import io.avaje.inject.BeanScope; import io.avaje.jex.spi.JsonService; +import io.avaje.jex.spi.TemplateRender; import java.util.Collection; import java.util.function.Consumer; @@ -97,7 +98,7 @@ static Jex create() { /** * Add Plugin functionality. */ - Jex plugin(Plugin plugin); + Jex plugin(JexPlugin plugin); /** * Configure given the dependency injection scope from avaje-inject. diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index f8815696..bbf3dbd1 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -7,6 +7,7 @@ import javax.net.ssl.SSLContext; import io.avaje.jex.spi.JsonService; +import io.avaje.jex.spi.TemplateRender; /** * Jex configuration. diff --git a/avaje-jex/src/main/java/io/avaje/jex/Plugin.java b/avaje-jex/src/main/java/io/avaje/jex/JexPlugin.java similarity index 86% rename from avaje-jex/src/main/java/io/avaje/jex/Plugin.java rename to avaje-jex/src/main/java/io/avaje/jex/JexPlugin.java index a9feb473..805402cb 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/Plugin.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexPlugin.java @@ -3,7 +3,7 @@ /** * A plugin that can register things like routes, exception handlers etc. */ -public interface Plugin { +public interface JexPlugin { /** * Register the plugin features with jex. diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java b/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java index 8ea2da11..6a84c555 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/HealthPlugin.java @@ -3,13 +3,13 @@ import io.avaje.jex.AppLifecycle; import io.avaje.jex.Context; import io.avaje.jex.Jex; -import io.avaje.jex.Plugin; +import io.avaje.jex.JexPlugin; /** * Health plugin with liveness and readiness support based on * the application lifecycle support. */ -public class HealthPlugin implements Plugin { +public class HealthPlugin implements JexPlugin { private AppLifecycle lifecycle; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java index 11d7f51f..4de183ac 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/TemplateManager.java @@ -1,7 +1,7 @@ package io.avaje.jex.core; import io.avaje.jex.Context; -import io.avaje.jex.TemplateRender; +import io.avaje.jex.spi.TemplateRender; import java.util.HashMap; import java.util.HashSet; diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java index e3ce91cb..14cd73b4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceLoader.java @@ -5,9 +5,9 @@ import java.util.Optional; import java.util.ServiceLoader; -import io.avaje.jex.TemplateRender; import io.avaje.jex.spi.JexExtension; import io.avaje.jex.spi.JsonService; +import io.avaje.jex.spi.TemplateRender; /** Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ class CoreServiceLoader { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java index 91f6a8e3..3a57e696 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java @@ -8,7 +8,7 @@ import io.avaje.jex.spi.HeaderKeys; import io.avaje.jex.spi.JsonService; import io.avaje.jex.spi.SpiContext; -import io.avaje.jex.spi.SpiServiceManager; +import io.avaje.jex.spi.TemplateRender; import java.io.UncheckedIOException; import java.io.UnsupportedEncodingException; @@ -20,7 +20,7 @@ /** * Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ -public class CoreServiceManager implements SpiServiceManager { +final class CoreServiceManager implements SpiServiceManager { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); public static final String UTF_8 = "UTF-8"; diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/SpiServiceManager.java similarity index 88% rename from avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManager.java rename to avaje-jex/src/main/java/io/avaje/jex/core/internal/SpiServiceManager.java index 017f23ba..7eed68f8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/SpiServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/SpiServiceManager.java @@ -1,7 +1,9 @@ -package io.avaje.jex.spi; +package io.avaje.jex.core.internal; import io.avaje.jex.Context; import io.avaje.jex.Routing; +import io.avaje.jex.jdk.CtxServiceManager; +import io.avaje.jex.spi.SpiContext; import java.util.Iterator; import java.util.List; @@ -11,7 +13,7 @@ /** * Core service methods available to Context implementations. */ -public interface SpiServiceManager { +public sealed interface SpiServiceManager permits CoreServiceManager, CtxServiceManager { /** * Read and return the type from json request content. diff --git a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java index 1aef4859..7258f667 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java +++ b/avaje-jex/src/main/java/io/avaje/jex/http/HttpResponseException.java @@ -3,6 +3,10 @@ import java.util.Collections; import java.util.Map; +/** + * Throwing an uncaught {@code HttpResponseException} will interrupt http processing and set the + * status code and response body with the given message + */ public class HttpResponseException extends RuntimeException { private final int status; diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/ProxyServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java similarity index 67% rename from avaje-jex/src/main/java/io/avaje/jex/spi/ProxyServiceManager.java rename to avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java index aeb80c02..68926fe0 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/ProxyServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/CtxServiceManager.java @@ -1,25 +1,43 @@ -package io.avaje.jex.spi; +package io.avaje.jex.jdk; import io.avaje.jex.Context; import io.avaje.jex.Routing; +import io.avaje.jex.core.internal.SpiServiceManager; +import io.avaje.jex.spi.SpiContext; +import java.io.OutputStream; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.stream.Stream; -/** - * Provides a delegating proxy to a SpiServiceManager. - *

- * Can be used by specific implementations like Jetty and JDK Http Server to add core functionality - * to provide to the specific context implementation. - */ -public abstract class ProxyServiceManager implements SpiServiceManager { +public final class CtxServiceManager implements SpiServiceManager { - protected final SpiServiceManager delegate; + private final String scheme; + private final String contextPath; - protected ProxyServiceManager(SpiServiceManager delegate) { + private final SpiServiceManager delegate; + + CtxServiceManager(SpiServiceManager delegate, String scheme, String contextPath) { this.delegate = delegate; + this.scheme = scheme; + this.contextPath = contextPath; + } + + OutputStream createOutputStream(JdkContext jdkContext) { + return new BufferedOutStream(jdkContext); + } + + String scheme() { + return scheme; + } + + public String url() { + return scheme + "://"; + } + + public String contextPath() { + return contextPath; } @Override diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java index 7dd5ec6b..0b916450 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java @@ -39,7 +39,7 @@ class JdkContext implements Context, SpiContext { private static final int SC_MOVED_TEMPORARILY = 302; private static final String SET_COOKIE = "Set-Cookie"; private static final String COOKIE = "Cookie"; - private final ServiceManager mgr; + private final CtxServiceManager mgr; private final String path; private final Map pathParams; private final Set roles; @@ -52,7 +52,7 @@ class JdkContext implements Context, SpiContext { private String characterEncoding; JdkContext( - ServiceManager mgr, + CtxServiceManager mgr, HttpExchange exchange, String path, Map pathParams, @@ -65,7 +65,7 @@ class JdkContext implements Context, SpiContext { } /** Create when no route matched. */ - JdkContext(ServiceManager mgr, HttpExchange exchange, String path, Set roles) { + JdkContext(CtxServiceManager mgr, HttpExchange exchange, String path, Set roles) { this.mgr = mgr; this.roles = roles; this.exchange = exchange; @@ -365,10 +365,8 @@ public Context write(byte[] bytes) { @Override public Context write(InputStream is) { - try (is; var os = exchange.getResponseBody()) { - exchange.sendResponseHeaders(statusCode(), 0); + try (is; var os = outputStream()) { is.transferTo(os); - os.flush(); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java index 5676b749..a331f46e 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/JdkServerStart.java @@ -6,22 +6,21 @@ import java.net.InetSocketAddress; import java.util.concurrent.Executors; -import com.sun.net.httpserver.*; +import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsServer; import io.avaje.applog.AppLog; import io.avaje.jex.AppLifecycle; import io.avaje.jex.Jex; +import io.avaje.jex.core.internal.SpiServiceManager; import io.avaje.jex.routes.SpiRoutes; -import io.avaje.jex.spi.SpiServiceManager; public class JdkServerStart { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceManager) { - final ServiceManager manager = new ServiceManager(serviceManager, "http", ""); try { final HttpServer server; @@ -29,14 +28,19 @@ public Jex.Server start(Jex jex, SpiRoutes routes, SpiServiceManager serviceMana var port = new InetSocketAddress(jex.config().port()); final var sslContext = jex.config().sslContext(); + final String scheme; if (sslContext != null) { var httpsServer = HttpsServer.create(port, 0); httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); server = httpsServer; + scheme = "https"; } else { + scheme = "http"; server = HttpServer.create(port, 0); } + final var manager = new CtxServiceManager(serviceManager, scheme, ""); + var handler = new BaseHandler(routes); var context = server.createContext("/", handler); context.getFilters().add(new RoutingFilter(routes, manager)); diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java index 32ecb31d..35498273 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java +++ b/avaje-jex/src/main/java/io/avaje/jex/jdk/RoutingFilter.java @@ -17,9 +17,9 @@ final class RoutingFilter extends Filter { private final SpiRoutes routes; - private final ServiceManager mgr; + private final CtxServiceManager mgr; - RoutingFilter(SpiRoutes routes, ServiceManager mgr) { + RoutingFilter(SpiRoutes routes, CtxServiceManager mgr) { this.mgr = mgr; this.routes = routes; } diff --git a/avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java deleted file mode 100644 index b1ce71c3..00000000 --- a/avaje-jex/src/main/java/io/avaje/jex/jdk/ServiceManager.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.avaje.jex.jdk; - -import io.avaje.jex.spi.ProxyServiceManager; -import io.avaje.jex.spi.SpiServiceManager; - -import java.io.OutputStream; - -final class ServiceManager extends ProxyServiceManager { - - private final String scheme; - private final String contextPath; - - ServiceManager(SpiServiceManager delegate, String scheme, String contextPath) { - super(delegate); - this.scheme = scheme; - this.contextPath = contextPath; - } - - OutputStream createOutputStream(JdkContext jdkContext) { - return new BufferedOutStream(jdkContext); - } - - String scheme() { - return scheme; - } - - public String url() { - return scheme + "://"; - } - - public String contextPath() { - return contextPath; - } -} diff --git a/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java index 31b9c132..a0eb3947 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/JexExtension.java @@ -1,6 +1,5 @@ package io.avaje.jex.spi; -import io.avaje.jex.TemplateRender; import io.avaje.spi.Service; @Service diff --git a/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java b/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java similarity index 92% rename from avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java rename to avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java index 562d3aaa..f0bebaa4 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/TemplateRender.java +++ b/avaje-jex/src/main/java/io/avaje/jex/spi/TemplateRender.java @@ -1,8 +1,8 @@ -package io.avaje.jex; +package io.avaje.jex.spi; import java.util.Map; -import io.avaje.jex.spi.JexExtension; +import io.avaje.jex.Context; /** * Template rendering typically of html. From bf370fea8c090aa1d017fe1f9cc7cdb63a0c10b7 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:51:52 -0500 Subject: [PATCH 2/3] Update CoreServiceManager.java --- .../java/io/avaje/jex/core/internal/CoreServiceManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java index 3a57e696..142cbef5 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/internal/CoreServiceManager.java @@ -20,7 +20,7 @@ /** * Core implementation of SpiServiceManager provided to specific implementations like jetty etc. */ -final class CoreServiceManager implements SpiServiceManager { +public final class CoreServiceManager implements SpiServiceManager { private static final System.Logger log = AppLog.getLogger("io.avaje.jex"); public static final String UTF_8 = "UTF-8"; From e214aa7a49ac3d994f0784f7685067e67b0c05fe Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:53:14 -0500 Subject: [PATCH 3/3] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1d68ee95..a2474d39 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ +[![Discord](https://img.shields.io/discord/1074074312421683250?color=%237289da&label=discord)](https://discord.gg/Qcqf9R27BR) [![Build](https://github.com/avaje/avaje-jex/actions/workflows/build.yml/badge.svg)](https://github.com/avaje/avaje-jex/actions/workflows/build.yml) -[![Maven Central](https://img.shields.io/maven-central/v/io.avaje/avaje-jex-parent.svg?label=Maven%20Central)](https://mvnrepository.com/artifact/io.avaje/avaje-jex-parent) -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/avaje/avaje-jex/blob/master/LICENSE) [![JDK EA](https://github.com/avaje/avaje-jex/actions/workflows/jdk-ea.yml/badge.svg)](https://github.com/avaje/avaje-jex/actions/workflows/jdk-ea.yml) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/avaje/avaje-jex/blob/master/LICENSE) +[![Maven Central](https://img.shields.io/maven-central/v/io.avaje/avaje-jex.svg?label=Maven%20Central)](https://mvnrepository.com/artifact/io.avaje/avaje-jex) +[![javadoc](https://javadoc.io/badge2/io.avaje/avaje-jex/javadoc.svg?color=purple)](https://javadoc.io/doc/io.avaje/avaje-jex) # avaje-jex