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:
David Král
2020-05-27 12:54:09 +02:00
committed by GitHub
parent 0353776d0e
commit 4e55096c71
34 changed files with 368 additions and 108 deletions

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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>> {

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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))
);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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>> {

View File

@@ -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

View File

@@ -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;
}