From 279b414f3822f90afa352400f165fc386164ddff Mon Sep 17 00:00:00 2001 From: Adam Gent Date: Fri, 5 Jan 2024 18:33:02 -0500 Subject: [PATCH] Add rabbitmq test --- .../io/jstach/rainbowgum/ConfigObject.java | 22 +++- .../io/jstach/rainbowgum/LogAppender.java | 19 ++- .../java/io/jstach/rainbowgum/LogConfig.java | 63 ++++++++++ .../io/jstach/rainbowgum/LogPublisher.java | 10 +- .../java/io/jstach/rainbowgum/LogRouter.java | 4 + .../java/io/jstach/rainbowgum/RainbowGum.java | 10 +- .../appender/SynchronizedLogAppender.java | 6 + .../spi/RainbowGumServiceProvider.java | 12 +- .../jstach/rainbowgum/TestSyncPublisher.java | 8 +- pom.xml | 7 +- .../jstach/rainbowgum/apt/BuilderModel.java | 28 +++++ .../rainbowgum/apt/ConfigProcessor.java | 119 ++++++++++++------ .../rainbowgum/apt/prism/package-info.java | 2 + .../jstach/rainbowgum/apt/ConfigBuilder.java | 20 ++- rainbowgum-rabbitmq/pom.xml | 8 ++ .../jstach/rainbowgum/rabbitmq/IgnoreMe.java | 8 -- .../rabbitmq/RabbitMQInitializer.java | 48 +++++++ .../rainbowgum/rabbitmq/RabbitMQOutput.java | 82 ++++++------ .../src/main/java/module-info.java | 7 +- test/pom.xml | 1 + .../rainbowgum/test/config/ExampleConfig.java | 8 +- test/rainbowgum-test-rabbitmq/pom.xml | 29 +++++ .../test/rabbitmq/RabbitMQSetup.java | 41 ++++++ .../test/rabbitmq/package-info.java | 2 + .../test/rabbitmq/RabbitMQSetupTest.java | 45 +++++++ 25 files changed, 499 insertions(+), 110 deletions(-) delete mode 100644 rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/IgnoreMe.java create mode 100644 rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQInitializer.java create mode 100644 test/rainbowgum-test-rabbitmq/pom.xml create mode 100644 test/rainbowgum-test-rabbitmq/src/main/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetup.java create mode 100644 test/rainbowgum-test-rabbitmq/src/main/java/io/jstach/rainbowgum/test/rabbitmq/package-info.java create mode 100644 test/rainbowgum-test-rabbitmq/src/test/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetupTest.java diff --git a/core/src/main/java/io/jstach/rainbowgum/ConfigObject.java b/core/src/main/java/io/jstach/rainbowgum/ConfigObject.java index 741ce3f6..579dce98 100644 --- a/core/src/main/java/io/jstach/rainbowgum/ConfigObject.java +++ b/core/src/main/java/io/jstach/rainbowgum/ConfigObject.java @@ -17,9 +17,10 @@ /** * Name of builder. - * @return name of builder. + * @return name of builder by default if not set Builder will be suffixed to + * targetType. */ - String name(); + String name() default ""; /** * Property prefix. @@ -43,4 +44,21 @@ } + /** + * Use to set static defaults to parameters. + */ + @Retention(CLASS) + @Target({ ElementType.PARAMETER }) + @Documented + public @interface DefaultParameter { + + /** + * Use as parameter to prefix property names. + * @return by default will use the static field + * DEFAULT_parameterName. + */ + String value() default ""; + + } + } diff --git a/core/src/main/java/io/jstach/rainbowgum/LogAppender.java b/core/src/main/java/io/jstach/rainbowgum/LogAppender.java index e8cefa23..fac11c62 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogAppender.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogAppender.java @@ -15,7 +15,7 @@ * * The only exception is if an Appender implements {@link ThreadSafeLogAppender}. */ -public interface LogAppender extends AutoCloseable, LogEventConsumer { +public interface LogAppender extends LogLifecycle, LogEventConsumer { /** * Batch of events. DO NOT MODIFY THE ARRAY. Do not use the @@ -221,6 +221,11 @@ protected void append(LogEvent[] events, int count, Buffer buffer) { protected abstract void append(LogEvent event, Buffer buffer); + @Override + public void start(LogConfig config) { + output.start(config); + } + @Override public void close() { output.close(); @@ -252,6 +257,13 @@ public void close() { } } + @Override + public void start(LogConfig config) { + for (var appender : appenders) { + appender.start(config); + } + } + } class LockingLogAppender implements ThreadSafeLogAppender { @@ -288,6 +300,11 @@ public void append(LogEvent event) { } + @Override + public void start(LogConfig config) { + appender.start(config); + } + @Override public void close() { appender.close(); diff --git a/core/src/main/java/io/jstach/rainbowgum/LogConfig.java b/core/src/main/java/io/jstach/rainbowgum/LogConfig.java index ec39d1b5..2bba9794 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogConfig.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogConfig.java @@ -6,6 +6,8 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; +import org.eclipse.jdt.annotation.Nullable; + import io.jstach.rainbowgum.LevelResolver.LevelConfig; import io.jstach.rainbowgum.LogConfig.ChangePublisher; import io.jstach.rainbowgum.LogProperties.Property; @@ -81,6 +83,14 @@ public static LogConfig of(ServiceRegistry registry, LogProperties properties) { return new DefaultLogConfig(registry, properties); } + /** + * Creates a builder for making LogConfig. + * @return builder. + */ + public static Builder builder() { + return new Builder(); + } + /** * An event publisher to publish configuration changes. * @return publisher. @@ -119,6 +129,59 @@ interface ChangePublisher { } + /** + * Builder for LogConfig. + */ + public static final class Builder { + + private @Nullable ServiceRegistry serviceRegistry; + + private @Nullable LogProperties logProperties; + + /** + * Default constructor + */ + private Builder() { + } + + /** + * Sets log properties + * @param logProperties log properties. + * @return this. + */ + public Builder logProperties(LogProperties logProperties) { + this.logProperties = logProperties; + return this; + } + + /** + * Sets service registry + * @param serviceRegistry service registry. + * @return this. + */ + public Builder serviceRegistry(ServiceRegistry serviceRegistry) { + this.serviceRegistry = serviceRegistry; + return this; + } + + /** + * Builds LogConfig using defaults on missing properties. + * @return log config + */ + public LogConfig build() { + ServiceRegistry serviceRegistry = this.serviceRegistry; + LogProperties logProperties = this.logProperties; + if (serviceRegistry == null) { + serviceRegistry = ServiceRegistry.of(); + } + if (logProperties == null) { + logProperties = LogProperties.StandardProperties.SYSTEM_PROPERTIES; + } + return LogConfig.of(serviceRegistry, logProperties); + } + + } + } abstract class AbstractChangePublisher implements ChangePublisher { diff --git a/core/src/main/java/io/jstach/rainbowgum/LogPublisher.java b/core/src/main/java/io/jstach/rainbowgum/LogPublisher.java index 0deb63f9..8b6ce2cf 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogPublisher.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogPublisher.java @@ -140,11 +140,6 @@ public static SyncLogPublisher.Builder builder() { return new Builder(); } - @Override - default void start(LogConfig config) { - - } - default boolean synchronous() { return true; } @@ -195,6 +190,11 @@ public void log(LogEvent event) { appender.append(event); } + @Override + public void start(LogConfig config) { + appender.start(config); + } + @Override public void close() { appender.close(); diff --git a/core/src/main/java/io/jstach/rainbowgum/LogRouter.java b/core/src/main/java/io/jstach/rainbowgum/LogRouter.java index d6fc9c61..b43eb272 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogRouter.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogRouter.java @@ -459,6 +459,10 @@ public void append(LogEvent event) { public void close() { } + @Override + public void start(LogConfig config) { + } + } enum GlobalLogRouter implements InternalRootRouter, Route { diff --git a/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java b/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java index b0bad377..652e4bf1 100644 --- a/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java +++ b/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java @@ -24,7 +24,7 @@ *

* To register a custom RainbowGum: * - * + * {@snippet class="snippets.RainbowGumProviderExample" region="provider" : class RainbowGumProviderExample implements RainbowGumProvider { @@ -208,6 +208,10 @@ static RainbowGum get() { finally { lock.readLock().unlock(); } + if (lock.writeLock().isHeldByCurrentThread()) { + throw new IllegalStateException("RainbowGum component tried to log too early. " + + "This is usually caused by dependencies calling logging."); + } lock.writeLock().lock(); try { var r = rainbowGum; @@ -228,6 +232,10 @@ static RainbowGum get() { static void set(Supplier rainbowGumSupplier) { Objects.requireNonNull(rainbowGumSupplier); + if (lock.writeLock().isHeldByCurrentThread()) { + throw new IllegalStateException("RainbowGum component tried to log too early. " + + "This is usually caused by dependencies calling logging."); + } lock.writeLock().lock(); try { rainbowGum = null; diff --git a/core/src/main/java/io/jstach/rainbowgum/appender/SynchronizedLogAppender.java b/core/src/main/java/io/jstach/rainbowgum/appender/SynchronizedLogAppender.java index e11c87e9..9f303c69 100644 --- a/core/src/main/java/io/jstach/rainbowgum/appender/SynchronizedLogAppender.java +++ b/core/src/main/java/io/jstach/rainbowgum/appender/SynchronizedLogAppender.java @@ -5,6 +5,7 @@ import io.jstach.rainbowgum.LogAppender; import io.jstach.rainbowgum.LogAppender.AbstractLogAppender; import io.jstach.rainbowgum.LogAppender.ThreadSafeLogAppender; +import io.jstach.rainbowgum.LogConfig; import io.jstach.rainbowgum.LogEncoder; import io.jstach.rainbowgum.LogEncoder.Buffer; import io.jstach.rainbowgum.LogEvent; @@ -60,6 +61,11 @@ public synchronized void append(LogEvent event) { appender.append(event); } + @Override + public void start(LogConfig config) { + appender.start(config); + } + @Override public void close() { appender.close(); diff --git a/core/src/main/java/io/jstach/rainbowgum/spi/RainbowGumServiceProvider.java b/core/src/main/java/io/jstach/rainbowgum/spi/RainbowGumServiceProvider.java index df8a5589..4b7d3597 100644 --- a/core/src/main/java/io/jstach/rainbowgum/spi/RainbowGumServiceProvider.java +++ b/core/src/main/java/io/jstach/rainbowgum/spi/RainbowGumServiceProvider.java @@ -118,9 +118,13 @@ private static LogConfig provideConfig(ServiceLoader .orElseGet(() -> LogConfig.of(registry, properties)); } - private static void runInitializers(ServiceLoader loader, ServiceRegistry registry, - LogConfig config) { - findProviders(loader, Initializer.class).forEach(c -> c.initialize(registry, config)); + /** + * Runs initializers based on config. + * @param loader service loader to use. + * @param config config before initializers have run. + */ + public static void runInitializers(ServiceLoader loader, LogConfig config) { + findProviders(loader, Initializer.class).forEach(c -> c.initialize(config.registry(), config)); } /** @@ -132,7 +136,7 @@ public static LogConfig provideConfig(ServiceLoader l ServiceRegistry registry = ServiceRegistry.of(); var properties = provideProperties(registry, loader); var config = provideConfig(loader, registry, properties); - runInitializers(loader, registry, config); + runInitializers(loader, config); return config; } diff --git a/core/src/test/java/io/jstach/rainbowgum/TestSyncPublisher.java b/core/src/test/java/io/jstach/rainbowgum/TestSyncPublisher.java index 6019d390..432f7398 100644 --- a/core/src/test/java/io/jstach/rainbowgum/TestSyncPublisher.java +++ b/core/src/test/java/io/jstach/rainbowgum/TestSyncPublisher.java @@ -6,15 +6,19 @@ import io.jstach.rainbowgum.LogPublisher.SyncLogPublisher; /** - * + * */ public class TestSyncPublisher implements SyncLogPublisher { /** - * + * */ public Deque events = new LinkedList<>(); + @Override + public void start(LogConfig config) { + } + @Override public void close() { } diff --git a/pom.xml b/pom.xml index e9befbb9..c9187246 100644 --- a/pom.xml +++ b/pom.xml @@ -103,10 +103,15 @@ rainbowgum-avaje-config ${project.version} - + ${project.groupId} rainbowgum-config-apt ${project.version} + + + ${project.groupId} + rainbowgum-rabbitmq + ${project.version} diff --git a/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/BuilderModel.java b/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/BuilderModel.java index 6897a858..9966fb01 100644 --- a/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/BuilderModel.java +++ b/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/BuilderModel.java @@ -71,6 +71,13 @@ public String convertMethod() { }; } + boolean isLiteralType() { + return switch (type) { + case INTEGER_TYPE, STRING_TYPE, BOOLEAN_TYPE -> true; + default -> false; + }; + } + public String valueMethod() { return required ? "value" : "valueOrNull"; } @@ -82,6 +89,27 @@ public boolean isNormal() { public boolean isPrefixParameter() { return kind == PropertyKind.NAME_PARAMETER; } + + public String defaultValueDoc() { + if (defaultValue.equals("null")) { + return "null"; + } + String link = linkStatic(defaultValue); + if (isLiteralType()) { + return "{@value " + link + " }"; + } + return "{@link " + link + " }"; + } + + private static String linkStatic(String constant) { + int index = constant.lastIndexOf("."); + if (index < 0) { + return constant; + } + StringBuilder sb = new StringBuilder(constant); + sb.setCharAt(index, '#'); + return sb.toString(); + } } enum PropertyKind { diff --git a/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/ConfigProcessor.java b/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/ConfigProcessor.java index 85c7f1aa..61cc6aef 100644 --- a/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/ConfigProcessor.java +++ b/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/ConfigProcessor.java @@ -10,7 +10,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -51,14 +50,17 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import io.jstach.rainbowgum.apt.BuilderModel.PropertyModel; import io.jstach.rainbowgum.apt.prism.ConfigObjectPrism; +import io.jstach.rainbowgum.apt.prism.DefaultParameterPrism; import io.jstach.rainbowgum.apt.prism.PrefixParameterPrism; import io.jstach.svc.ServiceProvider; /** * Creates ConfigBuilders from static factory methods. */ -@SupportedAnnotationTypes({ ConfigObjectPrism.PRISM_ANNOTATION_TYPE, PrefixParameterPrism.PRISM_ANNOTATION_TYPE }) +@SupportedAnnotationTypes({ ConfigObjectPrism.PRISM_ANNOTATION_TYPE, PrefixParameterPrism.PRISM_ANNOTATION_TYPE, + DefaultParameterPrism.PRISM_ANNOTATION_TYPE }) @ServiceProvider(value = Processor.class) public class ConfigProcessor extends AbstractProcessor { @@ -104,59 +106,27 @@ private BuilderModel model(Helper h, ConfigObjectPrism prism, ExecutableElement TypeElement enclosingType = (TypeElement) ee.getEnclosingElement(); String builderName = prism.name(); + if (builderName.isBlank()) { + builderName = h.getSimpleName(ee.getReturnType()) + "Builder"; + } String propertyPrefix = prism.prefix(); String packageName = h.getPackageString(enclosingType); String targetType = h.getFullyQualifiedClassName(ee.getReturnType()); String factoryMethod = enclosingType + "." + ee.getSimpleName(); List properties = new ArrayList<>(); - var propertyParams = extractPropertyParams(propertyPrefix); Map foundParams = new HashMap<>(); List parameters = ee.getParameters(); ConfigJavadoc methodDoc = ConfigJavadoc.of(h.getJavadoc(ee)); String description = methodDoc.description; for (var p : parameters) { - String name = p.getSimpleName().toString(); - String type = h.getFullyQualifiedClassName(p.asType()); - String typeWithAnnotation = ToStringTypeVisitor.toCodeSafeString(p.asType()); - String defaultValue = "null"; - boolean required = !h.isNullable(p.asType()); - BuilderModel.PropertyKind kind; - var prefixParameter = PrefixParameterPrism.getInstanceOn(p); - if (prefixParameter == null) { - kind = BuilderModel.PropertyKind.NORMAL; - } - else { - // TODO do validation here - kind = BuilderModel.PropertyKind.NAME_PARAMETER; - foundParams.put(name, p); - } - @Nullable - String javadoc = methodDoc.properties.get(name); - if (javadoc == null) { - javadoc = ""; - } - var prop = new BuilderModel.PropertyModel(kind, name, type, typeWithAnnotation, defaultValue, required, - javadoc); + var prop = propertyModel(ee, p, h, methodDoc, foundParams); properties.add(prop); } - var foundParamsKeys = foundParams.keySet(); - if (!foundParamsKeys.equals(propertyParams)) { - for (var p : foundParams.entrySet()) { - if (!propertyParams.contains(p.getKey())) { - processingEnv.getMessager() - .printMessage(Kind.ERROR, "Property parameter missing from prefix. parameter = " + p.getKey(), - p.getValue()); - } - } - for (var pp : propertyParams) { - if (!foundParamsKeys.contains(pp)) { - processingEnv.getMessager() - .printMessage(Kind.ERROR, "Property parameter defined but missing. parameter = " + pp, ee); - } - } + if (!validatePrefix(ee, propertyPrefix, foundParams)) { return null; } + var m = new BuilderModel(builderName, propertyPrefix, packageName, targetType, factoryMethod, description, properties); String java = BuilderModelRenderer.of().execute(m); @@ -179,6 +149,66 @@ private BuilderModel model(Helper h, ConfigObjectPrism prism, ExecutableElement return m; } + private boolean validatePrefix(ExecutableElement ee, String propertyPrefix, + Map foundParams) { + var propertyParams = extractPropertyParams(propertyPrefix); + var foundParamsKeys = foundParams.keySet(); + if (!foundParamsKeys.equals(propertyParams)) { + for (var p : foundParams.entrySet()) { + if (!propertyParams.contains(p.getKey())) { + processingEnv.getMessager() + .printMessage(Kind.ERROR, "Property parameter missing from prefix. parameter = " + p.getKey(), + p.getValue()); + } + } + for (var pp : propertyParams) { + if (!foundParamsKeys.contains(pp)) { + processingEnv.getMessager() + .printMessage(Kind.ERROR, "Property parameter defined but missing. parameter = " + pp, ee); + } + } + return false; + } + return true; + } + + private PropertyModel propertyModel(ExecutableElement ee, VariableElement p, Helper h, ConfigJavadoc methodDoc, + Map foundParams) { + String name = p.getSimpleName().toString(); + String type = h.getFullyQualifiedClassName(p.asType()); + String typeWithAnnotation = ToStringTypeVisitor.toCodeSafeString(p.asType()); + String defaultValue = "null"; + var defaultParameter = DefaultParameterPrism.getInstanceOn(p); + if (defaultParameter != null) { + TypeElement enclosingType = (TypeElement) ee.getEnclosingElement(); + String field = defaultParameter.value(); + if (field.isBlank()) { + field = "DEFAULT_" + name; + } + defaultValue = h.getFullyQualifiedClassName(enclosingType.asType()) + "." + field; + + } + boolean required = !h.isNullable(p.asType()); + BuilderModel.PropertyKind kind; + var prefixParameter = PrefixParameterPrism.getInstanceOn(p); + if (prefixParameter == null) { + kind = BuilderModel.PropertyKind.NORMAL; + } + else { + // TODO do validation here + kind = BuilderModel.PropertyKind.NAME_PARAMETER; + foundParams.put(name, p); + } + @Nullable + String javadoc = methodDoc.properties.get(name); + if (javadoc == null) { + javadoc = ""; + } + var prop = new BuilderModel.PropertyModel(kind, name, type, typeWithAnnotation, defaultValue, required, + javadoc); + return prop; + } + private static final Pattern pattern = Pattern.compile("\\{(.*?)\\}"); static Set extractPropertyParams(String input) { @@ -381,7 +411,16 @@ public String getFullyQualifiedClassName(TypeMirror t) { else { return t.toString(); } + } + public String getSimpleName(TypeMirror t) { + if (t.getKind() == TypeKind.DECLARED) { + TypeElement te = requireNonNull((TypeElement) types.asElement(t)); + return te.getSimpleName().toString(); + } + else { + return t.toString(); + } } public String getFullyQualifiedClassNameWithTypeAnnotations(TypeMirror t) { diff --git a/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/prism/package-info.java b/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/prism/package-info.java index f6bbe392..ab4dbf62 100644 --- a/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/prism/package-info.java +++ b/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/prism/package-info.java @@ -4,5 +4,7 @@ @io.jstach.prism.GeneratePrisms({ @io.jstach.prism.GeneratePrism(value = io.jstach.rainbowgum.ConfigObject.class, publicAccess = true), @io.jstach.prism.GeneratePrism(value = io.jstach.rainbowgum.ConfigObject.PrefixParameter.class, + publicAccess = true), + @io.jstach.prism.GeneratePrism(value = io.jstach.rainbowgum.ConfigObject.DefaultParameter.class, publicAccess = true) }) package io.jstach.rainbowgum.apt.prism; \ No newline at end of file diff --git a/rainbowgum-config-apt/src/main/resources/io/jstach/rainbowgum/apt/ConfigBuilder.java b/rainbowgum-config-apt/src/main/resources/io/jstach/rainbowgum/apt/ConfigBuilder.java index de3d808c..dc395192 100644 --- a/rainbowgum-config-apt/src/main/resources/io/jstach/rainbowgum/apt/ConfigBuilder.java +++ b/rainbowgum-config-apt/src/main/resources/io/jstach/rainbowgum/apt/ConfigBuilder.java @@ -22,7 +22,7 @@ * {@value $$propertyLiteral$$ } * $$type$$ * $$required$$ - * $$defaultValue$$ + * $$defaultValueDoc$$ * $$javadoc$$ * $$/normal$$ @@ -93,6 +93,7 @@ public final class $$builderName$$ { /** * Sets $$#required$$required $$/required$$$$name$$. + * Default is $$defaultValueDoc$$. * @param $$name$$ {@value #$$propertyLiteral$$ } = $$type$$ $$javadoc$$ * @return this builder. */ @@ -128,4 +129,21 @@ public final class $$builderName$$ { $$/properties$$ return this; } + + /** + * Turns the builder into java.util.Properties like Map + * @return properties. + */ + public java.util.Map asProperties() { + java.util.Map m = new java.util.LinkedHashMap<>(); + $$#properties$$ + $$#normal$$ + if (this.$$name$$ != null) { + m.put($$propertyVar$$.key(), String.valueOf(this.$$name$$)); + } + $$/normal$$ + $$/properties$$ + return m; + } + } \ No newline at end of file diff --git a/rainbowgum-rabbitmq/pom.xml b/rainbowgum-rabbitmq/pom.xml index 103ce118..1b3677bf 100644 --- a/rainbowgum-rabbitmq/pom.xml +++ b/rainbowgum-rabbitmq/pom.xml @@ -25,5 +25,13 @@ true provided + + io.jstach.pistachio + pistachio-svc + + + io.jstach.pistachio + pistachio-svc-apt + \ No newline at end of file diff --git a/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/IgnoreMe.java b/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/IgnoreMe.java deleted file mode 100644 index e6e5ba71..00000000 --- a/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/IgnoreMe.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.jstach.rainbowgum.rabbitmq; - -/** - * Ignore me - */ -public record IgnoreMe() { - -} diff --git a/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQInitializer.java b/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQInitializer.java new file mode 100644 index 00000000..d10626b6 --- /dev/null +++ b/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQInitializer.java @@ -0,0 +1,48 @@ +package io.jstach.rainbowgum.rabbitmq; + +import java.io.IOException; +import java.net.URI; + +import io.jstach.rainbowgum.LogConfig; +import io.jstach.rainbowgum.LogOutput; +import io.jstach.rainbowgum.LogOutputProvider; +import io.jstach.rainbowgum.LogProperties; +import io.jstach.rainbowgum.ServiceRegistry; +import io.jstach.rainbowgum.spi.RainbowGumServiceProvider; +import io.jstach.svc.ServiceProvider; + +/** + * RabbitMQ initializer to register output provider with scheme {@value #SCHEME}. + */ +@ServiceProvider(RainbowGumServiceProvider.class) +public class RabbitMQInitializer implements RainbowGumServiceProvider.Initializer { + + /** + * Default constructor for service loader. + */ + public RabbitMQInitializer() { + } + + final static String SCHEME = "amqp"; + + @Override + public void initialize(ServiceRegistry registry, LogConfig config) { + config.outputRegistry().put(SCHEME, RabbitMQOutputProvider.INSTANCE); + } + + private enum RabbitMQOutputProvider implements LogOutputProvider { + + INSTANCE; + + @Override + public LogOutput output(URI uri, String name, LogProperties properties) throws IOException { + name = name.equals("") ? "rabbitmq" : name; + RabbitMQOutputBuilder b = new RabbitMQOutputBuilder(name); + b.uri(uri); + b.fromProperties(properties); + return b.build(); + } + + } + +} diff --git a/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQOutput.java b/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQOutput.java index 5c5773cd..4d222681 100644 --- a/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQOutput.java +++ b/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQOutput.java @@ -7,10 +7,12 @@ import java.util.Map; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; import org.eclipse.jdt.annotation.Nullable; import com.rabbitmq.client.AMQP.BasicProperties; +import com.rabbitmq.client.AlreadyClosedException; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; @@ -22,7 +24,11 @@ import io.jstach.rainbowgum.LogProperties; import io.jstach.rainbowgum.MetaLog; -class RabbitMQOutput implements LogOutput { +/** + * RabbitMQ Output that will write publish messages to a given exchange with a given + * routing key. + */ +public class RabbitMQOutput implements LogOutput { private final URI uri; @@ -38,7 +44,7 @@ class RabbitMQOutput implements LogOutput { private final String exchange; - private final String routingKey; + private final Function routingKeyFunction; private final String connectionName; @@ -46,66 +52,42 @@ class RabbitMQOutput implements LogOutput { private final String exchangeType; - private static final String PREFIX = LogProperties.ROOT_PREFIX + "rabbitmq."; + final static String DEFAULT_EXCHANGE = "logging"; - public static final String EXCHANGE_PROPERTY = PREFIX + "exchange"; + final static String DEFAULT_EXCHANGE_TYPE = "topic"; - static final String ROUTING_KEY_PROPERTY = PREFIX + "routingKey"; - - static final String CONNECTION_NAME_PROPERTY = PREFIX + "connectionName"; - - static final String DECLARE_EXCHANGE_PROPERTY = PREFIX + "declareExchange"; - - static final String EXCHANGE_TYPE_PROPERTY = PREFIX + "exchangeType"; - - static final String USERNAME_PROPERTY = PREFIX + "username"; - - static final String PASSWORD_PROPERTY = PREFIX + "password"; - - static final String PORT_PROPERTY = PREFIX + "port"; - - static final String HOST_PROPERTY = PREFIX + "host"; - - static final String VIRTUAL_HOST_PROPERTY = PREFIX + "virtualHost"; - - static final String APP_ID_PROPERTY = PREFIX + "appId"; - - // public static RabbitMQOutput of(URI uri, LogProperties properties) { - // LogProperties combined = LogProperties.of(List.of(LogProperties.of(uri)), - // properties); - // Property.builder().build(ROUTING_KEY_PROPERTY); - // - // } - - public RabbitMQOutput(URI uri, ConnectionFactory connectionFactory, @Nullable String appId, String exchange, - String routingKey, String connectionName, boolean declareExchange, String exchangeType) { + RabbitMQOutput(URI uri, ConnectionFactory connectionFactory, @Nullable String appId, String exchange, + Function routingKeyFunction, String connectionName, boolean declareExchange, + String exchangeType) { super(); this.uri = uri; this.connectionFactory = connectionFactory; this.appId = appId; this.exchange = exchange; - this.routingKey = routingKey; + this.routingKeyFunction = routingKeyFunction; this.connectionName = connectionName; this.declareExchange = declareExchange; this.exchangeType = exchangeType; } - @ConfigObject(prefix = LogProperties.OUTPUT_PREFIX, name = "RabbitMQOutputBuilder") - public static RabbitMQOutput of(@ConfigObject.PrefixParameter String name, // + @ConfigObject(prefix = LogProperties.OUTPUT_PREFIX) + static RabbitMQOutput of( // + @ConfigObject.PrefixParameter String name, // @Nullable URI uri, // - String exchange, // - String routingKey, // + @ConfigObject.DefaultParameter("DEFAULT_EXCHANGE") String exchange, // + @Nullable String routingKey, // @Nullable Boolean declareExchange, // @Nullable String host, // - @Nullable String username, @Nullable String password, // + @Nullable String username, // + @Nullable String password, // @Nullable Integer port, // @Nullable String appId, // @Nullable String connectionName, // - @Nullable String exchangeType, // + @ConfigObject.DefaultParameter("DEFAULT_EXCHANGE_TYPE") @Nullable String exchangeType, // @Nullable String virtualHost) { connectionName = connectionName == null ? "rainbowgumOutput" : connectionName; declareExchange = declareExchange == null ? false : declareExchange; - exchangeType = exchangeType == null ? "topic" : exchangeType; + exchangeType = exchangeType == null ? DEFAULT_EXCHANGE_TYPE : exchangeType; ConnectionFactory factory = new ConnectionFactory(); if (uri != null) { try { @@ -130,7 +112,15 @@ public static RabbitMQOutput of(@ConfigObject.PrefixParameter String name, // if (virtualHost != null) { factory.setVirtualHost(virtualHost); } - return new RabbitMQOutput(uri, factory, appId, exchange, routingKey, connectionName, false, exchangeType); + Function routingKeyFunction; + if (routingKey != null) { + routingKeyFunction = e -> routingKey; + } + else { + routingKeyFunction = e -> e.level().name(); + } + return new RabbitMQOutput(uri, factory, appId, exchange, routingKeyFunction, connectionName, declareExchange, + exchangeType); } @Override @@ -173,7 +163,7 @@ public void write(LogEvent event, byte[] bytes, ContentType contentType) { byte[] body = bytes; try { var c = channel(); - c.basicPublish(exchange, routingKey, props, body); + c.basicPublish(exchange, routingKeyFunction.apply(event), props, body); } catch (IOException e) { MetaLog.error(RabbitMQOutput.class, e); @@ -244,6 +234,9 @@ public void close() { try { c.close(); } + catch (AlreadyClosedException ae) { + // do nothing. + } catch (IOException | TimeoutException e) { MetaLog.error(getClass(), e); } @@ -252,6 +245,9 @@ public void close() { try { c.close(); } + catch (AlreadyClosedException ae) { + // do nothing. + } catch (IOException | TimeoutException e) { MetaLog.error(getClass(), e); } diff --git a/rainbowgum-rabbitmq/src/main/java/module-info.java b/rainbowgum-rabbitmq/src/main/java/module-info.java index 5b93a20c..672ef948 100644 --- a/rainbowgum-rabbitmq/src/main/java/module-info.java +++ b/rainbowgum-rabbitmq/src/main/java/module-info.java @@ -1,10 +1,15 @@ +import io.jstach.rainbowgum.spi.RainbowGumServiceProvider; + /** * Provides RabbitMQ Rainbow Gum output. */ module io.jstach.rainbowgum.rabbitmq { exports io.jstach.rainbowgum.rabbitmq; - requires io.jstach.rainbowgum; + requires transitive io.jstach.rainbowgum; requires com.rabbitmq.client; requires static org.eclipse.jdt.annotation; + requires static io.jstach.svc; + + provides RainbowGumServiceProvider with io.jstach.rainbowgum.rabbitmq.RabbitMQInitializer; } \ No newline at end of file diff --git a/test/pom.xml b/test/pom.xml index 11421fa4..1d74c442 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -18,6 +18,7 @@ rainbowgum-test-avaje rainbowgum-test-config + rainbowgum-test-rabbitmq diff --git a/test/rainbowgum-test-config/src/main/java/io/jstach/rainbowgum/test/config/ExampleConfig.java b/test/rainbowgum-test-config/src/main/java/io/jstach/rainbowgum/test/config/ExampleConfig.java index 3d2fe2e0..655f4c79 100644 --- a/test/rainbowgum-test-config/src/main/java/io/jstach/rainbowgum/test/config/ExampleConfig.java +++ b/test/rainbowgum-test-config/src/main/java/io/jstach/rainbowgum/test/config/ExampleConfig.java @@ -16,7 +16,13 @@ public record ExampleConfig(String name, Integer count, @Nullable URI uri) { * @return config */ @ConfigObject(name = "ExampleConfigBuilder", prefix = "logging.example.{name}.") - public static ExampleConfig of(@ConfigObject.PrefixParameter String name, Integer count, @Nullable URI uri) { + public static ExampleConfig of( // + @ConfigObject.PrefixParameter String name, // + @ConfigObject.DefaultParameter("DEFAULT_COUNT") Integer count, // + @Nullable URI uri) { return new ExampleConfig(name, count, uri); } + + public static final int DEFAULT_COUNT = 8080; + } diff --git a/test/rainbowgum-test-rabbitmq/pom.xml b/test/rainbowgum-test-rabbitmq/pom.xml new file mode 100644 index 00000000..eb18e92b --- /dev/null +++ b/test/rainbowgum-test-rabbitmq/pom.xml @@ -0,0 +1,29 @@ + + 4.0.0 + + io.jstach.rainbowgum + rainbowgum-test-parent + 0.1.3-SNAPSHOT + + rainbowgum-test-rabbitmq + + + ${project.groupId} + rainbowgum + + + ${project.groupId} + rainbowgum-rabbitmq + + + org.slf4j + slf4j-api + + + org.testcontainers + rabbitmq + 1.19.3 + test + + + \ No newline at end of file diff --git a/test/rainbowgum-test-rabbitmq/src/main/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetup.java b/test/rainbowgum-test-rabbitmq/src/main/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetup.java new file mode 100644 index 00000000..a3199ec9 --- /dev/null +++ b/test/rainbowgum-test-rabbitmq/src/main/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetup.java @@ -0,0 +1,41 @@ +package io.jstach.rainbowgum.test.rabbitmq; + +import java.lang.System.Logger.Level; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.ServiceLoader; + +import org.slf4j.LoggerFactory; + +import io.jstach.rainbowgum.LogConfig; +import io.jstach.rainbowgum.LogProperties; +import io.jstach.rainbowgum.RainbowGum; +import io.jstach.rainbowgum.spi.RainbowGumServiceProvider; + +public class RabbitMQSetup { + + public static void main(String[] args) { + + Map properties = new LinkedHashMap<>(); + properties.put(LogProperties.OUTPUT_PROPERTY, "amqp"); + properties.put(LogProperties.OUTPUT_PROPERTY + ".amqp", "amqp:///"); + var config = LogConfig.builder().logProperties(properties::get).build(); + RainbowGumServiceProvider.runInitializers(ServiceLoader.load(RainbowGumServiceProvider.class), config); + RainbowGum.set(() -> RainbowGum.builder(config).build()); + // RainbowGum.set(() -> gum); + var logger = LoggerFactory.getLogger(RabbitMQSetup.class); + logger.info("hello"); + } + + public static void run(Map properties) { + var config = LogConfig.builder().logProperties(properties::get).build(); + RainbowGumServiceProvider.runInitializers(ServiceLoader.load(RainbowGumServiceProvider.class), config); + RainbowGum.set(() -> RainbowGum.builder(config).build()); + RainbowGum.of(); + // RainbowGum.set(() -> gum); + System.getLogger(RabbitMQSetup.class.getName()).log(Level.INFO, "hello"); + //var logger = LoggerFactory.getLogger(RabbitMQSetup.class); + //logger.info("hello"); + } + +} diff --git a/test/rainbowgum-test-rabbitmq/src/main/java/io/jstach/rainbowgum/test/rabbitmq/package-info.java b/test/rainbowgum-test-rabbitmq/src/main/java/io/jstach/rainbowgum/test/rabbitmq/package-info.java new file mode 100644 index 00000000..3f9c11ac --- /dev/null +++ b/test/rainbowgum-test-rabbitmq/src/main/java/io/jstach/rainbowgum/test/rabbitmq/package-info.java @@ -0,0 +1,2 @@ +@org.eclipse.jdt.annotation.NonNullByDefault +package io.jstach.rainbowgum.test.rabbitmq; \ No newline at end of file diff --git a/test/rainbowgum-test-rabbitmq/src/test/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetupTest.java b/test/rainbowgum-test-rabbitmq/src/test/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetupTest.java new file mode 100644 index 00000000..e7958481 --- /dev/null +++ b/test/rainbowgum-test-rabbitmq/src/test/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetupTest.java @@ -0,0 +1,45 @@ +package io.jstach.rainbowgum.test.rabbitmq; + +import java.net.URI; +import java.util.Map; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.RabbitMQContainer; +import org.testcontainers.utility.DockerImageName; + +import io.jstach.rainbowgum.LogProperties; +import io.jstach.rainbowgum.rabbitmq.RabbitMQOutputBuilder; + +class RabbitMQSetupTest { + + static RabbitMQContainer rabbit = new RabbitMQContainer(DockerImageName.parse("rabbitmq:3.7.25-management-alpine")); + + @BeforeAll + public static void beforeAll() { + rabbit.start(); + } + + @AfterAll + public static void afterAll() { + rabbit.stop(); + } + + @Test + void testMain() { + RabbitMQOutputBuilder b = new RabbitMQOutputBuilder("amqp"); + //b.host(rabbit.getHost()); + //b.port(rabbit.getAmqpPort()); + b.uri(URI.create(rabbit.getAmqpUrl())); + b.declareExchange(true); + Map properties = b.asProperties(); + properties.put(LogProperties.OUTPUT_PROPERTY, "amqp"); + properties.put(LogProperties.OUTPUT_PROPERTY + ".amqp", "amqp:///"); + System.out.println(properties); + System.out.println(rabbit.getAmqpUrl()); + //LoggerFactoryFriend.reset(); + RabbitMQSetup.run(properties); + } + +}