diff --git a/config/config/src/main/java/io/helidon/config/Config.java b/config/config/src/main/java/io/helidon/config/Config.java index 13100181c..ccae99f06 100644 --- a/config/config/src/main/java/io/helidon/config/Config.java +++ b/config/config/src/main/java/io/helidon/config/Config.java @@ -374,6 +374,22 @@ public interface Config { return new BuilderImpl(); } + /** + * Creates a new {@link Config} loaded from the specified {@link ConfigSource}s. + * No other sources will be included. + * + * @param configSources ordered list of configuration sources + * @return new instance of {@link Config} + * @see #builder(Supplier[]) + */ + @SafeVarargs + static Config just(Supplier... configSources) { + return builder(configSources) + .disableEnvironmentVariablesSource() + .disableSystemPropertiesSource() + .build(); + } + /** * Returns the {@code Context} instance associated with the current * {@code Config} node that allows the application to access the last loaded diff --git a/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/NameReader.java b/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/NameReader.java index e67ef9b82..761dd61ac 100644 --- a/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/NameReader.java +++ b/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/NameReader.java @@ -27,6 +27,8 @@ import io.helidon.media.common.MessageBodyReaderContext; public class NameReader implements MessageBodyReader { + private static final MediaType TYPE = MediaType.parse("application/name"); + private NameReader() { } @@ -41,11 +43,11 @@ public class NameReader implements MessageBodyReader { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { return context.contentType() - .map(ct -> MediaType.parse("application/name").equals(ct)) - .map(acceptable -> acceptable && Name.class.isAssignableFrom(type.rawType())) - .orElse(false); + .filter(TYPE::equals) + .map(it -> PredicateResult.supports(Name.class, type)) + .orElse(PredicateResult.NOT_SUPPORTED); } } diff --git a/media/common/src/main/java/io/helidon/media/common/ByteChannelBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/ByteChannelBodyWriter.java index d3bed5d47..273e95eb2 100644 --- a/media/common/src/main/java/io/helidon/media/common/ByteChannelBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/ByteChannelBodyWriter.java @@ -44,8 +44,8 @@ public final class ByteChannelBodyWriter implements MessageBodyWriter type, MessageBodyWriterContext context) { - return ReadableByteChannel.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(ReadableByteChannel.class, type); } @Override diff --git a/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyWriter.java index ad8df48d7..98079a621 100644 --- a/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyWriter.java @@ -30,14 +30,14 @@ import io.helidon.common.reactive.Single; public final class CharSequenceBodyWriter implements MessageBodyWriter { /** - * Enforce the use of {@link #get()}. + * Enforce the use of {@link #create()}. */ private CharSequenceBodyWriter() { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { - return CharSequence.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(CharSequence.class, type); } @Override diff --git a/media/common/src/main/java/io/helidon/media/common/FileBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/FileBodyWriter.java index 502bbe80e..650ed81ed 100644 --- a/media/common/src/main/java/io/helidon/media/common/FileBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/FileBodyWriter.java @@ -38,14 +38,14 @@ import static io.helidon.media.common.ByteChannelBodyWriter.DEFAULT_RETRY_SCHEMA public final class FileBodyWriter implements MessageBodyWriter { /** - * Enforces the use of {@link #get()}. + * Enforces the use of {@link #create()}. */ private FileBodyWriter() { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { - return File.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(File.class, type); } @Override @@ -64,7 +64,7 @@ public final class FileBodyWriter implements MessageBodyWriter { } /** - * Implementation of {@link MultiMapper} that converts {@link File} to a + * Implementation of {@link Mapper} that converts {@link File} to a * publisher of {@link DataChunk}. */ private static final class FileToChunks implements Mapper> { diff --git a/media/common/src/main/java/io/helidon/media/common/InputStreamBodyReader.java b/media/common/src/main/java/io/helidon/media/common/InputStreamBodyReader.java index 353b55d09..453c7ee93 100644 --- a/media/common/src/main/java/io/helidon/media/common/InputStreamBodyReader.java +++ b/media/common/src/main/java/io/helidon/media/common/InputStreamBodyReader.java @@ -34,8 +34,8 @@ public class InputStreamBodyReader implements MessageBodyReader { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { - return InputStream.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + return PredicateResult.supports(InputStream.class, type); } @Override diff --git a/media/common/src/main/java/io/helidon/media/common/MediaContext.java b/media/common/src/main/java/io/helidon/media/common/MediaContext.java index 009082275..2d6591cfd 100644 --- a/media/common/src/main/java/io/helidon/media/common/MediaContext.java +++ b/media/common/src/main/java/io/helidon/media/common/MediaContext.java @@ -15,9 +15,19 @@ */ package io.helidon.media.common; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.ServiceLoader; +import java.util.stream.Collectors; +import io.helidon.common.serviceloader.HelidonServiceLoader; import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.media.common.spi.MediaSupportProvider; /** * Media support. @@ -93,10 +103,28 @@ public final class MediaContext { public static class Builder implements io.helidon.common.Builder, MediaContextBuilder { + private static final String SERVICE_NAME = "name"; + private static final String DEFAULTS_NAME = "defaults"; + private static final String DEFAULTS_INCLUDE_STACK_TRACES = "include-stack-traces"; + + private static final int DEFAULTS_PRIORITY = 100; + private static final int BUILDER_PRIORITY = 200; + private static final int LOADER_PRIORITY = 300; + + private final HelidonServiceLoader.Builder services = HelidonServiceLoader + .builder(ServiceLoader.load(MediaSupportProvider.class)); + + private final List> builderReaders = new ArrayList<>(); + private final List> builderStreamReaders = new ArrayList<>(); + private final List> builderWriters = new ArrayList<>(); + private final List> builderStreamWriter = new ArrayList<>(); + private final List mediaSupports = new ArrayList<>(); + private final Map> servicesConfig = new HashMap<>(); private final MessageBodyReaderContext readerContext; private final MessageBodyWriterContext writerContext; private boolean registerDefaults = true; - private boolean includeStackTraces = false; + private boolean discoverServices = false; + private boolean filterServices = false; private Builder() { this.readerContext = MessageBodyReaderContext.create(); @@ -112,71 +140,87 @@ public final class MediaContext { * description * * - * server-errors-include-stack-traces - * Whether stack traces should be included in the response (server only) - * - * * register-defaults * Whether to register default reader and writers * + * + * discover-services + * Whether to discover services via service loader + * + * + * filter-services + * Whether to filter discovered services by service names in services section + * + * + * services + * Configuration section for each service. Each entry has to have "name" parameter. + * It is also used for filtering of loaded services. + * * + * * @param config a {@link Config} * @return this {@link Builder} */ public Builder config(Config config) { - config.get("server-errors-include-stack-traces").asBoolean().ifPresent(this::includeStackTraces); config.get("register-defaults").asBoolean().ifPresent(this::registerDefaults); + config.get("discover-services").asBoolean().ifPresent(this::discoverServices); + config.get("filter-services").asBoolean().ifPresent(this::filterServices); + config.get("services") + .asNodeList() + .ifPresent(it -> it.forEach(serviceConfig -> { + String name = serviceConfig.get(SERVICE_NAME).asString().get(); + servicesConfig.merge(name, + serviceConfig.detach().asMap().orElseGet(Map::of), + (first, second) -> { + HashMap result = new HashMap<>(first); + result.putAll(second); + return result; + }); + })); return this; } @Override public Builder addMediaSupport(MediaSupport mediaSupport) { Objects.requireNonNull(mediaSupport); - mediaSupport.register(readerContext, writerContext); + mediaSupports.add(mediaSupport); + return this; + } + + /** + * Adds new instance of {@link MediaSupport} with specific priority. + * + * @param mediaSupport media support + * @param priority priority + * @return updated instance of the builder + */ + public Builder addMediaSupport(MediaSupport mediaSupport, int priority) { + Objects.requireNonNull(mediaSupport); + services.addService((config) -> mediaSupport, priority); return this; } @Override public Builder addReader(MessageBodyReader reader) { - readerContext.registerReader(reader); + builderReaders.add(reader); return this; } @Override public Builder addStreamReader(MessageBodyStreamReader streamReader) { - readerContext.registerReader(streamReader); + builderStreamReaders.add(streamReader); return this; } @Override public Builder addWriter(MessageBodyWriter writer) { - writerContext.registerWriter(writer); + builderWriters.add(writer); return this; } @Override public Builder addStreamWriter(MessageBodyStreamWriter streamWriter) { - writerContext.registerWriter(streamWriter); - return this; - } - - /** - * Register a new stream reader. - * @param reader reader to register - * @return this builder instance - */ - public Builder registerStreamReader(MessageBodyStreamReader reader) { - readerContext.registerReader(reader); - return this; - } - - /** - * Register a new stream writer. - * @param writer writer to register - * @return this builder instance - */ - public Builder registerStreamWriter(MessageBodyStreamWriter writer) { - writerContext.registerWriter(writer); + builderStreamWriter.add(streamWriter); return this; } @@ -200,17 +244,98 @@ public final class MediaContext { * @return this builder instance */ public Builder includeStackTraces(boolean includeStackTraces) { - this.includeStackTraces = includeStackTraces; + servicesConfig.computeIfAbsent(DEFAULTS_NAME, k -> new HashMap<>()) + .put(DEFAULTS_INCLUDE_STACK_TRACES, Boolean.toString(includeStackTraces)); + return this; + } + + /** + * Whether Java Service Loader should be used to load {@link MediaSupportProvider}. + * + * @param discoverServices use Java Service Loader + * @return this builder instance + */ + public Builder discoverServices(boolean discoverServices) { + this.discoverServices = discoverServices; + return this; + } + + /** + * Whether services loaded by Java Service Loader should be filtered. + * All of the services which should pass the filter, have to be present under {@code services} section of configuration. + * + * @param filterServices filter services + * @return this builder instance + */ + public Builder filterServices(boolean filterServices) { + this.filterServices = filterServices; return this; } @Override public MediaContext build() { - if (registerDefaults) { - addMediaSupport(DefaultMediaSupport.create(includeStackTraces)); + //Remove all service names from the obtained service configurations + servicesConfig.forEach((key, values) -> values.remove(SERVICE_NAME)); + if (filterServices) { + this.services.useSystemServiceLoader(false); + filterServices(); + } else { + this.services.useSystemServiceLoader(discoverServices); } + if (registerDefaults) { + this.services.addService(new DefaultsProvider(), DEFAULTS_PRIORITY); + } + this.services.defaultPriority(LOADER_PRIORITY) + .addService(config -> new MediaSupport() { + @Override + public void register(MessageBodyReaderContext readerContext, MessageBodyWriterContext writerContext) { + builderReaders.forEach(readerContext::registerReader); + builderStreamReaders.forEach(readerContext::registerReader); + builderWriters.forEach(writerContext::registerWriter); + builderStreamWriter.forEach(writerContext::registerWriter); + } + }, BUILDER_PRIORITY) + .addService(config -> new MediaSupport() { + @Override + public void register(MessageBodyReaderContext readerContext, MessageBodyWriterContext writerContext) { + mediaSupports.forEach(it -> it.register(readerContext, writerContext)); + } + }, BUILDER_PRIORITY) + .build() + .asList() + .stream() + .map(it -> it.create(Config.just(ConfigSources.create(servicesConfig.getOrDefault(it.configKey(), + new HashMap<>()))))) + .collect(Collectors.toCollection(LinkedList::new)) + .descendingIterator() + .forEachRemaining(mediaService -> mediaService.register(readerContext, writerContext)); + return new MediaContext(readerContext, writerContext); } + + private void filterServices() { + HelidonServiceLoader.builder(ServiceLoader.load(MediaSupportProvider.class)) + .defaultPriority(LOADER_PRIORITY) + .build() + .asList() + .stream() + .filter(provider -> servicesConfig.containsKey(provider.configKey())) + .forEach(services::addService); + } + } + + private static final class DefaultsProvider implements MediaSupportProvider { + + @Override + public String configKey() { + return Builder.DEFAULTS_NAME; + } + + @Override + public MediaSupport create(Config config) { + boolean includeStackTraces = config.get(Builder.DEFAULTS_INCLUDE_STACK_TRACES).asBoolean().orElse(false); + return DefaultMediaSupport.create(includeStackTraces); + } } } diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyContext.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyContext.java index b890bf6bb..c06a714e0 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyContext.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyContext.java @@ -449,7 +449,7 @@ public abstract class MessageBodyContext implements MessageBodyFilters { } /** - * {@link Operator} adapter for {@link Filter}. + * {@link MessageBodyOperator} adapter for {@link MessageBodyFilter}. */ private static final class FilterOperator implements MessageBodyOperator, MessageBodyFilter { @@ -460,8 +460,10 @@ public abstract class MessageBodyContext implements MessageBodyFilters { } @Override - public boolean accept(GenericType type, MessageBodyContext context) { - return this.getClass().equals(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyContext context) { + return this.getClass().equals(type.rawType()) + ? PredicateResult.SUPPORTED + : PredicateResult.NOT_SUPPORTED; } @Override diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyOperator.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyOperator.java index a6ace322b..0b5bd662b 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyOperator.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyOperator.java @@ -30,8 +30,42 @@ public interface MessageBodyOperator { * * @param type the requested type * @param context the context providing the headers abstraction - * @return {@code true} if the operator can convert the specified type in - * the given context, {@code false} otherwise + * @return {@link PredicateResult} result */ - boolean accept(GenericType type, T context); + PredicateResult accept(GenericType type, T context); + + /** + * Status whether requested class type is supported by the operator. + */ + enum PredicateResult { + + /** + * Requested type not supported. + */ + NOT_SUPPORTED, + + /** + * Requested type is compatible with this operator, but it is not exact match. + */ + COMPATIBLE, + + /** + * Requested type is supported by that specific operator. + */ + SUPPORTED; + + /** + * Whether handled class is supported. + * Method {@link Class#isAssignableFrom(Class)} is invoked to verify if class under expected parameter is + * supported by by the class under actual parameter. + * + * @param expected expected type + * @param actual actual type + * @return if supported or not + */ + public static PredicateResult supports(Class expected, GenericType actual) { + return expected.isAssignableFrom(actual.rawType()) ? SUPPORTED : NOT_SUPPORTED; + } + + } } diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyOperators.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyOperators.java index 94ba55643..0f71e6472 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyOperators.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyOperators.java @@ -103,11 +103,19 @@ final class MessageBodyOperators> implements It Objects.requireNonNull(context, "context is null!"); try { lock.readLock().lock(); + T assignableOperator = null; + for (T operator : operators) { - if (((U) operator).accept(type, context)) { + MessageBodyOperator.PredicateResult accept = ((U) operator).accept(type, context); + if (accept == MessageBodyOperator.PredicateResult.COMPATIBLE && assignableOperator == null) { + assignableOperator = operator; + } else if (accept == MessageBodyOperator.PredicateResult.SUPPORTED) { return operator; } } + if (assignableOperator != null) { + return assignableOperator; + } } finally { lock.readLock().unlock(); } diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyReaderContext.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyReaderContext.java index 6f65cda7d..3343ff7e1 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyReaderContext.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyReaderContext.java @@ -400,11 +400,13 @@ public final class MessageBodyReaderContext extends MessageBodyContext implement } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { if (predicate != null) { - return predicate.test(type.rawType()); + return predicate.test(type.rawType()) + ? PredicateResult.SUPPORTED + : PredicateResult.NOT_SUPPORTED; } - return clazz.isAssignableFrom(type.rawType()); + return PredicateResult.supports(clazz, type); } } diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java index cd2449cf6..e1d302bbb 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java @@ -569,22 +569,22 @@ public final class MessageBodyWriterContext extends MessageBodyContext implement @Override @SuppressWarnings("unchecked") - public boolean accept(GenericType type, MessageBodyWriterContext context) { + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { if (this.type != null) { if (!this.type.isAssignableFrom(type.rawType())) { - return false; + return PredicateResult.NOT_SUPPORTED; } } else { if (!predicate.test((Object) type.rawType())) { - return false; + return PredicateResult.NOT_SUPPORTED; } } MediaType ct = context.contentType().orElse(null); if (!(contentType != null && ct != null && !ct.test(contentType))) { context.contentType(contentType); - return true; + return PredicateResult.SUPPORTED; } - return false; + return PredicateResult.NOT_SUPPORTED; } @Override diff --git a/media/common/src/main/java/io/helidon/media/common/PathBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/PathBodyWriter.java index 991f8e2a5..a7f7708e9 100644 --- a/media/common/src/main/java/io/helidon/media/common/PathBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/PathBodyWriter.java @@ -37,14 +37,14 @@ import static io.helidon.media.common.ByteChannelBodyWriter.DEFAULT_RETRY_SCHEMA public final class PathBodyWriter implements MessageBodyWriter { /** - * Enforces the use of {@link #get()}. + * Enforces the use of {@link #create()}. */ private PathBodyWriter() { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { - return Path.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(Path.class, type); } @Override diff --git a/media/common/src/main/java/io/helidon/media/common/StringBodyReader.java b/media/common/src/main/java/io/helidon/media/common/StringBodyReader.java index d62dd8e32..f89725429 100644 --- a/media/common/src/main/java/io/helidon/media/common/StringBodyReader.java +++ b/media/common/src/main/java/io/helidon/media/common/StringBodyReader.java @@ -27,14 +27,14 @@ import io.helidon.common.reactive.Single; public final class StringBodyReader implements MessageBodyReader { /** - * Private to enforce the use of {@link #get()}. + * Private to enforce the use of {@link #create()}. */ private StringBodyReader() { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { - return String.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + return PredicateResult.supports(String.class, type); } @Override diff --git a/media/common/src/main/java/io/helidon/media/common/ThrowableBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/ThrowableBodyWriter.java index b6e380d6c..0ee6ae665 100644 --- a/media/common/src/main/java/io/helidon/media/common/ThrowableBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/ThrowableBodyWriter.java @@ -40,8 +40,8 @@ public class ThrowableBodyWriter implements MessageBodyWriter { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { - return Throwable.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(Throwable.class, type); } @Override diff --git a/media/common/src/main/java/io/helidon/media/common/spi/MediaSupportProvider.java b/media/common/src/main/java/io/helidon/media/common/spi/MediaSupportProvider.java index 457939f25..db398ba0d 100644 --- a/media/common/src/main/java/io/helidon/media/common/spi/MediaSupportProvider.java +++ b/media/common/src/main/java/io/helidon/media/common/spi/MediaSupportProvider.java @@ -28,12 +28,14 @@ public interface MediaSupportProvider { * * @return name of the configuration node of this service */ - String type(); + default String configKey() { + return "unconfigured"; + } /** * Create a new service instance based on configuration. * - * @param config configuration of this service + * @param config configuration of this service, never null * @return a new media service instance */ MediaSupport create(Config config); diff --git a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyReader.java b/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyReader.java index 2cbd3a7f3..be54b54a5 100644 --- a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyReader.java +++ b/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyReader.java @@ -46,10 +46,12 @@ public final class JacksonBodyReader implements MessageBodyReader { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { Class clazz = type.rawType(); return !CharSequence.class.isAssignableFrom(clazz) - && objectMapper.canDeserialize(objectMapper.constructType(clazz)); + && objectMapper.canDeserialize(objectMapper.constructType(clazz)) + ? PredicateResult.COMPATIBLE + : PredicateResult.NOT_SUPPORTED; } @Override diff --git a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyWriter.java b/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyWriter.java index aa8d04ccc..1f496a950 100644 --- a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyWriter.java +++ b/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonBodyWriter.java @@ -45,9 +45,11 @@ public final class JacksonBodyWriter implements MessageBodyWriter { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { return !CharSequence.class.isAssignableFrom(type.rawType()) - && objectMapper.canSerialize(type.rawType()); + && objectMapper.canSerialize(type.rawType()) + ? PredicateResult.COMPATIBLE + : PredicateResult.NOT_SUPPORTED; } @Override diff --git a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonProvider.java b/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonProvider.java index 8eff3ee9f..60dd15e01 100644 --- a/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonProvider.java +++ b/media/jackson/common/src/main/java/io/helidon/media/jackson/common/JacksonProvider.java @@ -16,10 +16,22 @@ package io.helidon.media.jackson.common; +import java.util.stream.Stream; + import io.helidon.config.Config; import io.helidon.media.common.MediaSupport; import io.helidon.media.common.spi.MediaSupportProvider; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; + /** * Jackson support SPI provider. */ @@ -29,11 +41,39 @@ public class JacksonProvider implements MediaSupportProvider { @Override public MediaSupport create(Config config) { - return JacksonSupport.create(); + ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new ParameterNamesModule()) + .registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()); + configureJackson(objectMapper, config); + return JacksonSupport.create(objectMapper); + } + + private void configureJackson(ObjectMapper objectMapper, Config config) { + Stream.of(DeserializationFeature.values()) + .forEach(df -> config.get(configName(df.name())).asBoolean() + .ifPresent(val -> objectMapper.configure(df, val))); + Stream.of(SerializationFeature.values()) + .forEach(sf -> config.get(configName(sf.name())).asBoolean() + .ifPresent(val -> objectMapper.configure(sf, val))); + Stream.of(JsonParser.Feature.values()) + .forEach(jp -> config.get(configName(jp.name())).asBoolean() + .ifPresent(val -> objectMapper.configure(jp, val))); + Stream.of(MapperFeature.values()) + .forEach(mf -> config.get(configName(mf.name())).asBoolean() + .ifPresent(val -> objectMapper.configure(mf, val))); + Stream.of(JsonGenerator.Feature.values()) + .forEach(jgf -> config.get(configName(jgf.name())).asBoolean() + .ifPresent(val -> objectMapper.configure(jgf, val))); + } + + private String configName(String enumName) { + return enumName.toLowerCase() + .replace('_', '-'); } @Override - public String type() { + public String configKey() { return JACKSON; } } diff --git a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyReader.java b/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyReader.java index 018abaaf8..bced2e2f8 100644 --- a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyReader.java +++ b/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyReader.java @@ -45,8 +45,10 @@ public class JsonbBodyReader implements MessageBodyReader { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { - return !CharSequence.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + return !CharSequence.class.isAssignableFrom(type.rawType()) + ? PredicateResult.COMPATIBLE + : PredicateResult.NOT_SUPPORTED; } @Override diff --git a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyWriter.java b/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyWriter.java index 6b508b8ec..d3988ed95 100644 --- a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyWriter.java +++ b/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbBodyWriter.java @@ -45,10 +45,12 @@ public class JsonbBodyWriter implements MessageBodyWriter { } @Override - public boolean accept(GenericType type, - MessageBodyWriterContext context) { + public PredicateResult accept(GenericType type, + MessageBodyWriterContext context) { - return !CharSequence.class.isAssignableFrom(type.rawType()); + return !CharSequence.class.isAssignableFrom(type.rawType()) + ? PredicateResult.COMPATIBLE + : PredicateResult.NOT_SUPPORTED; } @Override diff --git a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbProvider.java b/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbProvider.java index 35f769974..bcff5cd17 100644 --- a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbProvider.java +++ b/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbProvider.java @@ -16,6 +16,10 @@ package io.helidon.media.jsonb.common; +import javax.json.bind.Jsonb; +import javax.json.bind.JsonbBuilder; +import javax.json.bind.JsonbConfig; + import io.helidon.config.Config; import io.helidon.media.common.MediaSupport; import io.helidon.media.common.spi.MediaSupportProvider; @@ -29,11 +33,14 @@ public class JsonbProvider implements MediaSupportProvider { @Override public MediaSupport create(Config config) { - return JsonbSupport.create(); + JsonbConfig jsonbConfig = new JsonbConfig(); + config.asMap().ifPresent(map -> map.forEach(jsonbConfig::setProperty)); + Jsonb jsonb = JsonbBuilder.create(jsonbConfig); + return JsonbSupport.create(jsonb); } @Override - public String type() { + public String configKey() { return JSON_B; } } diff --git a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbSupport.java b/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbSupport.java index dbedbdd60..04f8dd322 100644 --- a/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbSupport.java +++ b/media/jsonb/common/src/main/java/io/helidon/media/jsonb/common/JsonbSupport.java @@ -38,7 +38,7 @@ import io.helidon.media.common.MessageBodyWriter; public final class JsonbSupport implements MediaSupport { static { - HelidonFeatures.register(HelidonFlavor.SE, "WebServer", "JSON-B"); + HelidonFeatures.register(HelidonFlavor.SE, "Media", "JSON-B"); } private static final Jsonb JSON_B = JsonbBuilder.create(); diff --git a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyReader.java b/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyReader.java index 23050b376..7fba94ecc 100644 --- a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyReader.java +++ b/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyReader.java @@ -47,8 +47,8 @@ public final class JsonpBodyReader implements MessageBodyReader { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext context) { - return JsonStructure.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + return PredicateResult.supports(JsonStructure.class, type); } @Override diff --git a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyStreamWriter.java b/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyStreamWriter.java index cba215e48..44e2b223d 100644 --- a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyStreamWriter.java +++ b/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyStreamWriter.java @@ -46,8 +46,8 @@ public class JsonpBodyStreamWriter implements MessageBodyStreamWriter type, MessageBodyWriterContext context) { - return JsonStructure.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(JsonStructure.class, type); } @Override diff --git a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyWriter.java b/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyWriter.java index 633cd73e2..fba47067e 100644 --- a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyWriter.java +++ b/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpBodyWriter.java @@ -43,8 +43,8 @@ public class JsonpBodyWriter implements MessageBodyWriter { } @Override - public boolean accept(GenericType type, MessageBodyWriterContext context) { - return JsonStructure.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(JsonStructure.class, type); } @Override diff --git a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpProvider.java b/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpProvider.java index b827c7b68..e1ae1e671 100644 --- a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpProvider.java +++ b/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpProvider.java @@ -29,11 +29,11 @@ public class JsonpProvider implements MediaSupportProvider { @Override public MediaSupport create(Config config) { - return JsonpSupport.create(); + return JsonpSupport.create(config.asMap().get()); } @Override - public String type() { + public String configKey() { return JSON_P; } } diff --git a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpSupport.java b/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpSupport.java index 7f3c23a45..04377e976 100644 --- a/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpSupport.java +++ b/media/jsonp/common/src/main/java/io/helidon/media/jsonp/common/JsonpSupport.java @@ -24,6 +24,8 @@ import javax.json.Json; import javax.json.JsonReaderFactory; import javax.json.JsonWriterFactory; +import io.helidon.common.HelidonFeatures; +import io.helidon.common.HelidonFlavor; import io.helidon.media.common.MediaSupport; import io.helidon.media.common.MessageBodyReader; import io.helidon.media.common.MessageBodyStreamWriter; @@ -36,6 +38,10 @@ import io.helidon.media.common.MessageBodyWriter; */ public final class JsonpSupport implements MediaSupport { + static { + HelidonFeatures.register(HelidonFlavor.SE, "Media", "JSON-P"); + } + private final JsonReaderFactory jsonReaderFactory; private final JsonWriterFactory jsonWriterFactory; diff --git a/media/jsonp/common/src/test/java/io/helidon/media/jsonp/common/JsonpStreamWriterTest.java b/media/jsonp/common/src/test/java/io/helidon/media/jsonp/common/JsonpStreamWriterTest.java index 60c04dc50..f7d76cbac 100644 --- a/media/jsonp/common/src/test/java/io/helidon/media/jsonp/common/JsonpStreamWriterTest.java +++ b/media/jsonp/common/src/test/java/io/helidon/media/jsonp/common/JsonpStreamWriterTest.java @@ -33,6 +33,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; import io.helidon.common.http.HashParameters; import io.helidon.common.reactive.Multi; +import io.helidon.media.common.MessageBodyOperator; import io.helidon.media.common.MessageBodyWriterContext; import org.junit.jupiter.api.Test; @@ -58,9 +59,15 @@ public class JsonpStreamWriterTest { @Test void testAcceptedTypes() { assertAll( - () -> assertThat("JsonObject accepted", WRITER.accept(JSON_OBJECT, CONTEXT), is(true)), - () -> assertThat("JsonArray accepted", WRITER.accept(JSON_ARRAY, CONTEXT), is(true)), - () -> assertThat("Pojo not accepted", WRITER.accept(MY_TYPE, CONTEXT), is(false)) + () -> assertThat("JsonObject accepted", + WRITER.accept(JSON_OBJECT, CONTEXT), + is(MessageBodyOperator.PredicateResult.SUPPORTED)), + () -> assertThat("JsonArray accepted", + WRITER.accept(JSON_ARRAY, CONTEXT), + is(MessageBodyOperator.PredicateResult.SUPPORTED)), + () -> assertThat("Pojo not accepted", + WRITER.accept(MY_TYPE, CONTEXT), + is(MessageBodyOperator.PredicateResult.NOT_SUPPORTED)) ); } diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java index 386f7ab3b..077eefe4a 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java @@ -32,8 +32,8 @@ public final class BodyPartBodyStreamReader implements MessageBodyStreamReader type, MessageBodyReaderContext context) { - return BodyPart.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + return PredicateResult.supports(BodyPart.class, type); } @Override diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java index dfe778e39..006c59068 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java @@ -35,8 +35,8 @@ public final class BodyPartBodyStreamWriter implements MessageBodyStreamWriter type, MessageBodyWriterContext context) { - return WriteableBodyPart.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(WriteableBodyPart.class, type); } @Override diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyReader.java b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyReader.java index 48ee9c6ec..0db441333 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyReader.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyReader.java @@ -41,8 +41,8 @@ public final class MultiPartBodyReader implements MessageBodyReader { } @Override - public boolean accept(GenericType type, MessageBodyReaderContext ctx) { - return MultiPart.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyReaderContext ctx) { + return PredicateResult.supports(MultiPart.class, type); } @Override @@ -106,7 +106,7 @@ public final class MultiPartBodyReader implements MessageBodyReader { } /** - * Implementation of {@link MultiMapper} that converts {@code byte[]} to a + * Implementation of {@link Mapper} that converts {@code byte[]} to a * publisher of {@link DataChunk} by copying the bytes. */ private static final class BytesToChunks implements Mapper> { diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java index e5e57b915..223218c74 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java @@ -43,8 +43,8 @@ public final class MultiPartBodyWriter implements MessageBodyWriter type, MessageBodyWriterContext context) { - return WriteableMultiPart.class.isAssignableFrom(type.rawType()); + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + return PredicateResult.supports(WriteableMultiPart.class, type); } @Override diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientConfiguration.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientConfiguration.java index 359a70f11..74f8388a8 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientConfiguration.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientConfiguration.java @@ -611,6 +611,7 @@ class WebClientConfiguration { .as(Proxy.builder()::config) .map(Proxy.Builder::build) .ifPresent(this::proxy); + config.get("media-support").as(MediaContext::create).ifPresent(this::mediaContext); return me; }