mirror of
https://github.com/jlengrand/helidon.git
synced 2026-03-10 08:21:17 +00:00
Service loader added to MediaContext (#1861)
Media support now loadable via service loader Signed-off-by: David Kral <david.k.kral@oracle.com>
This commit is contained in:
@@ -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<? extends ConfigSource>... 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
|
||||
|
||||
@@ -27,6 +27,8 @@ import io.helidon.media.common.MessageBodyReaderContext;
|
||||
|
||||
public class NameReader implements MessageBodyReader<Name> {
|
||||
|
||||
private static final MediaType TYPE = MediaType.parse("application/name");
|
||||
|
||||
private NameReader() {
|
||||
}
|
||||
|
||||
@@ -41,11 +43,11 @@ public class NameReader implements MessageBodyReader<Name> {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,8 @@ public final class ByteChannelBodyWriter implements MessageBodyWriter<ReadableBy
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(GenericType<?> type, MessageBodyWriterContext context) {
|
||||
return ReadableByteChannel.class.isAssignableFrom(type.rawType());
|
||||
public PredicateResult accept(GenericType<?> type, MessageBodyWriterContext context) {
|
||||
return PredicateResult.supports(ReadableByteChannel.class, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,14 +30,14 @@ import io.helidon.common.reactive.Single;
|
||||
public final class CharSequenceBodyWriter implements MessageBodyWriter<CharSequence> {
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -38,14 +38,14 @@ import static io.helidon.media.common.ByteChannelBodyWriter.DEFAULT_RETRY_SCHEMA
|
||||
public final class FileBodyWriter implements MessageBodyWriter<File> {
|
||||
|
||||
/**
|
||||
* 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<File> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<File, Publisher<DataChunk>> {
|
||||
|
||||
@@ -34,8 +34,8 @@ public class InputStreamBodyReader implements MessageBodyReader<InputStream> {
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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<MediaContext>,
|
||||
MediaContextBuilder<Builder> {
|
||||
|
||||
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<MediaSupportProvider> services = HelidonServiceLoader
|
||||
.builder(ServiceLoader.load(MediaSupportProvider.class));
|
||||
|
||||
private final List<MessageBodyReader<?>> builderReaders = new ArrayList<>();
|
||||
private final List<MessageBodyStreamReader<?>> builderStreamReaders = new ArrayList<>();
|
||||
private final List<MessageBodyWriter<?>> builderWriters = new ArrayList<>();
|
||||
private final List<MessageBodyStreamWriter<?>> builderStreamWriter = new ArrayList<>();
|
||||
private final List<MediaSupport> mediaSupports = new ArrayList<>();
|
||||
private final Map<String, Map<String, String>> 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 {
|
||||
* <th>description</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>server-errors-include-stack-traces</td>
|
||||
* <td>Whether stack traces should be included in the response (server only)</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>register-defaults</td>
|
||||
* <td>Whether to register default reader and writers</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>discover-services</td>
|
||||
* <td>Whether to discover services via service loader</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>filter-services</td>
|
||||
* <td>Whether to filter discovered services by service names in services section</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>services</td>
|
||||
* <td>Configuration section for each service. Each entry has to have "name" parameter.
|
||||
* It is also used for filtering of loaded services.</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* @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<String, String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<MessageBodyContext>, 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
|
||||
|
||||
@@ -30,8 +30,42 @@ public interface MessageBodyOperator<T extends MessageBodyContext> {
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,11 +103,19 @@ final class MessageBodyOperators<T extends MessageBodyOperator<?>> 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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,14 +37,14 @@ import static io.helidon.media.common.ByteChannelBodyWriter.DEFAULT_RETRY_SCHEMA
|
||||
public final class PathBodyWriter implements MessageBodyWriter<Path> {
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -27,14 +27,14 @@ import io.helidon.common.reactive.Single;
|
||||
public final class StringBodyReader implements MessageBodyReader<String> {
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -40,8 +40,8 @@ public class ThrowableBodyWriter implements MessageBodyWriter<Throwable> {
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -46,10 +46,12 @@ public final class JacksonBodyReader implements MessageBodyReader<Object> {
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -45,9 +45,11 @@ public final class JacksonBodyWriter implements MessageBodyWriter<Object> {
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,10 @@ public class JsonbBodyReader implements MessageBodyReader<Object> {
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -45,10 +45,12 @@ public class JsonbBodyWriter implements MessageBodyWriter<Object> {
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -47,8 +47,8 @@ public final class JsonpBodyReader implements MessageBodyReader<JsonStructure> {
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -46,8 +46,8 @@ public class JsonpBodyStreamWriter implements MessageBodyStreamWriter<JsonStruct
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -43,8 +43,8 @@ public class JsonpBodyWriter implements MessageBodyWriter<JsonStructure> {
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ public final class BodyPartBodyStreamReader implements MessageBodyStreamReader<R
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(GenericType<?> type, MessageBodyReaderContext context) {
|
||||
return BodyPart.class.isAssignableFrom(type.rawType());
|
||||
public PredicateResult accept(GenericType<?> type, MessageBodyReaderContext context) {
|
||||
return PredicateResult.supports(BodyPart.class, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,8 +35,8 @@ public final class BodyPartBodyStreamWriter implements MessageBodyStreamWriter<W
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(GenericType<?> type, MessageBodyWriterContext context) {
|
||||
return WriteableBodyPart.class.isAssignableFrom(type.rawType());
|
||||
public PredicateResult accept(GenericType<?> type, MessageBodyWriterContext context) {
|
||||
return PredicateResult.supports(WriteableBodyPart.class, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -41,8 +41,8 @@ public final class MultiPartBodyReader implements MessageBodyReader<MultiPart> {
|
||||
}
|
||||
|
||||
@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<MultiPart> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<byte[], Publisher<DataChunk>> {
|
||||
|
||||
@@ -43,8 +43,8 @@ public final class MultiPartBodyWriter implements MessageBodyWriter<WriteableMul
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(GenericType<?> type, MessageBodyWriterContext context) {
|
||||
return WriteableMultiPart.class.isAssignableFrom(type.rawType());
|
||||
public PredicateResult accept(GenericType<?> type, MessageBodyWriterContext context) {
|
||||
return PredicateResult.supports(WriteableMultiPart.class, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user