diff --git a/.gitignore b/.gitignore index 72d52d29..025bea84 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ build/ *.project *.class .DS_Store -jsonb-generator/.factorypath +*.factorypath +jsonb-generator/io.avaje.jsonb.spi.JsonbExtension diff --git a/blackbox-test/src/main/java/module-info.java b/blackbox-test/src/main/java/module-info.java index d345c7a9..6536abcb 100644 --- a/blackbox-test/src/main/java/module-info.java +++ b/blackbox-test/src/main/java/module-info.java @@ -3,5 +3,6 @@ requires static io.avaje.jsonb; requires java.validation; - provides io.avaje.jsonb.Jsonb.GeneratedComponent with org.example.jsonb.GeneratedJsonComponent; -} + provides io.avaje.jsonb.spi.JsonbExtension with org.example.customer.customtype.CustomTypeComponent, org.example.jsonb.GeneratedJsonComponent; + +} \ No newline at end of file diff --git a/blackbox-test/src/main/java/org/example/customer/customtype/CustomTypeComponent.java b/blackbox-test/src/main/java/org/example/customer/customtype/CustomTypeComponent.java index 6dfdbba2..ee7bdc80 100644 --- a/blackbox-test/src/main/java/org/example/customer/customtype/CustomTypeComponent.java +++ b/blackbox-test/src/main/java/org/example/customer/customtype/CustomTypeComponent.java @@ -2,6 +2,7 @@ import io.avaje.jsonb.*; +import io.avaje.jsonb.spi.JsonbComponent; /** * Register via service loading. diff --git a/blackbox-test/src/main/resources/META-INF/services/io.avaje.jsonb.JsonbComponent b/blackbox-test/src/main/resources/META-INF/services/io.avaje.jsonb.spi.JsonbExtension similarity index 100% rename from blackbox-test/src/main/resources/META-INF/services/io.avaje.jsonb.JsonbComponent rename to blackbox-test/src/main/resources/META-INF/services/io.avaje.jsonb.spi.JsonbExtension diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ComponentReader.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ComponentReader.java index 0c9838e6..aa2ec5db 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ComponentReader.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ComponentReader.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; final class ComponentReader { @@ -24,14 +25,18 @@ final class ComponentReader { } void read() { - String componentFullName = loadMetaInfServices(); - if (componentFullName != null) { - TypeElement moduleType = typeElement(componentFullName); - if (moduleType != null) { - componentMetaData.setFullName(componentFullName); - readMetaData(moduleType); - } - } + loadMetaInf().stream() + .map(APContext::typeElement) + .filter(Objects::nonNull) + .filter(t -> "io.avaje.jsonb.spi.GeneratedComponent".equals(t.getSuperclass().toString())) + .findFirst() + .ifPresent( + moduleType -> { + if (moduleType != null) { + componentMetaData.setFullName(moduleType.getQualifiedName().toString()); + readMetaData(moduleType); + } + }); } /** @@ -56,11 +61,6 @@ private void readMetaData(TypeElement moduleType) { } } - private String loadMetaInfServices() { - final List lines = loadMetaInf(); - return lines.isEmpty() ? null : lines.get(0); - } - private List loadMetaInf() { try { FileObject fileObject = processingEnv() diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/Constants.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/Constants.java index 4011e244..b76c759f 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/Constants.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/Constants.java @@ -2,7 +2,7 @@ final class Constants { - static final String META_INF_COMPONENT = "META-INF/services/io.avaje.jsonb.Jsonb$GeneratedComponent"; + static final String META_INF_COMPONENT = "META-INF/services/io.avaje.jsonb.spi.JsonbExtension"; static final String JSONB_WILD = "io.avaje.jsonb.*"; static final String JSONB_SPI = "io.avaje.jsonb.spi.*"; static final String JSONB = "io.avaje.jsonb.Jsonb"; diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ProcessingContext.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ProcessingContext.java index 2ca9ef5c..78079e75 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ProcessingContext.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ProcessingContext.java @@ -108,7 +108,7 @@ static void validateModule(String fqn) { var buildPluginAvailable = buildPluginAvailable(); if (noProvides && !buildPluginAvailable) { - logError(module, "Missing `provides io.avaje.jsonb.Jsonb.GeneratedComponent with %s;`", fqn); + logError(module, "Missing `provides io.avaje.jsonb.spi.JsonbExtension with %s;`", fqn); } final var noDirectJsonb = diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/SimpleComponentWriter.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/SimpleComponentWriter.java index 92d65d36..6d339385 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/SimpleComponentWriter.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/SimpleComponentWriter.java @@ -3,7 +3,9 @@ import static io.avaje.jsonb.generator.ProcessingContext.createMetaInfWriterFor; import static io.avaje.jsonb.generator.APContext.createSourceFile; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.io.Writer; import java.util.List; import java.util.Set; @@ -11,6 +13,7 @@ import javax.tools.FileObject; import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; final class SimpleComponentWriter { @@ -48,14 +51,36 @@ void write() throws IOException { } void writeMetaInf() throws IOException { + var services = readExistingMetaInfServices(); final FileObject fileObject = createMetaInfWriterFor(Constants.META_INF_COMPONENT); if (fileObject != null) { + services.add(metaData.fullName()); final Writer writer = fileObject.openWriter(); - writer.write(metaData.fullName()); + writer.write(String.join("\n", services)); writer.close(); } } + private static Set readExistingMetaInfServices() { + var services = new TreeSet(); + try (final var file = + APContext.filer() + .getResource(StandardLocation.CLASS_OUTPUT, "", Constants.META_INF_COMPONENT) + .toUri() + .toURL() + .openStream(); + final var buffer = new BufferedReader(new InputStreamReader(file)); ) { + + String line; + while ((line = buffer.readLine()) != null) { + line.replaceAll("\\s", "").replace(",", "\n").lines().forEach(services::add); + } + } catch (Exception e) { + // not a critical error + } + return services; + } + private void writeRegister() { writer.append(" @Override").eol(); writer.append(" public void register(Jsonb.Builder builder) {").eol(); @@ -91,7 +116,7 @@ private void writeClassStart() { writeMetaDataEntry(all); writer.append("})").eol(); - writer.append("public class %s implements Jsonb.GeneratedComponent {", shortName).eol().eol(); + writer.append("public class %s implements GeneratedComponent {", shortName).eol().eol(); } private void writeMetaDataEntry(List entries) { diff --git a/jsonb-inject-plugin/pom.xml b/jsonb-inject-plugin/pom.xml index 9ce44316..c508969e 100644 --- a/jsonb-inject-plugin/pom.xml +++ b/jsonb-inject-plugin/pom.xml @@ -18,15 +18,15 @@ io.avaje avaje-jsonb - 1.10-RC1 + 1.11 provided true - + io.avaje avaje-inject - 9.12 + 10.0-RC7 provided true diff --git a/jsonb-inject-plugin/src/main/java/io/avaje/jsonb/inject/DefaultJsonbProvider.java b/jsonb-inject-plugin/src/main/java/io/avaje/jsonb/inject/DefaultJsonbProvider.java index ef0be6bf..36fb948a 100644 --- a/jsonb-inject-plugin/src/main/java/io/avaje/jsonb/inject/DefaultJsonbProvider.java +++ b/jsonb-inject-plugin/src/main/java/io/avaje/jsonb/inject/DefaultJsonbProvider.java @@ -1,23 +1,26 @@ package io.avaje.jsonb.inject; +import java.lang.reflect.Type; + import io.avaje.inject.BeanScopeBuilder; +import io.avaje.inject.spi.InjectPlugin; import io.avaje.jsonb.Jsonb; import io.avaje.jsonb.stream.BufferRecycleStrategy; /** * Plugin for avaje inject that provides a default Jsonb instance. */ -public final class DefaultJsonbProvider implements io.avaje.inject.spi.Plugin { +public final class DefaultJsonbProvider implements InjectPlugin { @Override - public Class[] provides() { - return new Class[]{Jsonb.class}; + public Type[] provides() { + return new Type[]{Jsonb.class}; } @Override public void apply(BeanScopeBuilder builder) { builder.provideDefault(null, Jsonb.class, () -> { - var props = builder.propertyPlugin(); + var props = builder.configPlugin(); return Jsonb.builder() .failOnUnknown(props.equalTo("jsonb.deserialize.failOnUnknown", "true")) diff --git a/jsonb-inject-plugin/src/main/java/module-info.java b/jsonb-inject-plugin/src/main/java/module-info.java index 3dffdedc..3bf1d5da 100644 --- a/jsonb-inject-plugin/src/main/java/module-info.java +++ b/jsonb-inject-plugin/src/main/java/module-info.java @@ -3,5 +3,5 @@ requires transitive io.avaje.jsonb; requires transitive io.avaje.inject; - provides io.avaje.inject.spi.Plugin with io.avaje.jsonb.inject.DefaultJsonbProvider; + provides io.avaje.inject.spi.InjectExtension with io.avaje.jsonb.inject.DefaultJsonbProvider; } diff --git a/jsonb-inject-plugin/src/main/resources/META-INF/services/io.avaje.inject.spi.Plugin b/jsonb-inject-plugin/src/main/resources/META-INF/services/io.avaje.inject.spi.InjectExtension similarity index 100% rename from jsonb-inject-plugin/src/main/resources/META-INF/services/io.avaje.inject.spi.Plugin rename to jsonb-inject-plugin/src/main/resources/META-INF/services/io.avaje.inject.spi.InjectExtension diff --git a/jsonb/pom.xml b/jsonb/pom.xml index 872bdc54..99371a06 100644 --- a/jsonb/pom.xml +++ b/jsonb/pom.xml @@ -25,6 +25,13 @@ 4.0.10 provided true + + + + io.avaje + avaje-spi-service + ${spi.version} + true diff --git a/jsonb/src/main/java/io/avaje/jsonb/Jsonb.java b/jsonb/src/main/java/io/avaje/jsonb/Jsonb.java index 0d83ce14..7dc81369 100644 --- a/jsonb/src/main/java/io/avaje/jsonb/Jsonb.java +++ b/jsonb/src/main/java/io/avaje/jsonb/Jsonb.java @@ -2,8 +2,8 @@ import io.avaje.jsonb.core.DefaultBootstrap; import io.avaje.jsonb.spi.AdapterFactory; -import io.avaje.jsonb.spi.Bootstrap; import io.avaje.jsonb.spi.JsonStreamAdapter; +import io.avaje.jsonb.spi.JsonbComponent; import io.avaje.jsonb.spi.PropertyNames; import io.avaje.jsonb.stream.BufferRecycleStrategy; import io.avaje.jsonb.stream.JsonOutput; @@ -13,8 +13,6 @@ import java.io.Reader; import java.io.Writer; import java.lang.reflect.Type; -import java.util.Iterator; -import java.util.ServiceLoader; /** * Provides access to json adapters by type. @@ -102,10 +100,6 @@ public interface Jsonb { * } */ static Builder builder() { - Iterator bootstrapService = ServiceLoader.load(Bootstrap.class).iterator(); - if (bootstrapService.hasNext()) { - return bootstrapService.next().builder(); - } return DefaultBootstrap.builder(); } @@ -424,16 +418,4 @@ interface AdapterBuilder { */ JsonAdapter build(Jsonb jsonb); } - - /** - * Components register JsonAdapters Jsonb.Builder - */ - @FunctionalInterface - interface GeneratedComponent extends JsonbComponent { - - /** - * Register JsonAdapters with the Builder. - */ - void register(Builder builder); - } } diff --git a/jsonb/src/main/java/io/avaje/jsonb/JsonbComponent.java b/jsonb/src/main/java/io/avaje/jsonb/JsonbComponent.java deleted file mode 100644 index a8cfa37a..00000000 --- a/jsonb/src/main/java/io/avaje/jsonb/JsonbComponent.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.avaje.jsonb; - -/** - * User defined components to register custom JsonAdapters with Jsonb.Builder. - *

- * These are service loaded when Jsonb starts. They can be specified in - * {@code META-INF/services/io.avaje.jsonb.JsonbComponent} or when using - * java module system via a {@code provides} clause in module-info. - */ -@FunctionalInterface -public interface JsonbComponent { - - /** - * Register JsonAdapters with the Builder. - */ - void register(Jsonb.Builder builder); -} diff --git a/jsonb/src/main/java/io/avaje/jsonb/core/DJsonb.java b/jsonb/src/main/java/io/avaje/jsonb/core/DJsonb.java index 1ea41f5b..1d8174ce 100644 --- a/jsonb/src/main/java/io/avaje/jsonb/core/DJsonb.java +++ b/jsonb/src/main/java/io/avaje/jsonb/core/DJsonb.java @@ -41,9 +41,9 @@ final class DJsonb implements Jsonb { if (adapter != null) { this.io = adapter; } else { - Iterator iterator = ServiceLoader.load(AdapterFactory.class).iterator(); - if (iterator.hasNext()) { - this.io = iterator.next().create(serializeNulls, serializeEmpty, failOnUnknown); + var adapterFactoryOptional = ExtensionLoader.adapterFactory(); + if (adapterFactoryOptional.isPresent()) { + this.io = adapterFactoryOptional.get().create(serializeNulls, serializeEmpty, failOnUnknown); } else { this.io = new JsonStream(serializeNulls, serializeEmpty, failOnUnknown, strategy); } @@ -290,10 +290,10 @@ public Builder add(JsonAdapter.Factory factory) { private void registerComponents() { // first register all user defined JsonbComponent - for (JsonbComponent next : ServiceLoader.load(JsonbComponent.class)) { + for (JsonbComponent next : ExtensionLoader.userComponents()) { next.register(this); } - for (GeneratedComponent next : ServiceLoader.load(GeneratedComponent.class)) { + for (GeneratedComponent next : ExtensionLoader.generatedComponents()) { next.register(this); } } diff --git a/jsonb/src/main/java/io/avaje/jsonb/core/ExtensionLoader.java b/jsonb/src/main/java/io/avaje/jsonb/core/ExtensionLoader.java new file mode 100644 index 00000000..1d4dcd83 --- /dev/null +++ b/jsonb/src/main/java/io/avaje/jsonb/core/ExtensionLoader.java @@ -0,0 +1,43 @@ +package io.avaje.jsonb.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.ServiceLoader; + +import io.avaje.jsonb.spi.AdapterFactory; +import io.avaje.jsonb.spi.GeneratedComponent; +import io.avaje.jsonb.spi.JsonbComponent; +import io.avaje.jsonb.spi.JsonbExtension; + +/** Load all the services using the common service interface. */ +final class ExtensionLoader { + + private static final List generatedComponents = new ArrayList<>(); + private static final List userComponents = new ArrayList<>(); + private static Optional adapterFactory = Optional.empty(); + + static { + for (var spi : ServiceLoader.load(JsonbExtension.class)) { + if (spi instanceof GeneratedComponent) { + generatedComponents.add((GeneratedComponent) spi); + } else if (spi instanceof JsonbComponent) { + userComponents.add((JsonbComponent) spi); + } else if (spi instanceof AdapterFactory) { + adapterFactory = Optional.of((AdapterFactory) spi); + } + } + } + + static List generatedComponents() { + return generatedComponents; + } + + static List userComponents() { + return userComponents; + } + + static Optional adapterFactory() { + return adapterFactory; + } +} diff --git a/jsonb/src/main/java/io/avaje/jsonb/spi/AdapterFactory.java b/jsonb/src/main/java/io/avaje/jsonb/spi/AdapterFactory.java index 6bf37471..346a7101 100644 --- a/jsonb/src/main/java/io/avaje/jsonb/spi/AdapterFactory.java +++ b/jsonb/src/main/java/io/avaje/jsonb/spi/AdapterFactory.java @@ -3,7 +3,7 @@ /** * Factory that is service loaded to create the adapter for underlying json parsing and generation. */ -public interface AdapterFactory { +public interface AdapterFactory extends JsonbExtension { /** * Create the adapter to use for the underlying json parsing and generation. diff --git a/jsonb/src/main/java/io/avaje/jsonb/spi/Bootstrap.java b/jsonb/src/main/java/io/avaje/jsonb/spi/Bootstrap.java deleted file mode 100644 index a48ce53b..00000000 --- a/jsonb/src/main/java/io/avaje/jsonb/spi/Bootstrap.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.avaje.jsonb.spi; - -import io.avaje.jsonb.Jsonb; - -/** - * Bootstrap Jsonb. - */ -public interface Bootstrap { - - /** - * Create and return a Builder (with an underling SPI implementation). - *

- */ - Jsonb.Builder builder(); -} diff --git a/jsonb/src/main/java/io/avaje/jsonb/spi/GeneratedComponent.java b/jsonb/src/main/java/io/avaje/jsonb/spi/GeneratedComponent.java new file mode 100644 index 00000000..7709edc7 --- /dev/null +++ b/jsonb/src/main/java/io/avaje/jsonb/spi/GeneratedComponent.java @@ -0,0 +1,5 @@ +package io.avaje.jsonb.spi; + +/** Component interface registers generated JsonAdapters to the Jsonb.Builder */ +@FunctionalInterface +public interface GeneratedComponent extends JsonbComponent, JsonbExtension {} diff --git a/jsonb/src/main/java/io/avaje/jsonb/spi/JsonbComponent.java b/jsonb/src/main/java/io/avaje/jsonb/spi/JsonbComponent.java new file mode 100644 index 00000000..a77c2763 --- /dev/null +++ b/jsonb/src/main/java/io/avaje/jsonb/spi/JsonbComponent.java @@ -0,0 +1,18 @@ +package io.avaje.jsonb.spi; + +import io.avaje.jsonb.Jsonb; + +/** + * User defined class to configure the Jsonb Builder instance or register custom JsonAdapters on + * startup. + * + *

These are service loaded when Jsonb starts. They can be specified in {@code + * META-INF/services/io.avaje.jsonb.spi.JsonbExtension} or when using java module system via a + * {@code provides} clause in module-info. + */ +@FunctionalInterface +public interface JsonbComponent extends JsonbExtension { + + /** Register JsonAdapters with the Builder. */ + void register(Jsonb.Builder builder); +} diff --git a/jsonb/src/main/java/io/avaje/jsonb/spi/JsonbExtension.java b/jsonb/src/main/java/io/avaje/jsonb/spi/JsonbExtension.java new file mode 100644 index 00000000..af94bfb3 --- /dev/null +++ b/jsonb/src/main/java/io/avaje/jsonb/spi/JsonbExtension.java @@ -0,0 +1,11 @@ +package io.avaje.jsonb.spi; + +/** + * Superclass for all Jsonb SPI classes. + * + * @see JsonbComponent + * @see GeneratedComponent + * @see AdapterFactory + */ +public interface JsonbExtension { +} diff --git a/jsonb/src/main/java/module-info.java b/jsonb/src/main/java/module-info.java index 6f54a3b2..f88125de 100644 --- a/jsonb/src/main/java/module-info.java +++ b/jsonb/src/main/java/module-info.java @@ -5,11 +5,9 @@ exports io.avaje.jsonb.stream; exports io.avaje.jsonb.spi; - uses io.avaje.jsonb.spi.AdapterFactory; - uses io.avaje.jsonb.spi.Bootstrap; - uses io.avaje.jsonb.JsonbComponent; - uses io.avaje.jsonb.Jsonb.GeneratedComponent; + uses io.avaje.jsonb.spi.JsonbExtension; requires static io.helidon.webserver; + requires static io.avaje.spi; } diff --git a/pom.xml b/pom.xml index c4950e3b..9d80b12e 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ false true + 1.11