diff --git a/aop/src/main/java/io/micronaut/aop/InterceptedMethod.java b/aop/src/main/java/io/micronaut/aop/InterceptedMethod.java index 5c4694f5652..877b4fa9702 100644 --- a/aop/src/main/java/io/micronaut/aop/InterceptedMethod.java +++ b/aop/src/main/java/io/micronaut/aop/InterceptedMethod.java @@ -18,6 +18,7 @@ import io.micronaut.aop.internal.intercepted.InterceptedMethodUtil; import io.micronaut.context.exceptions.ConfigurationException; import io.micronaut.core.annotation.Experimental; +import io.micronaut.core.convert.ConversionService; import io.micronaut.core.type.Argument; import org.reactivestreams.Publisher; @@ -40,7 +41,18 @@ public interface InterceptedMethod { * @return The {@link InterceptedMethod} */ static InterceptedMethod of(MethodInvocationContext context) { - return InterceptedMethodUtil.of(context); + return of(context, ConversionService.SHARED); + } + + /** + * Creates a new instance of intercept method supporting intercepting different reactive invocations. + * + * @param context The {@link MethodInvocationContext} + * @param conversionService The conversion service + * @return The {@link InterceptedMethod} + */ + static InterceptedMethod of(MethodInvocationContext context, ConversionService conversionService) { + return InterceptedMethodUtil.of(context, conversionService); } /** diff --git a/aop/src/main/java/io/micronaut/aop/internal/intercepted/CompletionStageInterceptedMethod.java b/aop/src/main/java/io/micronaut/aop/internal/intercepted/CompletionStageInterceptedMethod.java index 5a2ff647722..3c89ff79f0c 100644 --- a/aop/src/main/java/io/micronaut/aop/internal/intercepted/CompletionStageInterceptedMethod.java +++ b/aop/src/main/java/io/micronaut/aop/internal/intercepted/CompletionStageInterceptedMethod.java @@ -37,13 +37,13 @@ @Internal @Experimental class CompletionStageInterceptedMethod implements InterceptedMethod { - private final ConversionService conversionService = ConversionService.SHARED; - private final MethodInvocationContext context; + private final ConversionService conversionService; private final Argument returnTypeValue; - CompletionStageInterceptedMethod(MethodInvocationContext context) { + CompletionStageInterceptedMethod(MethodInvocationContext context, ConversionService conversionService) { this.context = context; + this.conversionService = conversionService; this.returnTypeValue = context.getReturnType().asArgument().getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT); } diff --git a/aop/src/main/java/io/micronaut/aop/internal/intercepted/InterceptedMethodUtil.java b/aop/src/main/java/io/micronaut/aop/internal/intercepted/InterceptedMethodUtil.java index 7b8c6e42d49..43f6b224baf 100644 --- a/aop/src/main/java/io/micronaut/aop/internal/intercepted/InterceptedMethodUtil.java +++ b/aop/src/main/java/io/micronaut/aop/internal/intercepted/InterceptedMethodUtil.java @@ -26,6 +26,7 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.convert.ConversionService; import io.micronaut.core.type.ReturnType; import java.util.List; @@ -48,10 +49,13 @@ private InterceptedMethodUtil() { /** * Find possible {@link InterceptedMethod} implementation. * - * @param context The {@link MethodInvocationContext} + * @param context The {@link MethodInvocationContext} + * @param conversionService The {@link ConversionService} * @return The {@link InterceptedMethod} + * @since 4.0.0 */ - public static InterceptedMethod of(MethodInvocationContext context) { + @NonNull + public static InterceptedMethod of(@NonNull MethodInvocationContext context, @NonNull ConversionService conversionService) { if (context.isSuspend()) { KotlinInterceptedMethod kotlinInterceptedMethod = KotlinInterceptedMethod.of(context); if (kotlinInterceptedMethod != null) { @@ -65,9 +69,9 @@ public static InterceptedMethod of(MethodInvocationContext context) { // Micro Optimization return new SynchronousInterceptedMethod(context); } else if (CompletionStage.class.isAssignableFrom(returnTypeClass) || Future.class.isAssignableFrom(returnTypeClass)) { - return new CompletionStageInterceptedMethod(context); + return new CompletionStageInterceptedMethod(context, conversionService); } else if (PublisherInterceptedMethod.isConvertibleToPublisher(returnTypeClass)) { - return new PublisherInterceptedMethod(context); + return new PublisherInterceptedMethod(context, conversionService); } else { return new SynchronousInterceptedMethod(context); } diff --git a/aop/src/main/java/io/micronaut/aop/internal/intercepted/PublisherInterceptedMethod.java b/aop/src/main/java/io/micronaut/aop/internal/intercepted/PublisherInterceptedMethod.java index f2629bf11f1..828580781f3 100644 --- a/aop/src/main/java/io/micronaut/aop/internal/intercepted/PublisherInterceptedMethod.java +++ b/aop/src/main/java/io/micronaut/aop/internal/intercepted/PublisherInterceptedMethod.java @@ -40,13 +40,13 @@ @Experimental class PublisherInterceptedMethod implements InterceptedMethod { private static final boolean AVAILABLE = ClassUtils.isPresent("io.micronaut.core.async.publisher.Publishers", PublisherInterceptedMethod.class.getClassLoader()); - private final ConversionService conversionService = ConversionService.SHARED; - private final MethodInvocationContext context; + private final ConversionService conversionService; private final Argument returnTypeValue; - PublisherInterceptedMethod(MethodInvocationContext context) { + PublisherInterceptedMethod(MethodInvocationContext context, ConversionService conversionService) { this.context = context; + this.conversionService = conversionService; this.returnTypeValue = context.getReturnType().asArgument().getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT); } diff --git a/buffer-netty/src/main/java/io/micronaut/buffer/netty/NettyByteBufferFactory.java b/buffer-netty/src/main/java/io/micronaut/buffer/netty/NettyByteBufferFactory.java index a4fc25a6624..388a01b3247 100644 --- a/buffer-netty/src/main/java/io/micronaut/buffer/netty/NettyByteBufferFactory.java +++ b/buffer-netty/src/main/java/io/micronaut/buffer/netty/NettyByteBufferFactory.java @@ -17,7 +17,7 @@ import io.micronaut.context.annotation.BootstrapContextCompatible; import io.micronaut.core.annotation.Internal; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.io.buffer.ByteBuffer; import io.micronaut.core.io.buffer.ByteBufferFactory; import io.netty.buffer.ByteBuf; @@ -59,7 +59,7 @@ public NettyByteBufferFactory(ByteBufAllocator allocator) { } @PostConstruct - final void register(ConversionService conversionService) { + final void register(MutableConversionService conversionService) { conversionService.addConverter(ByteBuf.class, ByteBuffer.class, DEFAULT::wrap); conversionService.addConverter(ByteBuffer.class, ByteBuf.class, byteBuffer -> { if (byteBuffer instanceof NettyByteBuffer) { diff --git a/context/src/main/java/io/micronaut/runtime/converters/time/TimeConverterRegistrar.java b/context/src/main/java/io/micronaut/runtime/converters/time/TimeConverterRegistrar.java index 3fcb8c5e28a..58bd8b6fa69 100644 --- a/context/src/main/java/io/micronaut/runtime/converters/time/TimeConverterRegistrar.java +++ b/context/src/main/java/io/micronaut/runtime/converters/time/TimeConverterRegistrar.java @@ -18,7 +18,7 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.TypeHint; import io.micronaut.core.convert.ConversionContext; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverter; import io.micronaut.core.convert.TypeConverterRegistrar; import io.micronaut.core.convert.format.Format; @@ -81,7 +81,7 @@ public class TimeConverterRegistrar implements TypeConverterRegistrar { private static final int MILLIS = 3; @Override - public void register(ConversionService conversionService) { + public void register(MutableConversionService conversionService) { final BiFunction> durationConverter = (object, context) -> { String value = object.toString().trim(); if (value.startsWith("P")) { @@ -179,7 +179,7 @@ public void register(ConversionService conversionService) { addTemporalToDateConverter(conversionService, LocalDateTime.class, ldt -> ldt.toInstant(ZoneOffset.UTC)); } - private void addTemporalStringConverter(ConversionService conversionService, Class temporalType, DateTimeFormatter isoFormatter, TemporalQuery query) { + private void addTemporalStringConverter(MutableConversionService conversionService, Class temporalType, DateTimeFormatter isoFormatter, TemporalQuery query) { conversionService.addConverter(CharSequence.class, temporalType, (CharSequence object, Class targetType, ConversionContext context) -> { if (StringUtils.isEmpty(object)) { return Optional.empty(); @@ -213,7 +213,7 @@ private void addTemporalStringConverter(ConversionS }); } - private void addTemporalToDateConverter(ConversionService conversionService, Class temporalType, Function toInstant) { + private void addTemporalToDateConverter(MutableConversionService conversionService, Class temporalType, Function toInstant) { conversionService.addConverter(temporalType, Date.class, (T object, Class targetType, ConversionContext context) -> Optional.of(Date.from(toInstant.apply(object)))); } diff --git a/context/src/main/java/io/micronaut/scheduling/processor/ScheduledMethodProcessor.java b/context/src/main/java/io/micronaut/scheduling/processor/ScheduledMethodProcessor.java index 3e2b8288bc7..63d7beb84b5 100644 --- a/context/src/main/java/io/micronaut/scheduling/processor/ScheduledMethodProcessor.java +++ b/context/src/main/java/io/micronaut/scheduling/processor/ScheduledMethodProcessor.java @@ -66,7 +66,7 @@ public class ScheduledMethodProcessor implements ExecutableMethodProcessor conversionService; + private final ConversionService conversionService; private final Queue> scheduledTasks = new ConcurrentLinkedDeque<>(); private final TaskExceptionHandler taskExceptionHandler; @@ -76,7 +76,7 @@ public class ScheduledMethodProcessor implements ExecutableMethodProcessor> conversionService, TaskExceptionHandler taskExceptionHandler) { + public ScheduledMethodProcessor(BeanContext beanContext, Optional conversionService, TaskExceptionHandler taskExceptionHandler) { this.beanContext = beanContext; this.conversionService = conversionService.orElse(ConversionService.SHARED); this.taskExceptionHandler = taskExceptionHandler; diff --git a/context/src/main/java/io/micronaut/runtime/converters/reactive/ReactiveTypeConverterRegistrar.java b/core-reactive/src/main/java/io/micronaut/core/async/converters/ReactiveTypeConverterRegistrar.java similarity index 80% rename from context/src/main/java/io/micronaut/runtime/converters/reactive/ReactiveTypeConverterRegistrar.java rename to core-reactive/src/main/java/io/micronaut/core/async/converters/ReactiveTypeConverterRegistrar.java index 2c2e4938080..db161604c6e 100644 --- a/context/src/main/java/io/micronaut/runtime/converters/reactive/ReactiveTypeConverterRegistrar.java +++ b/core-reactive/src/main/java/io/micronaut/core/async/converters/ReactiveTypeConverterRegistrar.java @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.micronaut.runtime.converters.reactive; +package io.micronaut.core.async.converters; -import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.Internal; import io.micronaut.core.async.publisher.Publishers; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverterRegistrar; import org.reactivestreams.Publisher; -import jakarta.inject.Singleton; /** * Registers converters for Reactive types such as {@link Publisher}. @@ -28,12 +27,11 @@ * @author Sergio del Amo * @since 3.0.0 */ -@Singleton -@Requires(classes = Publishers.class) +@Internal public class ReactiveTypeConverterRegistrar implements TypeConverterRegistrar { @Override - public void register(ConversionService conversionService) { + public void register(MutableConversionService conversionService) { conversionService.addConverter(Object.class, Publisher.class, obj -> { if (obj instanceof Publisher) { return (Publisher) obj; diff --git a/core-reactive/src/main/resources/META-INF/services/io.micronaut.core.convert.TypeConverterRegistrar b/core-reactive/src/main/resources/META-INF/services/io.micronaut.core.convert.TypeConverterRegistrar new file mode 100644 index 00000000000..219feaa6e6a --- /dev/null +++ b/core-reactive/src/main/resources/META-INF/services/io.micronaut.core.convert.TypeConverterRegistrar @@ -0,0 +1 @@ +io.micronaut.core.async.converters.ReactiveTypeConverterRegistrar diff --git a/core/src/main/java/io/micronaut/core/bind/annotation/AbstractAnnotatedArgumentBinder.java b/core/src/main/java/io/micronaut/core/bind/annotation/AbstractAnnotatedArgumentBinder.java index 0d1afa1ef04..f8e2eefe96f 100644 --- a/core/src/main/java/io/micronaut/core/bind/annotation/AbstractAnnotatedArgumentBinder.java +++ b/core/src/main/java/io/micronaut/core/bind/annotation/AbstractAnnotatedArgumentBinder.java @@ -39,14 +39,14 @@ public abstract class AbstractAnnotatedArgumentBinder implements AnnotatedArgumentBinder { private static final String DEFAULT_VALUE_MEMBER = "defaultValue"; - private final ConversionService conversionService; + protected final ConversionService conversionService; /** * Constructor. * * @param conversionService conversionService */ - protected AbstractAnnotatedArgumentBinder(ConversionService conversionService) { + protected AbstractAnnotatedArgumentBinder(ConversionService conversionService) { this.conversionService = conversionService; } diff --git a/core/src/main/java/io/micronaut/core/convert/ConversionService.java b/core/src/main/java/io/micronaut/core/convert/ConversionService.java index e99afba7288..7178fcbcf86 100644 --- a/core/src/main/java/io/micronaut/core/convert/ConversionService.java +++ b/core/src/main/java/io/micronaut/core/convert/ConversionService.java @@ -15,49 +15,24 @@ */ package io.micronaut.core.convert; +import io.micronaut.core.annotation.Nullable; import io.micronaut.core.convert.exceptions.ConversionErrorException; import io.micronaut.core.type.Argument; -import io.micronaut.core.annotation.Nullable; + import java.util.Optional; -import java.util.function.Function; /** * A service for allowing conversion from one type to another. * - * @param The type * @author Graeme Rocher * @since 1.0 */ -public interface ConversionService { +public interface ConversionService { /** * The default shared conversion service. */ - ConversionService SHARED = new DefaultConversionService(); - - /** - * Adds a type converter. - * - * @param sourceType The source type - * @param targetType The target type - * @param typeConverter The type converter - * @param The source generic type - * @param The target generic type - * @return This conversion service - */ - Impl addConverter(Class sourceType, Class targetType, Function typeConverter); - - /** - * Adds a type converter. - * - * @param sourceType The source type - * @param targetType The target type - * @param typeConverter The type converter - * @param The source generic type - * @param The target generic type - * @return This conversion service - */ - Impl addConverter(Class sourceType, Class targetType, TypeConverter typeConverter); + ConversionService SHARED = new DefaultMutableConversionService(); /** * Attempts to convert the given object to the given target type. If conversion fails or is not possible an empty {@link Optional} is returned. diff --git a/core/src/main/java/io/micronaut/core/convert/ConversionServiceAware.java b/core/src/main/java/io/micronaut/core/convert/ConversionServiceAware.java new file mode 100644 index 00000000000..5385ed0b352 --- /dev/null +++ b/core/src/main/java/io/micronaut/core/convert/ConversionServiceAware.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017-2022 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.core.convert; + +import io.micronaut.core.annotation.NonNull; + +/** + * Interface used when the component requires to set up bean context's {@link ConversionService}. + * + * @author Denis Stepanov + * @since 4.0.0 + */ +public interface ConversionServiceAware { + + /** + * Sets the conversion service. + * + * @param conversionService The conversion service + */ + void setConversionService(@NonNull ConversionService conversionService); + +} diff --git a/context/src/main/java/io/micronaut/runtime/converters/reactive/package-info.java b/core/src/main/java/io/micronaut/core/convert/ConversionServiceProvider.java similarity index 55% rename from context/src/main/java/io/micronaut/runtime/converters/reactive/package-info.java rename to core/src/main/java/io/micronaut/core/convert/ConversionServiceProvider.java index fb87664eafb..1d8b7dc4db6 100644 --- a/context/src/main/java/io/micronaut/runtime/converters/reactive/package-info.java +++ b/core/src/main/java/io/micronaut/core/convert/ConversionServiceProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 original authors + * Copyright 2017-2022 original authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package io.micronaut.core.convert; + +import io.micronaut.core.annotation.NonNull; + /** - * Contains classes for reactive streams conversion. + * Interface for a component to provide the access to its {@link ConversionService}. * - * @author Sergio del Amo - * @since 3.0.0 + * @author Denis Stepanov + * @since 4.0.0 */ -package io.micronaut.runtime.converters.reactive; +public interface ConversionServiceProvider { + + /** + * Provides the conversion service. + * + * @return the conversion service + */ + @NonNull + ConversionService getConversionService(); + +} diff --git a/core/src/main/java/io/micronaut/core/convert/DefaultConversionService.java b/core/src/main/java/io/micronaut/core/convert/DefaultMutableConversionService.java similarity index 95% rename from core/src/main/java/io/micronaut/core/convert/DefaultConversionService.java rename to core/src/main/java/io/micronaut/core/convert/DefaultMutableConversionService.java index fee4987e8a7..b57c7f4b00f 100644 --- a/core/src/main/java/io/micronaut/core/convert/DefaultConversionService.java +++ b/core/src/main/java/io/micronaut/core/convert/DefaultMutableConversionService.java @@ -17,7 +17,6 @@ import io.micronaut.core.annotation.AnnotationClassValue; import io.micronaut.core.annotation.AnnotationMetadata; -import io.micronaut.core.annotation.Internal; import io.micronaut.core.convert.converters.MultiValuesConverterFactory; import io.micronaut.core.convert.exceptions.ConversionErrorException; import io.micronaut.core.convert.format.Format; @@ -69,6 +68,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; import java.util.OptionalLong; @@ -85,7 +85,7 @@ * @author Graeme Rocher * @since 1.0 */ -public class DefaultConversionService implements ConversionService { +public class DefaultMutableConversionService implements MutableConversionService { private static final int CACHE_MAX = 150; private static final TypeConverter UNCONVERTIBLE = (object, targetType, context) -> Optional.empty(); @@ -98,7 +98,7 @@ public class DefaultConversionService implements ConversionService Optional convert(Object object, Class targetType, ConversionCon } targetType = targetType.isPrimitive() ? ReflectionUtils.getWrapperType(targetType) : targetType; - if (targetType.isInstance(object) && !Iterable.class.isInstance(object) && !Map.class.isInstance(object)) { + if (targetType.isInstance(object) && !(object instanceof Iterable) && !(object instanceof Map)) { return Optional.of((T) object); } @@ -123,7 +123,7 @@ public Optional convert(Object object, Class targetType, ConversionCon Optional formattingAnn = annotationMetadata.getAnnotationNameByStereotype(Format.class); String formattingAnnotation = formattingAnn.orElse(null); ConvertiblePair pair = new ConvertiblePair(sourceType, targetType, formattingAnnotation); - TypeConverter typeConverter = converterCache.get(pair); + TypeConverter typeConverter = converterCache.get(pair); if (typeConverter == null) { typeConverter = findTypeConverter(sourceType, targetType, formattingAnnotation); if (typeConverter == null) { @@ -141,7 +141,7 @@ public Optional convert(Object object, Class targetType, ConversionCon } } else { ConvertiblePair pair = new ConvertiblePair(sourceType, targetType, null); - TypeConverter typeConverter = converterCache.get(pair); + TypeConverter typeConverter = converterCache.get(pair); if (typeConverter == null) { typeConverter = findTypeConverter(sourceType, targetType, null); if (typeConverter == null) { @@ -166,7 +166,7 @@ public Optional convert(Object object, Class targetType, ConversionCon @Override public boolean canConvert(Class sourceType, Class targetType) { ConvertiblePair pair = new ConvertiblePair(sourceType, targetType, null); - TypeConverter typeConverter = converterCache.get(pair); + TypeConverter typeConverter = converterCache.get(pair); if (typeConverter == null) { typeConverter = findTypeConverter(sourceType, targetType, null); if (typeConverter != null) { @@ -179,40 +179,25 @@ public boolean canConvert(Class sourceType, Class targetType) { } @Override - public DefaultConversionService addConverter(Class sourceType, Class targetType, TypeConverter typeConverter) { + public void addConverter(Class sourceType, Class targetType, TypeConverter typeConverter) { ConvertiblePair pair = newPair(sourceType, targetType, typeConverter); typeConverters.put(pair, typeConverter); converterCache.put(pair, typeConverter); - return this; } @Override - public DefaultConversionService addConverter(Class sourceType, Class targetType, Function function) { + public void addConverter(Class sourceType, Class targetType, Function function) { ConvertiblePair pair = new ConvertiblePair(sourceType, targetType); TypeConverter typeConverter = TypeConverter.of(sourceType, targetType, function); typeConverters.put(pair, typeConverter); converterCache.put(pair, typeConverter); - return this; - } - - - /** - * Reset internal state. - * - * @since 3.5.3 - */ - @Internal - public void reset() { - typeConverters.clear(); - converterCache.clear(); - registerDefaultConverters(); } /** * Default Converters. */ @SuppressWarnings({"OptionalIsPresent", "unchecked"}) - protected void registerDefaultConverters() { + private void registerDefaultConverters() { // primitive array to wrapper array @SuppressWarnings("rawtypes") Function primitiveArrayToWrapperArray = ArrayUtils::toWrapperArray; @@ -235,7 +220,7 @@ protected void registerDefaultConverters() { addConverter(Object.class, List.class, (object, targetType, context) -> { Optional> firstTypeVariable = context.getFirstTypeVariable(); Argument argument = firstTypeVariable.orElse(Argument.OBJECT_ARGUMENT); - Optional converted = DefaultConversionService.this.convert(object, context.with(argument)); + Optional converted = DefaultMutableConversionService.this.convert(object, context.with(argument)); if (converted.isPresent()) { return Optional.of(Collections.singletonList(converted.get())); } @@ -246,7 +231,7 @@ protected void registerDefaultConverters() { addConverter(CharSequence.class, Class.class, (object, targetType, context) -> { ClassLoader classLoader = targetType.getClassLoader(); if (classLoader == null) { - classLoader = DefaultConversionService.class.getClassLoader(); + classLoader = DefaultMutableConversionService.class.getClassLoader(); } return ClassUtils.forName(object.toString(), classLoader); }); @@ -321,9 +306,9 @@ protected void registerDefaultConverters() { // InputStream -> Number addConverter(InputStream.class, Number.class, (object, targetType, context) -> { - Optional convert = DefaultConversionService.this.convert(object, String.class, context); + Optional convert = DefaultMutableConversionService.this.convert(object, String.class, context); if (convert.isPresent()) { - return convert.flatMap(val -> DefaultConversionService.this.convert(val, targetType, context)); + return convert.flatMap(val -> DefaultMutableConversionService.this.convert(val, targetType, context)); } return Optional.empty(); }); @@ -974,8 +959,8 @@ protected void registerDefaultConverters() { * @param Generic type * @return type converter */ - protected TypeConverter findTypeConverter(Class sourceType, Class targetType, String formattingAnnotation) { - TypeConverter typeConverter = UNCONVERTIBLE; + protected TypeConverter findTypeConverter(Class sourceType, Class targetType, String formattingAnnotation) { + TypeConverter typeConverter = UNCONVERTIBLE; List sourceHierarchy = ClassUtils.resolveHierarchy(sourceType); List targetHierarchy = ClassUtils.resolveHierarchy(targetType); for (Class sourceSuperType : sourceHierarchy) { @@ -1025,19 +1010,21 @@ private ConvertiblePair newPair(Class sourceType, Class targetType, /** * Binds the source and target. */ - private final class ConvertiblePair { - final Class source; - final Class target; + private static final class ConvertiblePair { + final Class source; + final Class target; final String formattingAnnotation; + final int hashCode; - ConvertiblePair(Class source, Class target) { + ConvertiblePair(Class source, Class target) { this(source, target, null); } - ConvertiblePair(Class source, Class target, String formattingAnnotation) { + ConvertiblePair(Class source, Class target, String formattingAnnotation) { this.source = source; this.target = target; this.formattingAnnotation = formattingAnnotation; + this.hashCode = Objects.hash(source, target, formattingAnnotation); } @Override @@ -1048,24 +1035,19 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - ConvertiblePair pair = (ConvertiblePair) o; - if (!source.equals(pair.source)) { return false; } if (!target.equals(pair.target)) { return false; } - return formattingAnnotation != null ? formattingAnnotation.equals(pair.formattingAnnotation) : pair.formattingAnnotation == null; + return Objects.equals(formattingAnnotation, pair.formattingAnnotation); } @Override public int hashCode() { - int result = source.hashCode(); - result = 31 * result + target.hashCode(); - result = 31 * result + (formattingAnnotation != null ? formattingAnnotation.hashCode() : 0); - return result; + return hashCode; } } } diff --git a/core/src/main/java/io/micronaut/core/convert/MutableConversionService.java b/core/src/main/java/io/micronaut/core/convert/MutableConversionService.java new file mode 100644 index 00000000000..50a03b04eb7 --- /dev/null +++ b/core/src/main/java/io/micronaut/core/convert/MutableConversionService.java @@ -0,0 +1,63 @@ +/* + * Copyright 2017-2020 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.core.convert; + +import io.micronaut.core.annotation.NonNull; + +import java.util.function.Function; + +/** + * A version of {@link ConversionService} that supports adding new converters. + * + * @author Denis Stepanov + * @since 4.0.0 + */ +public interface MutableConversionService extends ConversionService { + + /** + * Creates a new mutable conversion service that extends the shared conversion service. + * In most cases the mutable service from the bean context should be used. + * + * @return A new mutable conversion service. + */ + @NonNull + static MutableConversionService create() { + return new DefaultMutableConversionService(); + } + + /** + * Adds a type converter. + * + * @param sourceType The source type + * @param targetType The target type + * @param typeConverter The type converter + * @param The source generic type + * @param The target generic type + */ + void addConverter(@NonNull Class sourceType, @NonNull Class targetType, @NonNull Function typeConverter); + + /** + * Adds a type converter. + * + * @param sourceType The source type + * @param targetType The target type + * @param typeConverter The type converter + * @param The source generic type + * @param The target generic type + */ + void addConverter(@NonNull Class sourceType, @NonNull Class targetType, @NonNull TypeConverter typeConverter); + +} diff --git a/core/src/main/java/io/micronaut/core/convert/TypeConverterRegistrar.java b/core/src/main/java/io/micronaut/core/convert/TypeConverterRegistrar.java index 9baeacc5295..6d4e98fd465 100644 --- a/core/src/main/java/io/micronaut/core/convert/TypeConverterRegistrar.java +++ b/core/src/main/java/io/micronaut/core/convert/TypeConverterRegistrar.java @@ -31,5 +31,5 @@ public interface TypeConverterRegistrar { * * @param conversionService The {@link ConversionService} */ - void register(ConversionService conversionService); + void register(MutableConversionService conversionService); } diff --git a/core/src/main/java/io/micronaut/core/convert/converters/MultiValuesConverterFactory.java b/core/src/main/java/io/micronaut/core/convert/converters/MultiValuesConverterFactory.java index 22252128b90..59305ccd3ae 100644 --- a/core/src/main/java/io/micronaut/core/convert/converters/MultiValuesConverterFactory.java +++ b/core/src/main/java/io/micronaut/core/convert/converters/MultiValuesConverterFactory.java @@ -236,9 +236,9 @@ private static String joinStrings(Iterable strings, Character delimiter) */ private abstract static class AbstractConverterFromMultiValues implements FormattingTypeConverter { - protected ConversionService conversionService; + protected ConversionService conversionService; - AbstractConverterFromMultiValues(ConversionService conversionService) { + AbstractConverterFromMultiValues(ConversionService conversionService) { this.conversionService = conversionService; } @@ -336,7 +336,7 @@ public Class annotationType() { * A converter to convert from {@link ConvertibleMultiValues} to an {@link Iterable}. */ public static class MultiValuesToIterableConverter extends AbstractConverterFromMultiValues { - public MultiValuesToIterableConverter(ConversionService conversionService) { + public MultiValuesToIterableConverter(ConversionService conversionService) { super(conversionService); } @@ -407,7 +407,7 @@ private Optional convertValues(ArgumentConversionContext con * A converter to convert from {@link ConvertibleMultiValues} to an {@link Map}. */ public static class MultiValuesToMapConverter extends AbstractConverterFromMultiValues { - public MultiValuesToMapConverter(ConversionService conversionService) { + public MultiValuesToMapConverter(ConversionService conversionService) { super(conversionService); } @@ -484,7 +484,7 @@ private Optional convertValues(ArgumentConversionContext context, Map< */ public static class MultiValuesToObjectConverter extends AbstractConverterFromMultiValues { - public MultiValuesToObjectConverter(ConversionService conversionService) { + public MultiValuesToObjectConverter(ConversionService conversionService) { super(conversionService); } @@ -556,9 +556,9 @@ private Optional convertValues(ArgumentConversionContext context */ public abstract static class AbstractConverterToMultiValues implements FormattingTypeConverter { - protected ConversionService conversionService; + protected ConversionService conversionService; - public AbstractConverterToMultiValues(ConversionService conversionService) { + public AbstractConverterToMultiValues(ConversionService conversionService) { this.conversionService = conversionService; } @@ -655,7 +655,7 @@ public Class annotationType() { * A converter from {@link Iterable} to {@link ConvertibleMultiValues}. */ public static class IterableToMultiValuesConverter extends AbstractConverterToMultiValues { - public IterableToMultiValuesConverter(ConversionService conversionService) { + public IterableToMultiValuesConverter(ConversionService conversionService) { super(conversionService); } @@ -704,7 +704,7 @@ protected void addDeepObjectValues(ArgumentConversionContext context, St * A converter from {@link Map} to {@link ConvertibleMultiValues}. */ public static class MapToMultiValuesConverter extends AbstractConverterToMultiValues { - public MapToMultiValuesConverter(ConversionService conversionService) { + public MapToMultiValuesConverter(ConversionService conversionService) { super(conversionService); } @@ -759,7 +759,7 @@ protected void addDeepObjectValues(ArgumentConversionContext context, St * A converter from generic {@link Object} to {@link ConvertibleMultiValues}. */ public static class ObjectToMultiValuesConverter extends AbstractConverterToMultiValues { - public ObjectToMultiValuesConverter(ConversionService conversionService) { + public ObjectToMultiValuesConverter(ConversionService conversionService) { super(conversionService); } diff --git a/core/src/main/java/io/micronaut/core/convert/value/ConvertibleMultiValuesMap.java b/core/src/main/java/io/micronaut/core/convert/value/ConvertibleMultiValuesMap.java index 7354bbdb7a5..0dc0fc5012d 100644 --- a/core/src/main/java/io/micronaut/core/convert/value/ConvertibleMultiValuesMap.java +++ b/core/src/main/java/io/micronaut/core/convert/value/ConvertibleMultiValuesMap.java @@ -17,6 +17,7 @@ import io.micronaut.core.convert.ArgumentConversionContext; import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.ConversionServiceAware; import io.micronaut.core.type.Argument; import java.util.Collection; @@ -36,11 +37,15 @@ * @author Graeme Rocher * @since 1.0 */ -public class ConvertibleMultiValuesMap implements ConvertibleMultiValues { - public static final ConvertibleMultiValues EMPTY = new ConvertibleMultiValuesMap<>(Collections.emptyMap()); +public class ConvertibleMultiValuesMap implements ConvertibleMultiValues, ConversionServiceAware { + public static final ConvertibleMultiValues EMPTY = new ConvertibleMultiValuesMap(Collections.emptyMap()) { + @Override + public void setConversionService(ConversionService conversionService) { + } + }; protected final Map> values; - private final ConversionService conversionService; + private ConversionService conversionService; /** * Construct an empty {@link ConvertibleValuesMap}. @@ -64,7 +69,7 @@ public ConvertibleMultiValuesMap(Map> values) { * @param values The map * @param conversionService The conversion service */ - public ConvertibleMultiValuesMap(Map> values, ConversionService conversionService) { + public ConvertibleMultiValuesMap(Map> values, ConversionService conversionService) { this.values = wrapValues(values); this.conversionService = conversionService; } @@ -137,4 +142,13 @@ protected Map> wrapValues(Map> value return Collections.unmodifiableMap(values); } + @Override + public ConversionService getConversionService() { + return conversionService; + } + + @Override + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } } diff --git a/core/src/main/java/io/micronaut/core/convert/value/ConvertibleValues.java b/core/src/main/java/io/micronaut/core/convert/value/ConvertibleValues.java index 7b13e2edc87..66f40fcf3db 100644 --- a/core/src/main/java/io/micronaut/core/convert/value/ConvertibleValues.java +++ b/core/src/main/java/io/micronaut/core/convert/value/ConvertibleValues.java @@ -19,11 +19,21 @@ import io.micronaut.core.convert.ArgumentConversionContext; import io.micronaut.core.convert.ConversionContext; import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.ConversionServiceProvider; import io.micronaut.core.reflect.GenericTypeUtils; import io.micronaut.core.type.Argument; import io.micronaut.core.value.ValueResolver; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; +import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.Collectors; @@ -34,7 +44,7 @@ * @author Graeme Rocher * @since 1.0 */ -public interface ConvertibleValues extends ValueResolver, Iterable> { +public interface ConvertibleValues extends ValueResolver, Iterable>, ConversionServiceProvider { ConvertibleValues EMPTY = new ConvertibleValuesMap<>(Collections.emptyMap()); @@ -133,9 +143,9 @@ default Map asMap(Class keyType, Class valueType) { Map newMap = new LinkedHashMap<>(); for (Map.Entry entry : this) { String key = entry.getKey(); - Optional convertedKey = ConversionService.SHARED.convert(key, keyType); + Optional convertedKey = getConversionService().convert(key, keyType); if (convertedKey.isPresent()) { - Optional convertedValue = ConversionService.SHARED.convert(entry.getValue(), valueType); + Optional convertedValue = getConversionService().convert(entry.getValue(), valueType); convertedValue.ifPresent(vt -> newMap.put(convertedKey.get(), vt)); } } @@ -247,10 +257,23 @@ public V setValue(V value) { * @return The values */ static ConvertibleValues of(Map values) { + return of(values, ConversionService.SHARED); + } + + /** + * Creates a new {@link ConvertibleValues} for the values. + * + * @param values A map of values + * @param conversionService The conversion service + * @param The target generic type + * @return The values + * @since 4.0.0 + */ + static ConvertibleValues of(Map values, ConversionService conversionService) { if (values == null) { return ConvertibleValuesMap.empty(); } else { - return new ConvertibleValuesMap<>(values); + return new ConvertibleValuesMap<>(values, conversionService); } } @@ -264,4 +287,9 @@ static ConvertibleValues of(Map values) { static ConvertibleValues empty() { return ConvertibleValues.EMPTY; } + + @Override + default ConversionService getConversionService() { + return ConversionService.SHARED; + } } diff --git a/core/src/main/java/io/micronaut/core/convert/value/ConvertibleValuesMap.java b/core/src/main/java/io/micronaut/core/convert/value/ConvertibleValuesMap.java index aa63a3f0ae2..f8492761b48 100644 --- a/core/src/main/java/io/micronaut/core/convert/value/ConvertibleValuesMap.java +++ b/core/src/main/java/io/micronaut/core/convert/value/ConvertibleValuesMap.java @@ -18,6 +18,7 @@ import io.micronaut.core.annotation.Nullable; import io.micronaut.core.convert.ArgumentConversionContext; import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.ConversionServiceAware; import java.util.Collection; import java.util.Collections; @@ -34,10 +35,10 @@ * @param generic value * @since 1.0 */ -public class ConvertibleValuesMap implements ConvertibleValues { +public class ConvertibleValuesMap implements ConvertibleValues, ConversionServiceAware { protected final Map map; - private final ConversionService conversionService; + private ConversionService conversionService; /** * Constructor. @@ -59,7 +60,7 @@ public ConvertibleValuesMap(Map map) { * @param map map of values. * @param conversionService conversionService */ - public ConvertibleValuesMap(Map map, ConversionService conversionService) { + public ConvertibleValuesMap(Map map, ConversionService conversionService) { this.map = map; this.conversionService = conversionService; } @@ -104,4 +105,9 @@ public Collection values() { public static ConvertibleValues empty() { return EMPTY; } + + @Override + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } } diff --git a/core/src/main/java/io/micronaut/core/convert/value/MutableConvertibleMultiValuesMap.java b/core/src/main/java/io/micronaut/core/convert/value/MutableConvertibleMultiValuesMap.java index be9acbe3e3b..87f83ef0c74 100644 --- a/core/src/main/java/io/micronaut/core/convert/value/MutableConvertibleMultiValuesMap.java +++ b/core/src/main/java/io/micronaut/core/convert/value/MutableConvertibleMultiValuesMap.java @@ -47,7 +47,7 @@ public MutableConvertibleMultiValuesMap(Map> values) { * @param values The values * @param conversionService The conversion service */ - public MutableConvertibleMultiValuesMap(Map> values, ConversionService conversionService) { + public MutableConvertibleMultiValuesMap(Map> values, ConversionService conversionService) { super(values, conversionService); } diff --git a/core/src/main/java/io/micronaut/core/convert/value/MutableConvertibleValuesMap.java b/core/src/main/java/io/micronaut/core/convert/value/MutableConvertibleValuesMap.java index 16c1d95f818..856470bf71c 100644 --- a/core/src/main/java/io/micronaut/core/convert/value/MutableConvertibleValuesMap.java +++ b/core/src/main/java/io/micronaut/core/convert/value/MutableConvertibleValuesMap.java @@ -45,7 +45,7 @@ public MutableConvertibleValuesMap(Map map) { * @param map The map * @param conversionService The conversion service */ - public MutableConvertibleValuesMap(Map map, ConversionService conversionService) { + public MutableConvertibleValuesMap(Map map, ConversionService conversionService) { super(map, conversionService); } diff --git a/core/src/main/java/io/micronaut/core/serialize/JdkSerializer.java b/core/src/main/java/io/micronaut/core/serialize/JdkSerializer.java index cf9984f70e1..8e1462a8bf5 100644 --- a/core/src/main/java/io/micronaut/core/serialize/JdkSerializer.java +++ b/core/src/main/java/io/micronaut/core/serialize/JdkSerializer.java @@ -36,12 +36,12 @@ */ public class JdkSerializer implements ObjectSerializer { - private final ConversionService conversionService; + private final ConversionService conversionService; /** * @param conversionService The conversion service */ - public JdkSerializer(ConversionService conversionService) { + public JdkSerializer(ConversionService conversionService) { this.conversionService = conversionService; } diff --git a/core/src/main/java/io/micronaut/core/value/MapPropertyResolver.java b/core/src/main/java/io/micronaut/core/value/MapPropertyResolver.java index 369ae25b624..fe6b2d526f1 100644 --- a/core/src/main/java/io/micronaut/core/value/MapPropertyResolver.java +++ b/core/src/main/java/io/micronaut/core/value/MapPropertyResolver.java @@ -34,7 +34,7 @@ */ public class MapPropertyResolver implements PropertyResolver { private final Map map; - private final ConversionService conversionService; + private final ConversionService conversionService; /** * @param map The map to resolves the properties from diff --git a/core/src/test/groovy/io/micronaut/core/convert/DefaultConversionServiceSpec.groovy b/core/src/test/groovy/io/micronaut/core/convert/DefaultConversionServiceSpec.groovy index d83eaccf4cf..ba36af1a363 100644 --- a/core/src/test/groovy/io/micronaut/core/convert/DefaultConversionServiceSpec.groovy +++ b/core/src/test/groovy/io/micronaut/core/convert/DefaultConversionServiceSpec.groovy @@ -31,7 +31,7 @@ class DefaultConversionServiceSpec extends Specification { @Unroll void "test default conversion service converts a #sourceObject.class.name to a #targetType.name"() { given: - ConversionService conversionService = new DefaultConversionService() + ConversionService conversionService = new DefaultMutableConversionService() expect: conversionService.convert(sourceObject, targetType).get() == result @@ -70,7 +70,7 @@ class DefaultConversionServiceSpec extends Specification { void "test empty string conversion"() { given: - ConversionService conversionService = new DefaultConversionService() + ConversionService conversionService = new DefaultMutableConversionService() expect: !conversionService.convert("", targetType).isPresent() @@ -81,7 +81,7 @@ class DefaultConversionServiceSpec extends Specification { void "test convert required"() { given: - ConversionService conversionService = new DefaultConversionService() + ConversionService conversionService = new DefaultMutableConversionService() when: conversionService.convertRequired("junk", Integer) @@ -94,7 +94,7 @@ class DefaultConversionServiceSpec extends Specification { void "test conversion service with type arguments"() { given: - ConversionService conversionService = new DefaultConversionService() + ConversionService conversionService = new DefaultMutableConversionService() expect: conversionService.convert(sourceObject, targetType, ConversionContext.of(typeArguments)).get() == result diff --git a/core/src/test/groovy/io/micronaut/core/convert/value/ConvertibleValuesSpec.groovy b/core/src/test/groovy/io/micronaut/core/convert/value/ConvertibleValuesSpec.groovy index d139d38fc2e..b8814252e85 100644 --- a/core/src/test/groovy/io/micronaut/core/convert/value/ConvertibleValuesSpec.groovy +++ b/core/src/test/groovy/io/micronaut/core/convert/value/ConvertibleValuesSpec.groovy @@ -15,6 +15,7 @@ */ package io.micronaut.core.convert.value + import spock.lang.Specification class ConvertibleValuesSpec extends Specification { diff --git a/function-web/src/main/java/io/micronaut/function/web/AnnotatedFunctionRouteBuilder.java b/function-web/src/main/java/io/micronaut/function/web/AnnotatedFunctionRouteBuilder.java index 04ce2ac83c1..94cd450c02f 100644 --- a/function-web/src/main/java/io/micronaut/function/web/AnnotatedFunctionRouteBuilder.java +++ b/function-web/src/main/java/io/micronaut/function/web/AnnotatedFunctionRouteBuilder.java @@ -77,7 +77,7 @@ public class AnnotatedFunctionRouteBuilder public AnnotatedFunctionRouteBuilder( ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy, - ConversionService conversionService, + ConversionService conversionService, MediaTypeCodecRegistry codecRegistry, @Value("${micronaut.function.context-path:/}") String contextPath) { super(executionHandleLocator, uriNamingStrategy, conversionService); diff --git a/function/src/main/java/io/micronaut/function/executor/StreamFunctionExecutor.java b/function/src/main/java/io/micronaut/function/executor/StreamFunctionExecutor.java index 53c4d180064..fc86979eef1 100644 --- a/function/src/main/java/io/micronaut/function/executor/StreamFunctionExecutor.java +++ b/function/src/main/java/io/micronaut/function/executor/StreamFunctionExecutor.java @@ -178,7 +178,7 @@ static void encode(Environment environment, LocalFunctionRegistry registry, Clas } private Object decodeInputArgument( - ConversionService conversionService, + ConversionService conversionService, LocalFunctionRegistry localFunctionRegistry, Argument arg, InputStream input) { @@ -204,7 +204,7 @@ private Object decodeInputArgument( } private Object decodeContext( - ConversionService conversionService, + ConversionService conversionService, Argument arg, Object context) { if (ClassUtils.isJavaLangType(arg.getType())) { @@ -216,7 +216,7 @@ private Object decodeContext( throw new CodecException("Unable to decode argument from stream: " + arg); } - private Object doConvertInput(ConversionService conversionService, Argument arg, Object object) { + private Object doConvertInput(ConversionService conversionService, Argument arg, Object object) { ArgumentConversionContext conversionContext = ConversionContext.of(arg); Optional convert = conversionService.convert(object, conversionContext); if (convert.isPresent()) { diff --git a/http-client-core/src/main/java/io/micronaut/http/client/bind/DefaultHttpClientBinderRegistry.java b/http-client-core/src/main/java/io/micronaut/http/client/bind/DefaultHttpClientBinderRegistry.java index 64dd91d4fa3..f04bc5659cd 100644 --- a/http-client-core/src/main/java/io/micronaut/http/client/bind/DefaultHttpClientBinderRegistry.java +++ b/http-client-core/src/main/java/io/micronaut/http/client/bind/DefaultHttpClientBinderRegistry.java @@ -80,7 +80,7 @@ public class DefaultHttpClientBinderRegistry implements HttpClientBinderRegistry * @param binders The request binders * @param beanContext The context to resolve beans */ - protected DefaultHttpClientBinderRegistry(ConversionService conversionService, + protected DefaultHttpClientBinderRegistry(ConversionService conversionService, List binders, BeanContext beanContext) { byType.put(Argument.of(HttpHeaders.class).typeHashCode(), (ClientArgumentRequestBinder) (context, uriContext, value, request) -> value.forEachValue(request::header)); diff --git a/http-client-core/src/main/java/io/micronaut/http/client/bind/binders/QueryValueClientArgumentRequestBinder.java b/http-client-core/src/main/java/io/micronaut/http/client/bind/binders/QueryValueClientArgumentRequestBinder.java index 21bfee9a010..71e7e840081 100644 --- a/http-client-core/src/main/java/io/micronaut/http/client/bind/binders/QueryValueClientArgumentRequestBinder.java +++ b/http-client-core/src/main/java/io/micronaut/http/client/bind/binders/QueryValueClientArgumentRequestBinder.java @@ -42,9 +42,9 @@ */ public class QueryValueClientArgumentRequestBinder implements AnnotatedClientArgumentRequestBinder { - private final ConversionService conversionService; + private final ConversionService conversionService; - public QueryValueClientArgumentRequestBinder(ConversionService conversionService) { + public QueryValueClientArgumentRequestBinder(ConversionService conversionService) { this.conversionService = conversionService; } diff --git a/http-client-core/src/main/java/io/micronaut/http/client/interceptor/HttpClientIntroductionAdvice.java b/http-client-core/src/main/java/io/micronaut/http/client/interceptor/HttpClientIntroductionAdvice.java index e13c3b90578..b6a30bcceae 100644 --- a/http-client-core/src/main/java/io/micronaut/http/client/interceptor/HttpClientIntroductionAdvice.java +++ b/http-client-core/src/main/java/io/micronaut/http/client/interceptor/HttpClientIntroductionAdvice.java @@ -114,7 +114,7 @@ public class HttpClientIntroductionAdvice implements MethodInterceptor clientFactory; - private final ConversionService conversionService; + private final ConversionService conversionService; /** * Constructor for advice class to setup things like Headers, Cookies, Parameters for Clients. @@ -130,7 +130,7 @@ public HttpClientIntroductionAdvice( JsonMediaTypeCodec jsonMediaTypeCodec, List transformers, HttpClientBinderRegistry binderRegistry, - ConversionService conversionService) { + ConversionService conversionService) { this.clientFactory = clientFactory; this.jsonMediaTypeCodec = jsonMediaTypeCodec; this.transformers = transformers != null ? transformers : Collections.emptyList(); @@ -194,7 +194,7 @@ public Object intercept(MethodInvocationContext context) { .orElse(argument.getName()); // Convert and put as path param if (argument.getAnnotationMetadata().hasStereotype(Format.class)) { - ConversionService.SHARED.convert(value, + conversionService.convert(value, ConversionContext.STRING.with(argument.getAnnotationMetadata())) .ifPresent(v -> pathParams.put(name, v)); } else { @@ -461,9 +461,9 @@ private Publisher httpClientResponseStreamingPublisher(StreamingHttpClient strea if (reactiveValueType == ByteBuffer.class) { return byteBufferPublisher; } else { - if (ConversionService.SHARED.canConvert(ByteBuffer.class, reactiveValueType)) { + if (conversionService.canConvert(ByteBuffer.class, reactiveValueType)) { // It would be nice if we could capture the TypeConverter here - return Publishers.map(byteBufferPublisher, value -> ConversionService.SHARED.convert(value, reactiveValueType).get()); + return Publishers.map(byteBufferPublisher, value -> conversionService.convert(value, reactiveValueType).get()); } else { throw new ConfigurationException("Cannot create the generated HTTP client's " + "required return type, since no TypeConverter from ByteBuffer to " + diff --git a/http-client-core/src/main/java/io/micronaut/http/client/loadbalance/LoadBalancerConverters.java b/http-client-core/src/main/java/io/micronaut/http/client/loadbalance/LoadBalancerConverters.java index 384626fd81d..00dac991eb4 100644 --- a/http-client-core/src/main/java/io/micronaut/http/client/loadbalance/LoadBalancerConverters.java +++ b/http-client-core/src/main/java/io/micronaut/http/client/loadbalance/LoadBalancerConverters.java @@ -15,10 +15,9 @@ */ package io.micronaut.http.client.loadbalance; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverterRegistrar; import io.micronaut.http.client.LoadBalancer; -import jakarta.inject.Singleton; import java.net.URI; import java.net.URISyntaxException; @@ -30,11 +29,10 @@ * @author graemerocher * @since 1.0 */ -@Singleton public class LoadBalancerConverters implements TypeConverterRegistrar { - @SuppressWarnings("deprecation") + @Override - public void register(ConversionService conversionService) { + public void register(MutableConversionService conversionService) { conversionService.addConverter(URI.class, LoadBalancer.class, LoadBalancer::fixed); conversionService.addConverter(URL.class, LoadBalancer.class, LoadBalancer::fixed); conversionService.addConverter(String.class, LoadBalancer.class, url -> { diff --git a/http-client-core/src/main/resources/META-INF/services/io.micronaut.core.convert.TypeConverterRegistrar b/http-client-core/src/main/resources/META-INF/services/io.micronaut.core.convert.TypeConverterRegistrar new file mode 100644 index 00000000000..a1eb0673490 --- /dev/null +++ b/http-client-core/src/main/resources/META-INF/services/io.micronaut.core.convert.TypeConverterRegistrar @@ -0,0 +1 @@ +io.micronaut.http.client.loadbalance.LoadBalancerConverters diff --git a/http-client/src/main/java/io/micronaut/http/client/netty/DefaultHttpClient.java b/http-client/src/main/java/io/micronaut/http/client/netty/DefaultHttpClient.java index c1cdb016b40..f09f399e286 100644 --- a/http-client/src/main/java/io/micronaut/http/client/netty/DefaultHttpClient.java +++ b/http-client/src/main/java/io/micronaut/http/client/netty/DefaultHttpClient.java @@ -24,6 +24,7 @@ import io.micronaut.core.async.publisher.Publishers; import io.micronaut.core.beans.BeanMap; import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.ConversionServiceAware; import io.micronaut.core.io.ResourceResolver; import io.micronaut.core.io.buffer.ByteBuffer; import io.micronaut.core.io.buffer.ByteBufferFactory; @@ -252,7 +253,7 @@ public class DefaultHttpClient implements private final RequestBinderRegistry requestBinderRegistry; private final List invocationInstrumenterFactories; private final String informationalServiceId; - private final ConversionService conversionService; + private final ConversionService conversionService; /** * Construct a client for the given arguments. @@ -265,6 +266,7 @@ public class DefaultHttpClient implements * @param codecRegistry The {@link MediaTypeCodecRegistry} to use for encoding and decoding objects * @param annotationMetadataResolver The annotation metadata resolver * @param invocationInstrumenterFactories The invocation instrumeter factories to instrument netty handlers execution with + * @param conversionService The conversion service * @param filters The filters to use */ public DefaultHttpClient(@Nullable LoadBalancer loadBalancer, @@ -275,6 +277,7 @@ public DefaultHttpClient(@Nullable LoadBalancer loadBalancer, MediaTypeCodecRegistry codecRegistry, @Nullable AnnotationMetadataResolver annotationMetadataResolver, List invocationInstrumenterFactories, + ConversionService conversionService, HttpClientFilter... filters) { this(loadBalancer, null, @@ -286,13 +289,13 @@ public DefaultHttpClient(@Nullable LoadBalancer loadBalancer, nettyClientSslBuilder, codecRegistry, WebSocketBeanRegistry.EMPTY, - new DefaultRequestBinderRegistry(ConversionService.SHARED), + new DefaultRequestBinderRegistry(conversionService), null, NioSocketChannel::new, CompositeNettyClientCustomizer.EMPTY, invocationInstrumenterFactories, null, - ConversionService.SHARED); + conversionService); } /** @@ -313,6 +316,7 @@ public DefaultHttpClient(@Nullable LoadBalancer loadBalancer, * @param clientCustomizer The pipeline customizer * @param invocationInstrumenterFactories The invocation instrumeter factories to instrument netty handlers execution with * @param informationalServiceId Optional service ID that will be passed to exceptions created by this client + * @param conversionService The conversionService */ public DefaultHttpClient(@Nullable LoadBalancer loadBalancer, @Nullable HttpVersionSelection explicitHttpVersion, @@ -330,7 +334,7 @@ public DefaultHttpClient(@Nullable LoadBalancer loadBalancer, NettyClientCustomizer clientCustomizer, List invocationInstrumenterFactories, @Nullable String informationalServiceId, - @NonNull ConversionService conversionService + ConversionService conversionService ) { ArgumentUtils.requireNonNull("nettyClientSslBuilder", nettyClientSslBuilder); ArgumentUtils.requireNonNull("codecRegistry", codecRegistry); @@ -406,7 +410,7 @@ public DefaultHttpClient(@Nullable URI uri, @NonNull HttpClientConfiguration con new NettyClientSslBuilder(new ResourceResolver()), createDefaultMediaTypeRegistry(), AnnotationMetadataResolver.DEFAULT, - Collections.emptyList()); + Collections.emptyList(), ConversionService.SHARED); } /** @@ -420,7 +424,7 @@ configuration, null, new DefaultThreadFactory(MultithreadEventLoopGroup.class), new NettyClientSslBuilder(new ResourceResolver()), createDefaultMediaTypeRegistry(), AnnotationMetadataResolver.DEFAULT, - invocationInstrumenterFactories); + invocationInstrumenterFactories, ConversionService.SHARED); } static boolean isAcceptEvents(io.micronaut.http.HttpRequest request) { @@ -539,6 +543,7 @@ private MutableHttpRequest toMutableRequest(io.micronaut.http.HttpRequest @SuppressWarnings("SubscriberImplementation") @Override public Publisher>> eventStream(@NonNull io.micronaut.http.HttpRequest request) { + setupConversionService(request); return eventStreamOrError(request, null); } @@ -671,11 +676,13 @@ public void onComplete() { @Override public Publisher> eventStream(@NonNull io.micronaut.http.HttpRequest request, @NonNull Argument eventType) { + setupConversionService(request); return eventStream(request, eventType, DEFAULT_ERROR_TYPE); } @Override public Publisher> eventStream(@NonNull io.micronaut.http.HttpRequest request, @NonNull Argument eventType, @NonNull Argument errorType) { + setupConversionService(request); return Flux.from(eventStreamOrError(request, errorType)).map(byteBufferEvent -> { ByteBuffer data = byteBufferEvent.getData(); Optional registeredCodec; @@ -697,11 +704,13 @@ public Publisher> eventStream(@NonNull io.micronaut.http.HttpReq @Override public Publisher> dataStream(@NonNull io.micronaut.http.HttpRequest request) { + setupConversionService(request); return dataStream(request, DEFAULT_ERROR_TYPE); } @Override public Publisher> dataStream(@NonNull io.micronaut.http.HttpRequest request, @NonNull Argument errorType) { + setupConversionService(request); final io.micronaut.http.HttpRequest parentRequest = ServerRequestContext.currentRequest().orElse(null); return new MicronautFlux<>(Flux.from(resolveRequestURI(request)) .flatMap(requestURI -> dataStreamImpl(toMutableRequest(request), errorType, parentRequest, requestURI))) @@ -723,6 +732,7 @@ public Publisher>> exchangeStre @Override public Publisher>> exchangeStream(@NonNull io.micronaut.http.HttpRequest request, @NonNull Argument errorType) { + setupConversionService(request); io.micronaut.http.HttpRequest parentRequest = ServerRequestContext.currentRequest().orElse(null); return new MicronautFlux<>(Flux.from(resolveRequestURI(request)) .flatMap(uri -> exchangeStreamImpl(parentRequest, toMutableRequest(request), errorType, uri))) @@ -741,7 +751,9 @@ public Publisher jsonStream(@NonNull io.micronaut.http.HttpRequest @Override public Publisher jsonStream(@NonNull io.micronaut.http.HttpRequest request, @NonNull Argument type, @NonNull Argument errorType) { + setupConversionService(request); final io.micronaut.http.HttpRequest parentRequest = ServerRequestContext.currentRequest().orElse(null); + setupConversionService(parentRequest); return Flux.from(resolveRequestURI(request)) .flatMap(requestURI -> jsonStreamImpl(parentRequest, toMutableRequest(request), type, errorType, requestURI)); } @@ -754,11 +766,13 @@ public Publisher> jsonStream(@NonNull io.micronaut.http. @Override public Publisher jsonStream(@NonNull io.micronaut.http.HttpRequest request, @NonNull Class type) { + setupConversionService(request); return jsonStream(request, io.micronaut.core.type.Argument.of(type)); } @Override public Publisher> exchange(@NonNull io.micronaut.http.HttpRequest request, @NonNull Argument bodyType, @NonNull Argument errorType) { + setupConversionService(request); final io.micronaut.http.HttpRequest parentRequest = ServerRequestContext.currentRequest().orElse(null); Publisher uriPublisher = resolveRequestURI(request); return Flux.from(uriPublisher) @@ -767,6 +781,7 @@ public Publisher> exchange(@NonNull @Override public Publisher retrieve(io.micronaut.http.HttpRequest request, Argument bodyType, Argument errorType) { + setupConversionService(request); // mostly same as default impl, but with exception customization return Flux.from(exchange(request, bodyType, errorType)).map(response -> { if (bodyType.getType() == HttpStatus.class) { @@ -790,6 +805,7 @@ public Publisher retrieve(io.micronaut.http.HttpRequest request, @Override public Publisher connect(Class clientEndpointType, io.micronaut.http.MutableHttpRequest request) { + setupConversionService(request); Publisher uriPublisher = resolveRequestURI(request); return Flux.from(uriPublisher) .switchMap(resolvedURI -> connectWebSocket(resolvedURI, request, clientEndpointType, null)); @@ -851,7 +867,8 @@ private Publisher connectWebSocket(URI uri, MutableHttpRequest request WebSocketClientHandshakerFactory.newHandshaker( webSocketURL, protocolVersion, subprotocol, true, customHeaders, maxFramePayloadLength), requestBinderRegistry, - mediaTypeCodecRegistry); + mediaTypeCodecRegistry, + conversionService); return connectionManager.connectForWebsocket(requestKey, handler) .then(handler.getHandshakeCompletedMono()); @@ -872,7 +889,7 @@ private Flux>> exchangeStreamImpl(io.micronaut.ht traceBody("Response", byteBuf); } ByteBuffer byteBuffer = byteBufferFactory.wrap(byteBuf); - NettyStreamedHttpResponse> thisResponse = new NettyStreamedHttpResponse<>(streamedHttpResponse); + NettyStreamedHttpResponse> thisResponse = new NettyStreamedHttpResponse<>(streamedHttpResponse, conversionService); thisResponse.setBody(byteBuffer); return (HttpResponse>) new HttpResponseWrapper<>(thisResponse); }); @@ -964,6 +981,7 @@ public Publisher> proxy(@NonNull io.micronaut.http.HttpRe @Override public Publisher> proxy(@NonNull io.micronaut.http.HttpRequest request, @NonNull ProxyRequestOptions options) { Objects.requireNonNull(options, "options"); + setupConversionService(request); return Flux.from(resolveRequestURI(request)) .flatMap(requestURI -> { io.micronaut.http.MutableHttpRequest httpRequest = toMutableRequest(request); @@ -988,6 +1006,12 @@ public Publisher> proxy(@NonNull io.micronaut.http.HttpRe }); } + private void setupConversionService(io.micronaut.http.HttpRequest httpRequest) { + if (httpRequest instanceof ConversionServiceAware) { + ((ConversionServiceAware) httpRequest).setConversionService(conversionService); + } + } + private Flux> connectAndStream( io.micronaut.http.HttpRequest parentRequest, io.micronaut.http.HttpRequest request, @@ -1425,7 +1449,7 @@ public void onError(Throwable t) { public void onComplete() { try { FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(nettyResponse.protocolVersion(), nettyResponse.status(), buffer, nettyResponse.headers(), new DefaultHttpHeaders(true)); - final FullNettyClientHttpResponse fullNettyClientHttpResponse = new FullNettyClientHttpResponse<>(fullHttpResponse, mediaTypeCodecRegistry, byteBufferFactory, (Argument) errorType, true); + final FullNettyClientHttpResponse fullNettyClientHttpResponse = new FullNettyClientHttpResponse<>(fullHttpResponse, mediaTypeCodecRegistry, byteBufferFactory, (Argument) errorType, true, conversionService); fullNettyClientHttpResponse.onComplete(); emitter.error(customizeException(new HttpClientResponseException( fullHttpResponse.status().reasonPhrase(), @@ -2195,7 +2219,7 @@ protected void buildResponse(Promise> promise, FullHttpR boolean convertBodyWithBodyType = msg.status().code() < 400 || (!DefaultHttpClient.this.configuration.isExceptionOnErrorStatus() && bodyType.equalsType(errorType)); FullNettyClientHttpResponse response - = new FullNettyClientHttpResponse<>(msg, mediaTypeCodecRegistry, byteBufferFactory, bodyType, convertBodyWithBodyType); + = new FullNettyClientHttpResponse<>(msg, mediaTypeCodecRegistry, byteBufferFactory, bodyType, convertBodyWithBodyType, conversionService); if (convertBodyWithBodyType) { promise.trySuccess(response); @@ -2253,7 +2277,8 @@ private HttpClientResponseException makeErrorBodyParseError(FullHttpResponse ful mediaTypeCodecRegistry, byteBufferFactory, null, - false + false, + conversionService ); // this onComplete call disables further parsing by HttpClientResponseException errorResponse.onComplete(); @@ -2271,7 +2296,8 @@ private void makeNormalBodyParseError(FullHttpResponse fullResponse, Throwable t mediaTypeCodecRegistry, byteBufferFactory, null, - false + false, + conversionService ); HttpClientResponseException clientResponseError = customizeException(new HttpClientResponseException( "Error decoding HTTP response body: " + t.getMessage(), @@ -2344,7 +2370,7 @@ protected void buildResponse(Promise> promise, Fu msg.headers(), bodyPublisher ); - promise.trySuccess(new NettyStreamedHttpResponse<>(nettyResponse)); + promise.trySuccess(new NettyStreamedHttpResponse<>(nettyResponse, conversionService)); } @Override @@ -2367,7 +2393,6 @@ public boolean acceptInboundMessage(Object msg) { return msg instanceof StreamedHttpResponse; } - @Override protected void removeHandler(ChannelHandlerContext ctx) { ctx.pipeline().remove(ChannelPipelineCustomizer.HANDLER_MICRONAUT_HTTP_RESPONSE_FULL); ctx.pipeline().remove(ChannelPipelineCustomizer.HANDLER_MICRONAUT_HTTP_RESPONSE_STREAM); @@ -2375,7 +2400,7 @@ protected void removeHandler(ChannelHandlerContext ctx) { @Override protected void buildResponse(Promise> promise, StreamedHttpResponse msg) { - promise.trySuccess(new NettyStreamedHttpResponse<>(msg)); + promise.trySuccess(new NettyStreamedHttpResponse<>(msg, conversionService)); } @Override diff --git a/http-client/src/main/java/io/micronaut/http/client/netty/FullNettyClientHttpResponse.java b/http-client/src/main/java/io/micronaut/http/client/netty/FullNettyClientHttpResponse.java index d106a6d7b2f..208174e6b55 100644 --- a/http-client/src/main/java/io/micronaut/http/client/netty/FullNettyClientHttpResponse.java +++ b/http-client/src/main/java/io/micronaut/http/client/netty/FullNettyClientHttpResponse.java @@ -71,6 +71,7 @@ public class FullNettyClientHttpResponse implements HttpResponse, Completa private final B body; private boolean complete; private byte[] bodyBytes; + private final ConversionService conversionService; /** * @param fullHttpResponse The full Http response @@ -78,20 +79,22 @@ public class FullNettyClientHttpResponse implements HttpResponse, Completa * @param byteBufferFactory The byte buffer factory * @param bodyType The body type * @param convertBody Whether to auto convert the body to bodyType + * @param conversionService The conversion service */ FullNettyClientHttpResponse( FullHttpResponse fullHttpResponse, MediaTypeCodecRegistry mediaTypeCodecRegistry, ByteBufferFactory byteBufferFactory, Argument bodyType, - boolean convertBody) { - - this.headers = new NettyHttpHeaders(fullHttpResponse.headers(), ConversionService.SHARED); + boolean convertBody, + ConversionService conversionService) { + this.conversionService = conversionService; + this.headers = new NettyHttpHeaders(fullHttpResponse.headers(), conversionService); this.attributes = new MutableConvertibleValuesMap<>(); this.nettyHttpResponse = fullHttpResponse; this.mediaTypeCodecRegistry = mediaTypeCodecRegistry; this.byteBufferFactory = byteBufferFactory; - this.nettyCookies = new NettyCookies(fullHttpResponse.headers(), ConversionService.SHARED); + this.nettyCookies = new NettyCookies(fullHttpResponse.headers(), conversionService); Class rawBodyType = bodyType != null ? bodyType.getType() : null; if (rawBodyType != null && !HttpStatus.class.isAssignableFrom(rawBodyType)) { if (HttpResponse.class.isAssignableFrom(bodyType.getType())) { @@ -200,7 +203,7 @@ public Optional getBody(Argument type) { ByteBuf bytebuf = (ByteBuf) ((ByteBuffer) b).asNativeBuffer(); return convertByteBuf(bytebuf, finalArgument); } else { - final Optional opt = ConversionService.SHARED.convert(b, ConversionContext.of(finalArgument)); + final Optional opt = conversionService.convert(b, ConversionContext.of(finalArgument)); if (!opt.isPresent()) { ByteBuf content = nettyHttpResponse.content(); return convertByteBuf(content, finalArgument); @@ -280,7 +283,7 @@ private Optional convertByteBuf(ByteBuf content, Argument type) { LOG.trace("Missing or unknown Content-Type received from server."); } // last chance, try type conversion - return ConversionService.SHARED.convert(content, ConversionContext.of(type)); + return conversionService.convert(content, ConversionContext.of(type)); } private Optional convertBytes(byte[] bytes, Argument type) { @@ -301,7 +304,7 @@ private Optional convertBytes(byte[] bytes, Argument type) { } } // last chance, try type conversion - return ConversionService.SHARED.convert(bytes, ConversionContext.of(type)); + return conversionService.convert(bytes, ConversionContext.of(type)); } @Override diff --git a/http-client/src/main/java/io/micronaut/http/client/netty/MutableHttpRequestWrapper.java b/http-client/src/main/java/io/micronaut/http/client/netty/MutableHttpRequestWrapper.java index 8e1343be39f..a02019d0212 100644 --- a/http-client/src/main/java/io/micronaut/http/client/netty/MutableHttpRequestWrapper.java +++ b/http-client/src/main/java/io/micronaut/http/client/netty/MutableHttpRequestWrapper.java @@ -39,19 +39,19 @@ */ @Internal final class MutableHttpRequestWrapper extends HttpRequestWrapper implements MutableHttpRequest { - private final ConversionService conversionService; + private ConversionService conversionService; @Nullable private B body; @Nullable private URI uri; - MutableHttpRequestWrapper(ConversionService conversionService, HttpRequest delegate) { + MutableHttpRequestWrapper(ConversionService conversionService, HttpRequest delegate) { super(delegate); this.conversionService = conversionService; } - static MutableHttpRequest wrapIfNecessary(ConversionService conversionService, HttpRequest request) { + static MutableHttpRequest wrapIfNecessary(ConversionService conversionService, HttpRequest request) { if (request instanceof MutableHttpRequest) { return (MutableHttpRequest) request; } else { @@ -128,4 +128,9 @@ public MutableHttpRequest body(T body) { this.body = (B) body; return (MutableHttpRequest) this; } + + @Override + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } } diff --git a/http-client/src/main/java/io/micronaut/http/client/netty/NettyClientHttpRequest.java b/http-client/src/main/java/io/micronaut/http/client/netty/NettyClientHttpRequest.java index 0320e56e74c..347fd2fe02e 100644 --- a/http-client/src/main/java/io/micronaut/http/client/netty/NettyClientHttpRequest.java +++ b/http-client/src/main/java/io/micronaut/http/client/netty/NettyClientHttpRequest.java @@ -76,6 +76,7 @@ class NettyClientHttpRequest implements MutableHttpRequest, NettyHttpReque private URI uri; private Object body; private NettyHttpParameters httpParameters; + private ConversionService conversionService = ConversionService.SHARED; /** * This constructor is actually required for the case of non-standard http methods. @@ -175,7 +176,7 @@ public Optional getBody(Class type) { @Override public Optional getBody(Argument type) { - return getBody().flatMap(b -> ConversionService.SHARED.convert(b, ConversionContext.of(type))); + return getBody().flatMap(b -> conversionService.convert(b, ConversionContext.of(type))); } @Override @@ -217,7 +218,7 @@ public URI getUri() { private NettyHttpParameters decodeParameters(URI uri) { QueryStringDecoder queryStringDecoder = createDecoder(uri); return new NettyHttpParameters(queryStringDecoder.parameters(), - ConversionService.SHARED, + conversionService, (name, value) -> { UriBuilder newUri = UriBuilder.of(getUri()); newUri.replaceQueryParam(name.toString(), value.toArray()); @@ -327,4 +328,9 @@ public HttpRequest toHttpRequest() { public boolean isStream() { return body instanceof Publisher; } + + @Override + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } } diff --git a/http-client/src/main/java/io/micronaut/http/client/netty/NettyStreamedHttpResponse.java b/http-client/src/main/java/io/micronaut/http/client/netty/NettyStreamedHttpResponse.java index 9d9ed4d521a..56e7aed9114 100644 --- a/http-client/src/main/java/io/micronaut/http/client/netty/NettyStreamedHttpResponse.java +++ b/http-client/src/main/java/io/micronaut/http/client/netty/NettyStreamedHttpResponse.java @@ -57,10 +57,11 @@ class NettyStreamedHttpResponse implements MutableHttpResponse, NettyHttpR /** * @param response The streamed Http response + * @param conversionService The conversion service */ - NettyStreamedHttpResponse(StreamedHttpResponse response) { + NettyStreamedHttpResponse(StreamedHttpResponse response, ConversionService conversionService) { this.nettyResponse = response; - this.headers = new NettyHttpHeaders(response.headers(), ConversionService.SHARED); + this.headers = new NettyHttpHeaders(response.headers(), conversionService); } /** diff --git a/http-client/src/main/java/io/micronaut/http/client/netty/websocket/NettyWebSocketClientHandler.java b/http-client/src/main/java/io/micronaut/http/client/netty/websocket/NettyWebSocketClientHandler.java index 0bf36fc9700..a7e7690d2a8 100644 --- a/http-client/src/main/java/io/micronaut/http/client/netty/websocket/NettyWebSocketClientHandler.java +++ b/http-client/src/main/java/io/micronaut/http/client/netty/websocket/NettyWebSocketClientHandler.java @@ -87,23 +87,26 @@ public class NettyWebSocketClientHandler extends AbstractNettyWebSocketHandle /** * Default constructor. - * @param request The originating request that created the WebSocket. - * @param webSocketBean The WebSocket client bean. - * @param handshaker The handshaker - * @param requestBinderRegistry The request binder registry + * + * @param request The originating request that created the WebSocket. + * @param webSocketBean The WebSocket client bean. + * @param handshaker The handshaker + * @param requestBinderRegistry The request binder registry * @param mediaTypeCodecRegistry The media type codec registry + * @param conversionService The conversionService */ public NettyWebSocketClientHandler( MutableHttpRequest request, WebSocketBean webSocketBean, final WebSocketClientHandshaker handshaker, RequestBinderRegistry requestBinderRegistry, - MediaTypeCodecRegistry mediaTypeCodecRegistry) { - super(null, requestBinderRegistry, mediaTypeCodecRegistry, webSocketBean, request, Collections.emptyMap(), handshaker.version(), handshaker.actualSubprotocol(), null); + MediaTypeCodecRegistry mediaTypeCodecRegistry, + ConversionService conversionService) { + super(null, requestBinderRegistry, mediaTypeCodecRegistry, webSocketBean, request, Collections.emptyMap(), handshaker.version(), handshaker.actualSubprotocol(), null, conversionService); this.codecRegistry = mediaTypeCodecRegistry; this.handshaker = handshaker; this.genericWebSocketBean = webSocketBean; - this.webSocketStateBinderRegistry = new WebSocketStateBinderRegistry(requestBinderRegistry != null ? requestBinderRegistry : new DefaultRequestBinderRegistry(ConversionService.SHARED)); + this.webSocketStateBinderRegistry = new WebSocketStateBinderRegistry(requestBinderRegistry != null ? requestBinderRegistry : new DefaultRequestBinderRegistry(conversionService), conversionService); String clientPath = webSocketBean.getBeanDefinition().stringValue(ClientWebSocket.class).orElse(""); UriMatchTemplate matchTemplate = UriMatchTemplate.of(clientPath); this.matchInfo = matchTemplate.match(request.getPath()).orElse(null); @@ -282,7 +285,7 @@ protected NettyWebSocketSession createWebSocketSession(ChannelHandlerContext ctx @Override public ConvertibleValues getUriVariables() { if (matchInfo != null) { - return ConvertibleValues.of(matchInfo.getVariableValues()); + return ConvertibleValues.of(matchInfo.getVariableValues(), conversionService); } return ConvertibleValues.empty(); } diff --git a/http-client/src/test/groovy/io/micronaut/http/client/HttpPostSpec.groovy b/http-client/src/test/groovy/io/micronaut/http/client/HttpPostSpec.groovy index 345729153c6..75de09a4989 100644 --- a/http-client/src/test/groovy/io/micronaut/http/client/HttpPostSpec.groovy +++ b/http-client/src/test/groovy/io/micronaut/http/client/HttpPostSpec.groovy @@ -19,6 +19,7 @@ import groovy.transform.EqualsAndHashCode import io.micronaut.context.annotation.Property import io.micronaut.context.annotation.Requires import io.micronaut.core.annotation.Introspected +import io.micronaut.core.convert.ConversionService import io.micronaut.core.type.Argument import io.micronaut.http.HttpRequest import io.micronaut.http.HttpResponse @@ -51,6 +52,9 @@ class HttpPostSpec extends Specification { @Inject PostClient postClient + @Inject + ConversionService conversionService + void "test send invalid http method"() { given: def book = new Book(title: "The Stand", pages: 1000) @@ -383,6 +387,12 @@ class HttpPostSpec extends Specification { when: def request = HttpRequest.POST('/', JsonObject.createObjectNode([:])) + then: + request.getBody(String).get() != '{}' + + when: + request.setConversionService(conversionService) + then: request.getBody(String).get() == '{}' } diff --git a/http-client/src/test/groovy/io/micronaut/http/client/netty/FullNettyClientHttpResponseSpec.groovy b/http-client/src/test/groovy/io/micronaut/http/client/netty/FullNettyClientHttpResponseSpec.groovy index 611e4c9ca4d..1728a0a4475 100644 --- a/http-client/src/test/groovy/io/micronaut/http/client/netty/FullNettyClientHttpResponseSpec.groovy +++ b/http-client/src/test/groovy/io/micronaut/http/client/netty/FullNettyClientHttpResponseSpec.groovy @@ -1,6 +1,6 @@ package io.micronaut.http.client.netty - +import io.micronaut.core.convert.ConversionService import io.micronaut.http.cookie.Cookie import io.micronaut.http.cookie.Cookies import io.netty.handler.codec.http.DefaultFullHttpResponse @@ -22,7 +22,7 @@ class FullNettyClientHttpResponseSpec extends Specification { fullHttpResponse.headers().set(httpHeaders) when: - FullNettyClientHttpResponse response = new FullNettyClientHttpResponse(fullHttpResponse, null, null, null, false) + FullNettyClientHttpResponse response = new FullNettyClientHttpResponse(fullHttpResponse, null, null, null, false, ConversionService.SHARED) then: Cookies cookies = response.getCookies() @@ -45,7 +45,7 @@ class FullNettyClientHttpResponseSpec extends Specification { fullHttpResponse.headers().set(httpHeaders) when: - FullNettyClientHttpResponse response = new FullNettyClientHttpResponse(fullHttpResponse, null, null, null, false) + FullNettyClientHttpResponse response = new FullNettyClientHttpResponse(fullHttpResponse, null, null, null, false, ConversionService.SHARED) then: Cookies cookies = response.getCookies() diff --git a/http-client/src/test/groovy/io/micronaut/http/client/netty/NettyStreamedHttpResponseSpec.groovy b/http-client/src/test/groovy/io/micronaut/http/client/netty/NettyStreamedHttpResponseSpec.groovy index 6eae29b79d9..8d488e29b39 100644 --- a/http-client/src/test/groovy/io/micronaut/http/client/netty/NettyStreamedHttpResponseSpec.groovy +++ b/http-client/src/test/groovy/io/micronaut/http/client/netty/NettyStreamedHttpResponseSpec.groovy @@ -1,5 +1,6 @@ package io.micronaut.http.client.netty +import io.micronaut.core.convert.ConversionService import io.micronaut.http.cookie.Cookie import io.micronaut.http.cookie.Cookies import io.micronaut.http.netty.stream.DefaultStreamedHttpResponse @@ -21,7 +22,7 @@ class NettyStreamedHttpResponseSpec extends Specification { streamedHttpResponse.headers().set(httpHeaders) when: - NettyStreamedHttpResponse response = new NettyStreamedHttpResponse(streamedHttpResponse) + NettyStreamedHttpResponse response = new NettyStreamedHttpResponse(streamedHttpResponse, ConversionService.SHARED) then: Cookies cookies = response.getCookies() @@ -44,7 +45,7 @@ class NettyStreamedHttpResponseSpec extends Specification { streamedHttpResponse.headers().set(httpHeaders) when: - NettyStreamedHttpResponse response = new NettyStreamedHttpResponse(streamedHttpResponse) + NettyStreamedHttpResponse response = new NettyStreamedHttpResponse(streamedHttpResponse, ConversionService.SHARED) then: Cookies cookies = response.getCookies() @@ -68,7 +69,7 @@ class NettyStreamedHttpResponseSpec extends Specification { streamedHttpResponse.headers().set(httpHeaders) when: - NettyStreamedHttpResponse response = new NettyStreamedHttpResponse(streamedHttpResponse) + NettyStreamedHttpResponse response = new NettyStreamedHttpResponse(streamedHttpResponse, ConversionService.SHARED) response.cookie(Cookie.of("ADDED", "xyz").httpOnly(true).domain(".foo.com")) then: diff --git a/http-netty/src/main/java/io/micronaut/http/netty/AbstractNettyHttpRequest.java b/http-netty/src/main/java/io/micronaut/http/netty/AbstractNettyHttpRequest.java index 6d368acb75f..7bdc7a23c33 100644 --- a/http-netty/src/main/java/io/micronaut/http/netty/AbstractNettyHttpRequest.java +++ b/http-netty/src/main/java/io/micronaut/http/netty/AbstractNettyHttpRequest.java @@ -54,7 +54,7 @@ public abstract class AbstractNettyHttpRequest extends DefaultAttributeMap im public static final AsciiString STREAM_ID = HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(); public static final AsciiString HTTP2_SCHEME = HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(); protected final io.netty.handler.codec.http.HttpRequest nettyRequest; - protected final ConversionService conversionService; + protected final ConversionService conversionService; protected final HttpMethod httpMethod; protected final URI uri; protected final String httpMethodName; diff --git a/http-netty/src/main/java/io/micronaut/http/netty/NettyHttpHeaders.java b/http-netty/src/main/java/io/micronaut/http/netty/NettyHttpHeaders.java index 668ef5107f6..0a46c1ebb34 100644 --- a/http-netty/src/main/java/io/micronaut/http/netty/NettyHttpHeaders.java +++ b/http-netty/src/main/java/io/micronaut/http/netty/NettyHttpHeaders.java @@ -50,7 +50,7 @@ public class NettyHttpHeaders implements MutableHttpHeaders { io.netty.handler.codec.http.HttpHeaders nettyHeaders; - final ConversionService conversionService; + ConversionService conversionService; /** * @param nettyHeaders The Netty Http headers @@ -233,4 +233,13 @@ public MutableHttpHeaders contentType(MediaType mediaType) { return add(HttpHeaderNames.CONTENT_TYPE, mediaType); } + @Override + public ConversionService getConversionService() { + return conversionService; + } + + @Override + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } } diff --git a/http-netty/src/main/java/io/micronaut/http/netty/NettyHttpParameters.java b/http-netty/src/main/java/io/micronaut/http/netty/NettyHttpParameters.java index ccd4ee8cd64..07a811e2062 100644 --- a/http-netty/src/main/java/io/micronaut/http/netty/NettyHttpParameters.java +++ b/http-netty/src/main/java/io/micronaut/http/netty/NettyHttpParameters.java @@ -19,7 +19,6 @@ import io.micronaut.core.annotation.Nullable; import io.micronaut.core.convert.ArgumentConversionContext; import io.micronaut.core.convert.ConversionService; -import io.micronaut.core.convert.value.ConvertibleMultiValues; import io.micronaut.core.convert.value.ConvertibleMultiValuesMap; import io.micronaut.http.MutableHttpParameters; @@ -44,7 +43,7 @@ public class NettyHttpParameters implements MutableHttpParameters { private final LinkedHashMap> valuesMap; - private final ConvertibleMultiValues values; + private final ConvertibleMultiValuesMap values; private final BiConsumer> onChange; /** @@ -53,7 +52,7 @@ public class NettyHttpParameters implements MutableHttpParameters { * @param onChange A callback for changes */ public NettyHttpParameters(Map> parameters, - ConversionService conversionService, + ConversionService conversionService, @Nullable BiConsumer> onChange) { this.valuesMap = new LinkedHashMap<>(parameters.size()); this.values = new ConvertibleMultiValuesMap<>(valuesMap, conversionService); @@ -108,4 +107,14 @@ public MutableHttpParameters add(CharSequence name, List values) { } return this; } + + @Override + public ConversionService getConversionService() { + return values.getConversionService(); + } + + @Override + public void setConversionService(ConversionService conversionService) { + values.setConversionService(conversionService); + } } diff --git a/http-netty/src/main/java/io/micronaut/http/netty/channel/converters/KQueueChannelOptionFactory.java b/http-netty/src/main/java/io/micronaut/http/netty/channel/converters/KQueueChannelOptionFactory.java index 22fbf983444..7f4a00ff0e6 100644 --- a/http-netty/src/main/java/io/micronaut/http/netty/channel/converters/KQueueChannelOptionFactory.java +++ b/http-netty/src/main/java/io/micronaut/http/netty/channel/converters/KQueueChannelOptionFactory.java @@ -18,7 +18,7 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.context.env.Environment; import io.micronaut.core.annotation.Internal; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverterRegistrar; import io.micronaut.http.netty.channel.KQueueAvailabilityCondition; import io.netty.channel.ChannelOption; @@ -56,7 +56,7 @@ public Object convertValue(ChannelOption option, Object value, Environment en } @Override - public void register(ConversionService conversionService) { + public void register(MutableConversionService conversionService) { conversionService.addConverter( Map.class, AcceptFilter.class, diff --git a/http-netty/src/main/java/io/micronaut/http/netty/cookies/NettyCookies.java b/http-netty/src/main/java/io/micronaut/http/netty/cookies/NettyCookies.java index 7d0207c36a7..9b0364176e5 100644 --- a/http-netty/src/main/java/io/micronaut/http/netty/cookies/NettyCookies.java +++ b/http-netty/src/main/java/io/micronaut/http/netty/cookies/NettyCookies.java @@ -45,7 +45,7 @@ public class NettyCookies implements Cookies { private static final Logger LOG = LoggerFactory.getLogger(NettyCookies.class); - private final ConversionService conversionService; + private final ConversionService conversionService; private final Map cookies; /** @@ -132,4 +132,9 @@ public Optional get(CharSequence name, ArgumentConversionContext conve public Collection values() { return Collections.unmodifiableCollection(cookies.values()); } + + @Override + public ConversionService getConversionService() { + return conversionService; + } } diff --git a/http-netty/src/main/java/io/micronaut/http/netty/websocket/AbstractNettyWebSocketHandler.java b/http-netty/src/main/java/io/micronaut/http/netty/websocket/AbstractNettyWebSocketHandler.java index 405515fba9c..59a2a08d763 100644 --- a/http-netty/src/main/java/io/micronaut/http/netty/websocket/AbstractNettyWebSocketHandler.java +++ b/http-netty/src/main/java/io/micronaut/http/netty/websocket/AbstractNettyWebSocketHandler.java @@ -94,10 +94,11 @@ public abstract class AbstractNettyWebSocketHandler extends SimpleChannelInbound protected final WebSocketVersion webSocketVersion; protected final String subProtocol; protected final WebSocketSessionRepository webSocketSessionRepository; + protected final ConversionService conversionService; private final Argument bodyArgument; private final Argument pongArgument; private final AtomicBoolean closed = new AtomicBoolean(false); - private AtomicReference frameBuffer = new AtomicReference<>(); + private final AtomicReference frameBuffer = new AtomicReference<>(); /** * Default constructor. @@ -111,6 +112,7 @@ public abstract class AbstractNettyWebSocketHandler extends SimpleChannelInbound * @param version The websocket version being used * @param subProtocol The handler sub-protocol * @param webSocketSessionRepository The web socket repository if they are supported (like on the server), null otherwise + * @param conversionService The conversion service */ protected AbstractNettyWebSocketHandler( ChannelHandlerContext ctx, @@ -121,10 +123,11 @@ protected AbstractNettyWebSocketHandler( Map uriVariables, WebSocketVersion version, String subProtocol, - WebSocketSessionRepository webSocketSessionRepository) { + WebSocketSessionRepository webSocketSessionRepository, + ConversionService conversionService) { this.subProtocol = subProtocol; this.webSocketSessionRepository = webSocketSessionRepository; - this.webSocketBinder = new WebSocketStateBinderRegistry(binderRegistry); + this.webSocketBinder = new WebSocketStateBinderRegistry(binderRegistry, conversionService); this.uriVariables = uriVariables; this.webSocketBean = webSocketBean; this.originatingRequest = request; @@ -133,6 +136,7 @@ protected AbstractNettyWebSocketHandler( this.mediaTypeCodecRegistry = mediaTypeCodecRegistry; this.webSocketVersion = version; this.session = createWebSocketSession(ctx); + this.conversionService = conversionService; if (session != null) { @@ -402,7 +406,7 @@ protected void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame ms } Argument bodyArgument = this.getBodyArgument(); - Optional converted = ConversionService.SHARED.convert(content, bodyArgument); + Optional converted = conversionService.convert(content, bodyArgument); content.release(); if (!converted.isPresent()) { diff --git a/http-server-netty/build.gradle b/http-server-netty/build.gradle index a568bdf19f5..09cc6f9c129 100644 --- a/http-server-netty/build.gradle +++ b/http-server-netty/build.gradle @@ -42,11 +42,12 @@ dependencies { testImplementation libs.bcpkix } - testImplementation(libs.managed.micronaut.xml) { - exclude module:'micronaut-inject' - exclude module:'micronaut-http' - exclude module:'micronaut-bom' - } +// Add Micronaut Jackson XML after v4 Migration +// testImplementation(libs.managed.micronaut.xml) { +// exclude module:'micronaut-inject' +// exclude module:'micronaut-http' +// exclude module:'micronaut-bom' +// } testImplementation libs.managed.jackson.databind // http impls for tests diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/HttpPipelineBuilder.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/HttpPipelineBuilder.java index 22920c87aee..c8c75603475 100644 --- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/HttpPipelineBuilder.java +++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/HttpPipelineBuilder.java @@ -132,8 +132,8 @@ final class HttpPipelineBuilder { embeddedServices.getEventPublisher(HttpRequestReceivedEvent.class)); responseEncoder = new HttpResponseEncoder( embeddedServices.getMediaTypeCodecRegistry(), - server.getServerConfiguration() - ); + server.getServerConfiguration(), + embeddedServices.getApplicationContext().getConversionService()); } boolean supportsSsl() { diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyEmbeddedServerInstance.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyEmbeddedServerInstance.java index e2f4a0c1276..66b2963789e 100644 --- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyEmbeddedServerInstance.java +++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyEmbeddedServerInstance.java @@ -20,6 +20,7 @@ import io.micronaut.context.annotation.Prototype; import io.micronaut.context.env.Environment; import io.micronaut.core.annotation.Internal; +import io.micronaut.core.convert.ConversionService; import io.micronaut.core.convert.value.ConvertibleValues; import io.micronaut.core.util.CollectionUtils; import io.micronaut.discovery.EmbeddedServerInstance; @@ -48,6 +49,7 @@ class NettyEmbeddedServerInstance implements EmbeddedServerInstance { private final Environment environment; private final List metadataContributors; private final BeanLocator beanLocator; + private final ConversionService conversionService; private ConvertibleValues instanceMetadata; @@ -57,19 +59,21 @@ class NettyEmbeddedServerInstance implements EmbeddedServerInstance { * @param environment The Environment * @param beanLocator The bean locator * @param metadataContributors The {@link ServiceInstanceMetadataContributor} + * @param conversionService The conversion service */ NettyEmbeddedServerInstance( - @Parameter String id, - @Parameter NettyHttpServer nettyHttpServer, - Environment environment, - BeanLocator beanLocator, - List metadataContributors) { + @Parameter String id, + @Parameter NettyHttpServer nettyHttpServer, + Environment environment, + BeanLocator beanLocator, + List metadataContributors, ConversionService conversionService) { this.id = id; this.nettyHttpServer = nettyHttpServer; this.environment = environment; this.beanLocator = beanLocator; this.metadataContributors = metadataContributors; + this.conversionService = conversionService; } @Override @@ -108,7 +112,7 @@ public ConvertibleValues getMetadata() { if (cloudMetadata != null) { cloudMetadata.putAll(metadata); } - instanceMetadata = ConvertibleValues.of(cloudMetadata); + instanceMetadata = ConvertibleValues.of(cloudMetadata, conversionService); } return instanceMetadata; } diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpRequest.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpRequest.java index fe7def6f3dd..f2b245a75cc 100644 --- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpRequest.java +++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpRequest.java @@ -627,9 +627,18 @@ public Optional convert(Argument valueType, Object value) { private class NettyMutableHttpRequest implements MutableHttpRequest, NettyHttpRequestBuilder { private URI uri = NettyHttpRequest.this.uri; + @Nullable private MutableHttpParameters httpParameters; + @Nullable private Object body; + @Override + public void setConversionService(ConversionService conversionService) { + if (httpParameters != null) { + httpParameters.setConversionService(conversionService); + } + } + @Override public MutableHttpRequest cookie(Cookie cookie) { if (cookie instanceof NettyCookie) { diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpServer.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpServer.java index 21dd006bdf5..7a31e8a0aa8 100644 --- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpServer.java +++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyHttpServer.java @@ -176,7 +176,8 @@ public NettyHttpServer( nettyEmbeddedServices, ioExecutor, httpContentProcessorResolver, - httpRequestTerminatedEventPublisher + httpRequestTerminatedEventPublisher, + applicationContext.getConversionService() ); this.hostResolver = new DefaultHttpHostResolver(serverConfiguration, () -> NettyHttpServer.this); diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/RoutingInBoundHandler.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/RoutingInBoundHandler.java index 1aa25b728bb..42871f0a081 100644 --- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/RoutingInBoundHandler.java +++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/RoutingInBoundHandler.java @@ -158,6 +158,7 @@ class RoutingInBoundHandler extends SimpleChannelInboundHandler terminateEventPublisher; private final RouteExecutor routeExecutor; + private final ConversionService conversionService; /** * @param customizableResponseTypeHandlerRegistry The customizable response type handler registry @@ -166,6 +167,7 @@ class RoutingInBoundHandler extends SimpleChannelInboundHandler ioExecutor, HttpContentProcessorResolver httpContentProcessorResolver, - ApplicationEventPublisher terminateEventPublisher) { + ApplicationEventPublisher terminateEventPublisher, + ConversionService conversionService) { this.mediaTypeCodecRegistry = embeddedServerContext.getMediaTypeCodecRegistry(); this.customizableResponseTypeHandlerRegistry = customizableResponseTypeHandlerRegistry; this.staticResourceResolver = embeddedServerContext.getStaticResourceResolver(); @@ -186,6 +189,7 @@ class RoutingInBoundHandler extends SimpleChannelInboundHandler multipartEnabled = serverConfiguration.getMultipart().getEnabled(); this.multipartEnabled = !multipartEnabled.isPresent() || multipartEnabled.get(); this.routeExecutor = embeddedServerContext.getRouteExecutor(); + this.conversionService = conversionService; } @Override @@ -395,7 +399,6 @@ private Subscriber buildSubscriber(NettyHttpRequest request, final ConcurrentHashMap> subjectsByDataName = new ConcurrentHashMap<>(); final Collection> downstreamSubscribers = Collections.synchronizedList(new ArrayList<>()); final ConcurrentHashMap dataReferences = new ConcurrentHashMap<>(); - final ConversionService conversionService = ConversionService.SHARED; Subscription s; final LongConsumer onRequest = num -> pressureRequested.updateAndGet(p -> { long newVal = p - num; @@ -832,7 +835,7 @@ private Flux mapToHttpContent(NettyHttpRequest request, } else { MediaTypeCodec codec = mediaTypeCodecRegistry.findCodec(finalMediaType, message.getClass()).orElse( - new TextPlainCodec(serverConfiguration.getDefaultCharset())); + new TextPlainCodec(serverConfiguration.getDefaultCharset(), conversionService)); if (LOG.isTraceEnabled()) { LOG.trace("Encoding emitted response object [{}] using codec: {}", message, codec); @@ -931,7 +934,7 @@ private void encodeResponseBody( MediaTypeCodec codec = registeredCodec.get(); encodeBodyWithCodec(message, bodyType, body, codec, context, request); } else { - MediaTypeCodec defaultCodec = new TextPlainCodec(serverConfiguration.getDefaultCharset()); + MediaTypeCodec defaultCodec = new TextPlainCodec(serverConfiguration.getDefaultCharset(), conversionService); encodeBodyWithCodec(message, bodyType, body, defaultCodec, context, request); } } @@ -1091,10 +1094,10 @@ private NettyMutableHttpResponse createNettyResponse(HttpResponse message) io.netty.handler.codec.http.HttpHeaders nettyHeaders = new DefaultHttpHeaders(serverConfiguration.isValidateHeaders()); message.getHeaders().forEach((BiConsumer>) nettyHeaders::set); return new NettyMutableHttpResponse<>( - HttpVersion.HTTP_1_1, - HttpResponseStatus.valueOf(message.code(), message.reason()), - body instanceof ByteBuf ? body : null, - ConversionService.SHARED + HttpVersion.HTTP_1_1, + HttpResponseStatus.valueOf(message.code(), message.reason()), + body instanceof ByteBuf ? body : null, + conversionService ); } diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/NettyBinderRegistrar.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/NettyBinderRegistrar.java index d24acc1e0e0..07374ae7b8f 100644 --- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/NettyBinderRegistrar.java +++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/binders/NettyBinderRegistrar.java @@ -20,7 +20,6 @@ import io.micronaut.context.event.BeanCreatedEvent; import io.micronaut.context.event.BeanCreatedEventListener; import io.micronaut.core.annotation.Internal; -import io.micronaut.core.annotation.Nullable; import io.micronaut.core.convert.ConversionService; import io.micronaut.http.bind.RequestBinderRegistry; import io.micronaut.http.server.HttpServerConfiguration; @@ -42,7 +41,7 @@ @Internal class NettyBinderRegistrar implements BeanCreatedEventListener { - private final ConversionService conversionService; + private final ConversionService conversionService; private final HttpContentProcessorResolver httpContentProcessorResolver; private final BeanLocator beanLocator; private final BeanProvider httpServerConfiguration; @@ -57,13 +56,12 @@ class NettyBinderRegistrar implements BeanCreatedEventListener conversionService, - HttpContentProcessorResolver httpContentProcessorResolver, - BeanLocator beanLocator, - BeanProvider httpServerConfiguration, - @Named(TaskExecutors.IO) BeanProvider executorService) { - this.conversionService = conversionService == null ? ConversionService.SHARED : conversionService; + NettyBinderRegistrar(ConversionService conversionService, + HttpContentProcessorResolver httpContentProcessorResolver, + BeanLocator beanLocator, + BeanProvider httpServerConfiguration, + @Named(TaskExecutors.IO) BeanProvider executorService) { + this.conversionService = conversionService; this.httpContentProcessorResolver = httpContentProcessorResolver; this.beanLocator = beanLocator; this.httpServerConfiguration = httpServerConfiguration; diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/converters/NettyConverters.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/converters/NettyConverters.java index 78e0de6ccf3..74c9f9f7700 100644 --- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/converters/NettyConverters.java +++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/converters/NettyConverters.java @@ -18,6 +18,7 @@ import io.micronaut.context.BeanProvider; import io.micronaut.core.annotation.Internal; import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverter; import io.micronaut.core.convert.TypeConverterRegistrar; import io.micronaut.core.naming.NameUtils; @@ -50,7 +51,7 @@ @Internal public class NettyConverters implements TypeConverterRegistrar { - private final ConversionService conversionService; + private final ConversionService conversionService; private final BeanProvider decoderRegistryProvider; private final ChannelOptionFactory channelOptionFactory; @@ -60,7 +61,7 @@ public class NettyConverters implements TypeConverterRegistrar { * @param decoderRegistryProvider The decoder registry provider * @param channelOptionFactory The decoder channel option factory */ - public NettyConverters(ConversionService conversionService, + public NettyConverters(ConversionService conversionService, //Prevent early initialization of the codecs BeanProvider decoderRegistryProvider, ChannelOptionFactory channelOptionFactory) { @@ -70,7 +71,7 @@ public NettyConverters(ConversionService conversionService, } @Override - public void register(ConversionService conversionService) { + public void register(MutableConversionService conversionService) { conversionService.addConverter( CharSequence.class, ChannelOption.class, diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/converters/NettyConvertersSpi.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/converters/NettyConvertersSpi.java index 683d0811a6b..3c9ec4e1161 100644 --- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/converters/NettyConvertersSpi.java +++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/converters/NettyConvertersSpi.java @@ -16,7 +16,7 @@ package io.micronaut.http.server.netty.converters; import io.micronaut.core.annotation.Internal; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverter; import io.micronaut.core.convert.TypeConverterRegistrar; import io.micronaut.http.multipart.CompletedFileUpload; @@ -47,7 +47,7 @@ @Internal public final class NettyConvertersSpi implements TypeConverterRegistrar { @Override - public void register(ConversionService conversionService) { + public void register(MutableConversionService conversionService) { conversionService.addConverter( ByteBuf.class, CharSequence.class, diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/decoders/HttpRequestDecoder.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/decoders/HttpRequestDecoder.java index 3d3fb12b6ef..c86816d4ce5 100644 --- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/decoders/HttpRequestDecoder.java +++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/decoders/HttpRequestDecoder.java @@ -54,7 +54,7 @@ public class HttpRequestDecoder extends MessageToMessageDecoder imp private static final Logger LOG = LoggerFactory.getLogger(NettyHttpServer.class); private final EmbeddedServer embeddedServer; - private final ConversionService conversionService; + private final ConversionService conversionService; private final HttpServerConfiguration configuration; private final ApplicationEventPublisher httpRequestReceivedEventPublisher; @@ -64,7 +64,7 @@ public class HttpRequestDecoder extends MessageToMessageDecoder imp * @param configuration The Http server configuration * @param httpRequestReceivedEventPublisher The publisher of {@link HttpRequestReceivedEvent} */ - public HttpRequestDecoder(EmbeddedServer embeddedServer, ConversionService conversionService, HttpServerConfiguration configuration, ApplicationEventPublisher httpRequestReceivedEventPublisher) { + public HttpRequestDecoder(EmbeddedServer embeddedServer, ConversionService conversionService, HttpServerConfiguration configuration, ApplicationEventPublisher httpRequestReceivedEventPublisher) { this.embeddedServer = embeddedServer; this.conversionService = conversionService; this.configuration = configuration; diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/encoders/HttpResponseEncoder.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/encoders/HttpResponseEncoder.java index 7110153926d..16fea285227 100644 --- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/encoders/HttpResponseEncoder.java +++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/encoders/HttpResponseEncoder.java @@ -17,6 +17,7 @@ import io.micronaut.buffer.netty.NettyByteBufferFactory; import io.micronaut.core.annotation.Internal; +import io.micronaut.core.convert.ConversionService; import io.micronaut.core.io.Writable; import io.micronaut.core.io.buffer.ByteBuffer; import io.micronaut.http.HttpHeaders; @@ -64,16 +65,19 @@ public class HttpResponseEncoder extends MessageToMessageEncoder resp response = encodeBodyWithCodec(response, body, codec, responseMediaType, context); } - MediaTypeCodec defaultCodec = new TextPlainCodec(serverConfiguration.getDefaultCharset()); + MediaTypeCodec defaultCodec = new TextPlainCodec(serverConfiguration.getDefaultCharset(), conversionService); response = encodeBodyWithCodec(response, body, defaultCodec, responseMediaType, context); } diff --git a/http-server-netty/src/main/java/io/micronaut/http/server/netty/websocket/NettyServerWebSocketHandler.java b/http-server-netty/src/main/java/io/micronaut/http/server/netty/websocket/NettyServerWebSocketHandler.java index 2f40262ab9f..31963261aeb 100644 --- a/http-server-netty/src/main/java/io/micronaut/http/server/netty/websocket/NettyServerWebSocketHandler.java +++ b/http-server-netty/src/main/java/io/micronaut/http/server/netty/websocket/NettyServerWebSocketHandler.java @@ -111,7 +111,8 @@ public class NettyServerWebSocketHandler extends AbstractNettyWebSocketHandler { routeMatch.getVariableValues(), handshaker.version(), handshaker.selectedSubprotocol(), - webSocketSessionRepository); + webSocketSessionRepository, + nettyEmbeddedServices.getApplicationContext().getConversionService()); this.nettyEmbeddedServices = nettyEmbeddedServices; this.coroutineHelper = coroutineHelper; diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/ContentNegotiationSpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/ContentNegotiationSpec.groovy index 7fa8e5f935a..13c03141258 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/ContentNegotiationSpec.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/ContentNegotiationSpec.groovy @@ -45,8 +45,9 @@ class ContentNegotiationSpec extends Specification { [new MediaType("application/json;q=0.5"), new MediaType("application/xml;q=0.9")] | XML [MediaType.APPLICATION_JSON_TYPE] | JSON [MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE] | JSON - [MediaType.APPLICATION_XML_TYPE, MediaType.APPLICATION_JSON_TYPE] | XML - [MediaType.APPLICATION_XML_TYPE] | XML +// Add Micronaut Jackson XML after v4 Migration +// [MediaType.APPLICATION_XML_TYPE, MediaType.APPLICATION_JSON_TYPE] | XML +// [MediaType.APPLICATION_XML_TYPE] | XML [MediaType.TEXT_PLAIN_TYPE] | TEXT [MediaType.ALL_TYPE] | JSON @@ -72,7 +73,8 @@ class ContentNegotiationSpec extends Specification { contentType | expectedContentType | expectedBody null | MediaType.APPLICATION_JSON_TYPE | '{"name":"Fred","age":10}' MediaType.APPLICATION_JSON_TYPE | MediaType.APPLICATION_JSON_TYPE | '{"name":"Fred","age":10}' - MediaType.APPLICATION_XML_TYPE | MediaType.APPLICATION_XML_TYPE | 'Fred10' +// Add Micronaut Jackson XML after v4 Migration +// MediaType.APPLICATION_XML_TYPE | MediaType.APPLICATION_XML_TYPE | 'Fred10' } void "test send unacceptable type"() { diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/NettyHttpRequestSpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/NettyHttpRequestSpec.groovy index b211faf817e..ccaf8fc33a9 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/NettyHttpRequestSpec.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/NettyHttpRequestSpec.groovy @@ -15,7 +15,7 @@ */ package io.micronaut.http.server.netty.binding -import io.micronaut.core.convert.DefaultConversionService +import io.micronaut.core.convert.DefaultMutableConversionService import io.micronaut.http.HttpHeaders import io.micronaut.http.HttpMethod import io.micronaut.http.MutableHttpRequest @@ -37,7 +37,7 @@ class NettyHttpRequestSpec extends Specification { void "test mutate request"() { given: DefaultFullHttpRequest nettyRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, GET, "/foo/bar") - NettyHttpRequest request = new NettyHttpRequest(nettyRequest, Mock(ChannelHandlerContext), new DefaultConversionService(), new HttpServerConfiguration()) + NettyHttpRequest request = new NettyHttpRequest(nettyRequest, Mock(ChannelHandlerContext), new DefaultMutableConversionService(), new HttpServerConfiguration()) when: def altered = request.mutate() @@ -52,7 +52,7 @@ class NettyHttpRequestSpec extends Specification { void "test mutating a mutable request"() { given: DefaultFullHttpRequest nettyRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, GET, "/foo/bar") - def request = new NettyHttpRequest(nettyRequest, Mock(ChannelHandlerContext), new DefaultConversionService(), new HttpServerConfiguration()) + def request = new NettyHttpRequest(nettyRequest, Mock(ChannelHandlerContext), new DefaultMutableConversionService(), new HttpServerConfiguration()) when: request = request.mutate() @@ -70,7 +70,7 @@ class NettyHttpRequestSpec extends Specification { void "test netty http request parameters"() { given: DefaultFullHttpRequest nettyRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, method, uri) - NettyHttpRequest request = new NettyHttpRequest(nettyRequest, Mock(ChannelHandlerContext), new DefaultConversionService(), new HttpServerConfiguration()) + NettyHttpRequest request = new NettyHttpRequest(nettyRequest, Mock(ChannelHandlerContext), new DefaultMutableConversionService(), new HttpServerConfiguration()) String fullURI = request.uri.toString() String expectedPath = fullURI.indexOf('?') > -1 ? fullURI.substring(0, fullURI.indexOf('?')) : fullURI @@ -93,7 +93,7 @@ class NettyHttpRequestSpec extends Specification { nettyRequest.headers().add(header.key.toString(), header.value) } - NettyHttpRequest request = new NettyHttpRequest(nettyRequest, Mock(ChannelHandlerContext), new DefaultConversionService(), new HttpServerConfiguration()) + NettyHttpRequest request = new NettyHttpRequest(nettyRequest, Mock(ChannelHandlerContext), new DefaultMutableConversionService(), new HttpServerConfiguration()) String fullURI = request.uri.toString() String expectedPath = fullURI.indexOf('?') > -1 ? fullURI.substring(0, fullURI.indexOf('?')) : fullURI @@ -115,7 +115,7 @@ class NettyHttpRequestSpec extends Specification { nettyRequest.headers().add(header.key.toString(), header.value) } - NettyHttpRequest request = new NettyHttpRequest(nettyRequest, Mock(ChannelHandlerContext), new DefaultConversionService(), new HttpServerConfiguration()) + NettyHttpRequest request = new NettyHttpRequest(nettyRequest, Mock(ChannelHandlerContext), new DefaultMutableConversionService(), new HttpServerConfiguration()) String fullURI = request.uri.toString() String expectedPath = fullURI.indexOf('?') > -1 ? fullURI.substring(0, fullURI.indexOf('?')) : fullURI @@ -140,7 +140,7 @@ class NettyHttpRequestSpec extends Specification { nettyRequest.headers().add(header.key.toString(), header.value) } - NettyHttpRequest request = new NettyHttpRequest(nettyRequest, Mock(ChannelHandlerContext), new DefaultConversionService(), new HttpServerConfiguration()) + NettyHttpRequest request = new NettyHttpRequest(nettyRequest, Mock(ChannelHandlerContext), new DefaultMutableConversionService(), new HttpServerConfiguration()) String fullURI = request.uri.toString() String expectedPath = fullURI.indexOf('?') > -1 ? fullURI.substring(0, fullURI.indexOf('?')) : fullURI diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/NettyHttpResponseSpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/NettyHttpResponseSpec.groovy index fa128a5454f..8405902a017 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/NettyHttpResponseSpec.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/binding/NettyHttpResponseSpec.groovy @@ -15,7 +15,7 @@ */ package io.micronaut.http.server.netty.binding -import io.micronaut.core.convert.DefaultConversionService +import io.micronaut.core.convert.DefaultMutableConversionService import io.micronaut.http.HttpHeaders import io.micronaut.http.HttpStatus import io.micronaut.http.MutableHttpResponse @@ -37,7 +37,7 @@ class NettyHttpResponseSpec extends Specification { void "test add headers"() { given: DefaultFullHttpResponse nettyResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK) - NettyMutableHttpResponse response = new NettyMutableHttpResponse(nettyResponse, new DefaultConversionService()) + NettyMutableHttpResponse response = new NettyMutableHttpResponse(nettyResponse, new DefaultMutableConversionService()) response.status(HttpStatus."$status") response.headers.add(header, value) @@ -54,7 +54,7 @@ class NettyHttpResponseSpec extends Specification { void "test add simple cookie"() { given: DefaultFullHttpResponse nettyResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK) - MutableHttpResponse response = new NettyMutableHttpResponse(nettyResponse, new DefaultConversionService()) + MutableHttpResponse response = new NettyMutableHttpResponse(nettyResponse, new DefaultMutableConversionService()) response.status(HttpStatus."$status") response.cookie(Cookie.of("foo", "bar")) @@ -71,7 +71,7 @@ class NettyHttpResponseSpec extends Specification { void "test add cookie with max age"() { given: DefaultFullHttpResponse nettyResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK) - MutableHttpResponse response = new NettyMutableHttpResponse(nettyResponse, new DefaultConversionService()) + MutableHttpResponse response = new NettyMutableHttpResponse(nettyResponse, new DefaultMutableConversionService()) response.status(HttpStatus."$status") response.cookie(Cookie.of("foo", "bar").maxAge(Duration.ofHours(2))) @@ -89,7 +89,7 @@ class NettyHttpResponseSpec extends Specification { void "test multiple cookies"() { given: DefaultFullHttpResponse nettyResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK) - MutableHttpResponse response = new NettyMutableHttpResponse(nettyResponse, new DefaultConversionService()) + MutableHttpResponse response = new NettyMutableHttpResponse(nettyResponse, new DefaultMutableConversionService()) response.cookies([Cookie.of("a", "b"), Cookie.of("c", "d")] as Set) diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/converters/ConverterRegistrySpec.groovy b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/converters/ConverterRegistrySpec.groovy index 3bf9ecfd59a..d0fc17f93d0 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/converters/ConverterRegistrySpec.groovy +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/converters/ConverterRegistrySpec.groovy @@ -21,7 +21,6 @@ import io.netty.buffer.ByteBuf import io.netty.buffer.CompositeByteBuf import io.netty.buffer.Unpooled import io.netty.channel.ChannelOption -import spock.lang.PendingFeature import spock.lang.Specification import java.nio.charset.StandardCharsets @@ -67,7 +66,6 @@ class ConverterRegistrySpec extends Specification { ctx1.close() } - @PendingFeature def "test convert string to channel option after context reset"() { given: ApplicationContext ctx1 = ApplicationContext.run() @@ -82,7 +80,6 @@ class ConverterRegistrySpec extends Specification { ctx2.stop() then: - // this fails, the converter has been removed by ctx2.stop() ctx1.getBean(ConversionService).convert("AUTO_READ", ChannelOption).get() == ChannelOption.AUTO_READ cleanup: diff --git a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/util/MockHttpHeaders.java b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/util/MockHttpHeaders.java index 02566db55e9..bade4c95f11 100644 --- a/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/util/MockHttpHeaders.java +++ b/http-server-netty/src/test/groovy/io/micronaut/http/server/netty/util/MockHttpHeaders.java @@ -32,6 +32,7 @@ public class MockHttpHeaders implements MutableHttpHeaders { private final Map> headers; + private ConversionService conversionService = ConversionService.SHARED; public MockHttpHeaders(Map> headers) { this.headers = headers; @@ -88,6 +89,11 @@ public Collection> values() { @Override public Optional get(CharSequence name, ArgumentConversionContext conversionContext) { - return ConversionService.SHARED.convert(get(name), conversionContext); + return conversionService.convert(get(name), conversionContext); + } + + @Override + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; } } diff --git a/http-server/src/main/java/io/micronaut/http/server/websocket/ServerWebSocketProcessor.java b/http-server/src/main/java/io/micronaut/http/server/websocket/ServerWebSocketProcessor.java index 53aa4cb94fa..913fc9c1182 100644 --- a/http-server/src/main/java/io/micronaut/http/server/websocket/ServerWebSocketProcessor.java +++ b/http-server/src/main/java/io/micronaut/http/server/websocket/ServerWebSocketProcessor.java @@ -50,7 +50,7 @@ public class ServerWebSocketProcessor extends DefaultRouteBuilder implements Exe * @param uriNamingStrategy The {@link io.micronaut.web.router.RouteBuilder.UriNamingStrategy} * @param conversionService The {@link ConversionService} */ - ServerWebSocketProcessor(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy, ConversionService conversionService) { + ServerWebSocketProcessor(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy, ConversionService conversionService) { super(executionHandleLocator, uriNamingStrategy, conversionService); } diff --git a/http-server/src/test/groovy/io/micronaut/http/server/util/MockHttpHeaders.java b/http-server/src/test/groovy/io/micronaut/http/server/util/MockHttpHeaders.java index cb395d5a189..3942ae6f3d8 100644 --- a/http-server/src/test/groovy/io/micronaut/http/server/util/MockHttpHeaders.java +++ b/http-server/src/test/groovy/io/micronaut/http/server/util/MockHttpHeaders.java @@ -32,6 +32,7 @@ public class MockHttpHeaders implements MutableHttpHeaders { private final Map> headers; + private ConversionService conversionService = ConversionService.SHARED; public MockHttpHeaders(Map> headers) { this.headers = headers; @@ -90,4 +91,9 @@ public Collection> values() { public Optional get(CharSequence name, ArgumentConversionContext conversionContext) { return ConversionService.SHARED.convert(get(name), conversionContext); } + + @Override + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } } diff --git a/http-validation/src/main/java/io/micronaut/validation/routes/RouteValidationVisitor.java b/http-validation/src/main/java/io/micronaut/validation/routes/RouteValidationVisitor.java index 4256cb7719f..9c188bf7afd 100644 --- a/http-validation/src/main/java/io/micronaut/validation/routes/RouteValidationVisitor.java +++ b/http-validation/src/main/java/io/micronaut/validation/routes/RouteValidationVisitor.java @@ -21,13 +21,17 @@ import io.micronaut.context.env.DefaultPropertyPlaceholderResolver.Segment; import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.NonNull; -import io.micronaut.core.convert.DefaultConversionService; +import io.micronaut.core.convert.ConversionService; import io.micronaut.core.util.CollectionUtils; import io.micronaut.http.uri.UriMatchTemplate; import io.micronaut.inject.ast.MethodElement; import io.micronaut.inject.visitor.TypeElementVisitor; import io.micronaut.inject.visitor.VisitorContext; -import io.micronaut.validation.routes.rules.*; +import io.micronaut.validation.routes.rules.ClientTypesRule; +import io.micronaut.validation.routes.rules.MissingParameterRule; +import io.micronaut.validation.routes.rules.NullableParameterRule; +import io.micronaut.validation.routes.rules.RequestBeanParameterRule; +import io.micronaut.validation.routes.rules.RouteValidationRule; import javax.annotation.processing.SupportedOptions; import java.util.ArrayList; @@ -51,7 +55,7 @@ public class RouteValidationVisitor implements TypeElementVisitor rules = new ArrayList<>(); private boolean skipValidation = false; - private final DefaultPropertyPlaceholderResolver resolver = new DefaultPropertyPlaceholderResolver(null, new DefaultConversionService()); + private final DefaultPropertyPlaceholderResolver resolver = new DefaultPropertyPlaceholderResolver(null, ConversionService.SHARED); @NonNull @Override diff --git a/http/src/main/java/io/micronaut/http/MediaTypeConvertersRegistrar.java b/http/src/main/java/io/micronaut/http/MediaTypeConvertersRegistrar.java index 5d4ed613bef..5dbd5fc0305 100644 --- a/http/src/main/java/io/micronaut/http/MediaTypeConvertersRegistrar.java +++ b/http/src/main/java/io/micronaut/http/MediaTypeConvertersRegistrar.java @@ -16,10 +16,12 @@ package io.micronaut.http; import io.micronaut.core.annotation.Internal; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverterRegistrar; import io.micronaut.core.util.StringUtils; +import java.util.Optional; + /** * The media type converters registrar. * @@ -30,13 +32,18 @@ public final class MediaTypeConvertersRegistrar implements TypeConverterRegistrar { @Override - public void register(ConversionService conversionService) { - conversionService.addConverter(CharSequence.class, MediaType.class, charSequence -> { - if (StringUtils.isNotEmpty(charSequence)) { - return MediaType.of(charSequence.toString()); + public void register(MutableConversionService conversionService) { + conversionService.addConverter(CharSequence.class, MediaType.class, (object, targetType, context) -> { + if (StringUtils.isEmpty(object)) { + return Optional.empty(); + } else { + try { + return Optional.of(MediaType.of(object.toString())); + } catch (IllegalArgumentException e) { + context.reject(e); + return Optional.empty(); } - return null; } - ); + }); } } diff --git a/http/src/main/java/io/micronaut/http/MutableHttpHeaders.java b/http/src/main/java/io/micronaut/http/MutableHttpHeaders.java index 6181984c451..3252d303ca5 100644 --- a/http/src/main/java/io/micronaut/http/MutableHttpHeaders.java +++ b/http/src/main/java/io/micronaut/http/MutableHttpHeaders.java @@ -15,6 +15,7 @@ */ package io.micronaut.http; +import io.micronaut.core.convert.ConversionServiceAware; import io.micronaut.core.type.MutableHeaders; import java.net.URI; @@ -35,7 +36,7 @@ * @author Graeme Rocher * @since 1.0 */ -public interface MutableHttpHeaders extends MutableHeaders, HttpHeaders { +public interface MutableHttpHeaders extends MutableHeaders, HttpHeaders, ConversionServiceAware { /** * The default GMT zone for date values. diff --git a/http/src/main/java/io/micronaut/http/MutableHttpParameters.java b/http/src/main/java/io/micronaut/http/MutableHttpParameters.java index 240e87c67c7..4960b76d730 100644 --- a/http/src/main/java/io/micronaut/http/MutableHttpParameters.java +++ b/http/src/main/java/io/micronaut/http/MutableHttpParameters.java @@ -15,6 +15,8 @@ */ package io.micronaut.http; +import io.micronaut.core.convert.ConversionServiceAware; + import java.util.Collections; import java.util.List; @@ -24,7 +26,7 @@ * @author Vladimir Orany * @since 1.0 */ -public interface MutableHttpParameters extends HttpParameters { +public interface MutableHttpParameters extends HttpParameters, ConversionServiceAware { /** * Adds a new http parameter. diff --git a/http/src/main/java/io/micronaut/http/MutableHttpRequest.java b/http/src/main/java/io/micronaut/http/MutableHttpRequest.java index 553a495dc52..96e9ae0f554 100644 --- a/http/src/main/java/io/micronaut/http/MutableHttpRequest.java +++ b/http/src/main/java/io/micronaut/http/MutableHttpRequest.java @@ -16,6 +16,7 @@ package io.micronaut.http; import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.convert.ConversionServiceAware; import io.micronaut.core.util.ArrayUtils; import io.micronaut.http.cookie.Cookie; import io.micronaut.http.uri.UriBuilder; @@ -33,7 +34,7 @@ * @author Graeme Rocher * @since 1.0 */ -public interface MutableHttpRequest extends HttpRequest, MutableHttpMessage { +public interface MutableHttpRequest extends HttpRequest, MutableHttpMessage, ConversionServiceAware { /** * Sets the specified cookie on the request. diff --git a/http/src/main/java/io/micronaut/http/bind/DefaultRequestBinderRegistry.java b/http/src/main/java/io/micronaut/http/bind/DefaultRequestBinderRegistry.java index 7c8a6f34fb6..a38e8402512 100644 --- a/http/src/main/java/io/micronaut/http/bind/DefaultRequestBinderRegistry.java +++ b/http/src/main/java/io/micronaut/http/bind/DefaultRequestBinderRegistry.java @@ -23,16 +23,36 @@ import io.micronaut.core.type.Argument; import io.micronaut.core.util.CollectionUtils; import io.micronaut.core.util.clhm.ConcurrentLinkedHashMap; -import io.micronaut.core.util.StringUtils; -import io.micronaut.http.*; +import io.micronaut.http.FullHttpRequest; +import io.micronaut.http.HttpHeaders; +import io.micronaut.http.HttpMethod; +import io.micronaut.http.HttpParameters; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.PushCapableHttpRequest; import io.micronaut.http.annotation.Body; -import io.micronaut.http.bind.binders.*; +import io.micronaut.http.bind.binders.AnnotatedRequestArgumentBinder; +import io.micronaut.http.bind.binders.ContinuationArgumentBinder; +import io.micronaut.http.bind.binders.CookieAnnotationBinder; +import io.micronaut.http.bind.binders.DefaultBodyAnnotationBinder; +import io.micronaut.http.bind.binders.HeaderAnnotationBinder; +import io.micronaut.http.bind.binders.ParameterAnnotationBinder; +import io.micronaut.http.bind.binders.PartAnnotationBinder; +import io.micronaut.http.bind.binders.PathVariableAnnotationBinder; +import io.micronaut.http.bind.binders.RequestArgumentBinder; +import io.micronaut.http.bind.binders.RequestAttributeAnnotationBinder; +import io.micronaut.http.bind.binders.RequestBeanAnnotationBinder; +import io.micronaut.http.bind.binders.TypedRequestArgumentBinder; import io.micronaut.http.cookie.Cookie; import io.micronaut.http.cookie.Cookies; import jakarta.inject.Inject; import jakarta.inject.Singleton; + import java.lang.annotation.Annotation; -import java.util.*; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; import static io.micronaut.core.util.KotlinUtils.KOTLIN_COROUTINES_SUPPORTED; @@ -50,7 +70,7 @@ public class DefaultRequestBinderRegistry implements RequestBinderRegistry { private final Map, RequestArgumentBinder> byAnnotation = new LinkedHashMap<>(); private final Map byTypeAndAnnotation = new LinkedHashMap<>(); private final Map byType = new LinkedHashMap<>(); - private final ConversionService conversionService; + private final ConversionService conversionService; private final Map> argumentBinderCache = new ConcurrentLinkedHashMap.Builder>().maximumWeightedCapacity(CACHE_MAX_SIZE).build(); @@ -66,7 +86,8 @@ public DefaultRequestBinderRegistry(ConversionService conversionService, Request * @param conversionService The conversion service * @param binders The request argument binders */ - @Inject public DefaultRequestBinderRegistry(ConversionService conversionService, List binders) { + @Inject + public DefaultRequestBinderRegistry(ConversionService conversionService, List binders) { this.conversionService = conversionService; if (CollectionUtils.isNotEmpty(binders)) { @@ -75,7 +96,6 @@ public DefaultRequestBinderRegistry(ConversionService conversionService, Request } } - registerDefaultConverters(conversionService); registerDefaultAnnotationBinders(byAnnotation); byType.put(Argument.of(HttpHeaders.class).typeHashCode(), (RequestArgumentBinder) (argument, source) -> () -> Optional.of(source.getHeaders())); @@ -213,30 +233,6 @@ protected RequestArgumentBinder findBinder(Argument argument, Class conversionService) { - conversionService.addConverter( - CharSequence.class, - MediaType.class, (object, targetType, context) -> { - if (StringUtils.isEmpty(object)) { - return Optional.empty(); - } else { - final String str = object.toString(); - try { - return Optional.of(MediaType.of(str)); - } catch (IllegalArgumentException e) { - context.reject(e); - return Optional.empty(); - } - } - }); - - } - /** * @param byAnnotation The request argument binder */ diff --git a/http/src/main/java/io/micronaut/http/bind/binders/CookieAnnotationBinder.java b/http/src/main/java/io/micronaut/http/bind/binders/CookieAnnotationBinder.java index 0c7dc2f8964..0f02c97d895 100644 --- a/http/src/main/java/io/micronaut/http/bind/binders/CookieAnnotationBinder.java +++ b/http/src/main/java/io/micronaut/http/bind/binders/CookieAnnotationBinder.java @@ -38,7 +38,7 @@ public class CookieAnnotationBinder extends AbstractAnnotatedArgumentBinder conversionService) { + public CookieAnnotationBinder(ConversionService conversionService) { super(conversionService); } diff --git a/http/src/main/java/io/micronaut/http/bind/binders/DefaultBodyAnnotationBinder.java b/http/src/main/java/io/micronaut/http/bind/binders/DefaultBodyAnnotationBinder.java index 62bd82c555d..244b6038aee 100644 --- a/http/src/main/java/io/micronaut/http/bind/binders/DefaultBodyAnnotationBinder.java +++ b/http/src/main/java/io/micronaut/http/bind/binders/DefaultBodyAnnotationBinder.java @@ -36,7 +36,7 @@ */ public class DefaultBodyAnnotationBinder implements BodyArgumentBinder { - protected final ConversionService conversionService; + protected final ConversionService conversionService; /** * @param conversionService The conversion service diff --git a/http/src/main/java/io/micronaut/http/bind/binders/HeaderAnnotationBinder.java b/http/src/main/java/io/micronaut/http/bind/binders/HeaderAnnotationBinder.java index 4d685b50634..2572c350d97 100644 --- a/http/src/main/java/io/micronaut/http/bind/binders/HeaderAnnotationBinder.java +++ b/http/src/main/java/io/micronaut/http/bind/binders/HeaderAnnotationBinder.java @@ -39,7 +39,7 @@ public class HeaderAnnotationBinder extends AbstractAnnotatedArgumentBinder conversionService) { + public HeaderAnnotationBinder(ConversionService conversionService) { super(conversionService); } diff --git a/http/src/main/java/io/micronaut/http/bind/binders/ParameterAnnotationBinder.java b/http/src/main/java/io/micronaut/http/bind/binders/ParameterAnnotationBinder.java index 7184705242c..77f9d9e1def 100644 --- a/http/src/main/java/io/micronaut/http/bind/binders/ParameterAnnotationBinder.java +++ b/http/src/main/java/io/micronaut/http/bind/binders/ParameterAnnotationBinder.java @@ -43,7 +43,7 @@ public class ParameterAnnotationBinder extends AbstractAnnotatedArgumentBinde /** * @param conversionService The conversion service */ - public ParameterAnnotationBinder(ConversionService conversionService) { + public ParameterAnnotationBinder(ConversionService conversionService) { super(conversionService); this.queryValueArgumentBinder = new QueryValueArgumentBinder<>(conversionService); } diff --git a/http/src/main/java/io/micronaut/http/bind/binders/PartAnnotationBinder.java b/http/src/main/java/io/micronaut/http/bind/binders/PartAnnotationBinder.java index dc4ef36ac69..a1528871bd8 100644 --- a/http/src/main/java/io/micronaut/http/bind/binders/PartAnnotationBinder.java +++ b/http/src/main/java/io/micronaut/http/bind/binders/PartAnnotationBinder.java @@ -30,7 +30,7 @@ */ public class PartAnnotationBinder extends AbstractAnnotatedArgumentBinder> implements AnnotatedRequestArgumentBinder { - public PartAnnotationBinder(ConversionService conversionService) { + public PartAnnotationBinder(ConversionService conversionService) { super(conversionService); } diff --git a/http/src/main/java/io/micronaut/http/bind/binders/PathVariableAnnotationBinder.java b/http/src/main/java/io/micronaut/http/bind/binders/PathVariableAnnotationBinder.java index 144c294cb9a..0e552930c60 100644 --- a/http/src/main/java/io/micronaut/http/bind/binders/PathVariableAnnotationBinder.java +++ b/http/src/main/java/io/micronaut/http/bind/binders/PathVariableAnnotationBinder.java @@ -44,7 +44,7 @@ public class PathVariableAnnotationBinder extends AbstractAnnotatedArgumentBi /** * @param conversionService The conversion service */ - public PathVariableAnnotationBinder(ConversionService conversionService) { + public PathVariableAnnotationBinder(ConversionService conversionService) { super(conversionService); } @@ -78,7 +78,7 @@ public BindingResult bind(ArgumentConversionContext context, HttpRequest variableValues = ConvertibleValues.of(matchInfo.get().getVariableValues()); + final ConvertibleValues variableValues = ConvertibleValues.of(matchInfo.get().getVariableValues(), conversionService); if (bindAll) { Object value; // Only maps and POJOs will "bindAll", lists work like normal diff --git a/http/src/main/java/io/micronaut/http/bind/binders/QueryValueArgumentBinder.java b/http/src/main/java/io/micronaut/http/bind/binders/QueryValueArgumentBinder.java index 5be8643e1f7..7ee7d6a0add 100644 --- a/http/src/main/java/io/micronaut/http/bind/binders/QueryValueArgumentBinder.java +++ b/http/src/main/java/io/micronaut/http/bind/binders/QueryValueArgumentBinder.java @@ -44,16 +44,13 @@ public class QueryValueArgumentBinder extends AbstractAnnotatedArgumentBinder> implements AnnotatedRequestArgumentBinder { - private final ConversionService conversionService; - /** * Constructor. * * @param conversionService conversion service */ - public QueryValueArgumentBinder(ConversionService conversionService) { + public QueryValueArgumentBinder(ConversionService conversionService) { super(conversionService); - this.conversionService = conversionService; } @Override diff --git a/http/src/main/java/io/micronaut/http/bind/binders/RequestAttributeAnnotationBinder.java b/http/src/main/java/io/micronaut/http/bind/binders/RequestAttributeAnnotationBinder.java index 56e99e7c29c..cc63bac32f4 100644 --- a/http/src/main/java/io/micronaut/http/bind/binders/RequestAttributeAnnotationBinder.java +++ b/http/src/main/java/io/micronaut/http/bind/binders/RequestAttributeAnnotationBinder.java @@ -38,7 +38,7 @@ public class RequestAttributeAnnotationBinder extends AbstractAnnotatedArgume /** * @param conversionService conversionService */ - public RequestAttributeAnnotationBinder(ConversionService conversionService) { + public RequestAttributeAnnotationBinder(ConversionService conversionService) { super(conversionService); } diff --git a/http/src/main/java/io/micronaut/http/bind/binders/RequestBeanAnnotationBinder.java b/http/src/main/java/io/micronaut/http/bind/binders/RequestBeanAnnotationBinder.java index 6b8e58baea6..d7342c069ca 100644 --- a/http/src/main/java/io/micronaut/http/bind/binders/RequestBeanAnnotationBinder.java +++ b/http/src/main/java/io/micronaut/http/bind/binders/RequestBeanAnnotationBinder.java @@ -53,7 +53,7 @@ public class RequestBeanAnnotationBinder extends AbstractAnnotatedArgumentBin * @param requestBinderRegistry Original request binder registry * @param conversionService The conversion service */ - public RequestBeanAnnotationBinder(RequestBinderRegistry requestBinderRegistry, ConversionService conversionService) { + public RequestBeanAnnotationBinder(RequestBinderRegistry requestBinderRegistry, ConversionService conversionService) { super(conversionService); this.requestBinderRegistry = requestBinderRegistry; } diff --git a/http/src/main/java/io/micronaut/http/converters/HttpConverterRegistrar.java b/http/src/main/java/io/micronaut/http/converters/HttpConverterRegistrar.java index cd18a778b35..f0699e9bb78 100644 --- a/http/src/main/java/io/micronaut/http/converters/HttpConverterRegistrar.java +++ b/http/src/main/java/io/micronaut/http/converters/HttpConverterRegistrar.java @@ -16,20 +16,13 @@ package io.micronaut.http.converters; import io.micronaut.context.exceptions.ConfigurationException; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverterRegistrar; import io.micronaut.core.io.Readable; import io.micronaut.core.io.ResourceLoader; import io.micronaut.core.io.ResourceResolver; -import io.micronaut.http.HttpStatus; -import io.micronaut.http.HttpVersion; -import io.micronaut.http.MediaType; import jakarta.inject.Singleton; -import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.ProxySelector; -import java.net.SocketAddress; import java.net.URL; import java.util.Optional; @@ -54,15 +47,7 @@ protected HttpConverterRegistrar(ResourceResolver resourceResolver) { } @Override - public void register(ConversionService conversionService) { - conversionService.addConverter(String.class, HttpVersion.class, s -> { - try { - return HttpVersion.valueOf(Double.parseDouble(s)); - } catch (NumberFormatException e) { - return HttpVersion.valueOf(s); - } - }); - conversionService.addConverter(Number.class, HttpVersion.class, s -> HttpVersion.valueOf(s.doubleValue())); + public void register(MutableConversionService conversionService) { conversionService.addConverter( CharSequence.class, Readable.class, @@ -86,74 +71,5 @@ public void register(ConversionService conversionService) { } ); - conversionService.addConverter( - CharSequence.class, - MediaType.class, - (object, targetType, context) -> { - try { - return Optional.of(MediaType.of(object)); - } catch (IllegalArgumentException e) { - context.reject(e); - return Optional.empty(); - } - } - ); - conversionService.addConverter( - Number.class, - HttpStatus.class, - (object, targetType, context) -> { - try { - HttpStatus status = HttpStatus.valueOf(object.shortValue()); - return Optional.of(status); - } catch (IllegalArgumentException e) { - context.reject(object, e); - return Optional.empty(); - } - } - ); - conversionService.addConverter( - CharSequence.class, - SocketAddress.class, - (object, targetType, context) -> { - String address = object.toString(); - try { - URL url = new URL(address); - int port = url.getPort(); - if (port == -1) { - port = url.getDefaultPort(); - } - if (port == -1) { - context.reject(object, new ConfigurationException("Failed to find a port in the given value")); - return Optional.empty(); - } - return Optional.of(InetSocketAddress.createUnresolved(url.getHost(), port)); - } catch (MalformedURLException malformedURLException) { - String[] parts = object.toString().split(":"); - if (parts.length == 2) { - try { - int port = Integer.parseInt(parts[1]); - return Optional.of(InetSocketAddress.createUnresolved(parts[0], port)); - } catch (IllegalArgumentException illegalArgumentException) { - context.reject(object, illegalArgumentException); - return Optional.empty(); - } - } else { - context.reject(object, new ConfigurationException("The address is not in a proper format of IP:PORT or a standard URL")); - return Optional.empty(); - } - } - } - ); - conversionService.addConverter( - CharSequence.class, - ProxySelector.class, - (object, targetType, context) -> { - if (object.toString().equals("default")) { - return Optional.of(ProxySelector.getDefault()); - } else { - return Optional.empty(); - } - } - ); } } diff --git a/http/src/main/java/io/micronaut/http/converters/SharedHttpConvertersRegistrar.java b/http/src/main/java/io/micronaut/http/converters/SharedHttpConvertersRegistrar.java new file mode 100644 index 00000000000..1924dae98df --- /dev/null +++ b/http/src/main/java/io/micronaut/http/converters/SharedHttpConvertersRegistrar.java @@ -0,0 +1,109 @@ +/* + * Copyright 2017-2020 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.http.converters; + +import io.micronaut.context.exceptions.ConfigurationException; +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.convert.MutableConversionService; +import io.micronaut.core.convert.TypeConverterRegistrar; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.HttpVersion; + +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URL; +import java.util.Optional; + +/** + * Converter registrar for HTTP classes. + * + * @author Denis Stepanov + * @since 4.0.0 + */ +@Internal +public class SharedHttpConvertersRegistrar implements TypeConverterRegistrar { + + @Override + public void register(MutableConversionService conversionService) { + conversionService.addConverter(String.class, HttpVersion.class, s -> { + try { + return HttpVersion.valueOf(Double.parseDouble(s)); + } catch (NumberFormatException e) { + return HttpVersion.valueOf(s); + } + }); + conversionService.addConverter(Number.class, HttpVersion.class, s -> HttpVersion.valueOf(s.doubleValue())); + conversionService.addConverter( + Number.class, + HttpStatus.class, + (object, targetType, context) -> { + try { + HttpStatus status = HttpStatus.valueOf(object.shortValue()); + return Optional.of(status); + } catch (IllegalArgumentException e) { + context.reject(object, e); + return Optional.empty(); + } + } + ); + conversionService.addConverter( + CharSequence.class, + SocketAddress.class, + (object, targetType, context) -> { + String address = object.toString(); + try { + URL url = new URL(address); + int port = url.getPort(); + if (port == -1) { + port = url.getDefaultPort(); + } + if (port == -1) { + context.reject(object, new ConfigurationException("Failed to find a port in the given value")); + return Optional.empty(); + } + return Optional.of(InetSocketAddress.createUnresolved(url.getHost(), port)); + } catch (MalformedURLException malformedURLException) { + String[] parts = object.toString().split(":"); + if (parts.length == 2) { + try { + int port = Integer.parseInt(parts[1]); + return Optional.of(InetSocketAddress.createUnresolved(parts[0], port)); + } catch (IllegalArgumentException illegalArgumentException) { + context.reject(object, illegalArgumentException); + return Optional.empty(); + } + } else { + context.reject(object, new ConfigurationException("The address is not in a proper format of IP:PORT or a standard URL")); + return Optional.empty(); + } + } + } + ); + conversionService.addConverter( + CharSequence.class, + ProxySelector.class, + (object, targetType, context) -> { + if (object.toString().equals("default")) { + return Optional.of(ProxySelector.getDefault()); + } else { + return Optional.empty(); + } + } + ); + } +} diff --git a/http/src/main/java/io/micronaut/http/simple/SimpleHttpHeaders.java b/http/src/main/java/io/micronaut/http/simple/SimpleHttpHeaders.java index 451e376e5e5..243a38a40ae 100644 --- a/http/src/main/java/io/micronaut/http/simple/SimpleHttpHeaders.java +++ b/http/src/main/java/io/micronaut/http/simple/SimpleHttpHeaders.java @@ -31,7 +31,7 @@ public class SimpleHttpHeaders implements MutableHttpHeaders { private final MutableConvertibleMultiValuesMap headers = new MutableConvertibleMultiValuesMap<>(); - private final ConversionService conversionService; + private ConversionService conversionService; /** * Map-based implementation of {@link MutableHttpHeaders}. @@ -93,4 +93,9 @@ public MutableHttpHeaders remove(CharSequence header) { headers.remove(header.toString()); return this; } + + @Override + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } } diff --git a/http/src/main/java/io/micronaut/http/simple/SimpleHttpParameters.java b/http/src/main/java/io/micronaut/http/simple/SimpleHttpParameters.java index 99958735039..746ecd5ca17 100644 --- a/http/src/main/java/io/micronaut/http/simple/SimpleHttpParameters.java +++ b/http/src/main/java/io/micronaut/http/simple/SimpleHttpParameters.java @@ -17,11 +17,15 @@ import io.micronaut.core.convert.ArgumentConversionContext; import io.micronaut.core.convert.ConversionService; -import io.micronaut.core.convert.value.ConvertibleMultiValues; import io.micronaut.core.convert.value.ConvertibleMultiValuesMap; import io.micronaut.http.MutableHttpParameters; -import java.util.*; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; /** @@ -35,7 +39,7 @@ public class SimpleHttpParameters implements MutableHttpParameters { private final Map> valuesMap; - private final ConvertibleMultiValues values; + private final ConvertibleMultiValuesMap values; /** * @param values The parameter values @@ -83,4 +87,9 @@ public MutableHttpParameters add(CharSequence name, List values) { valuesMap.put(name, values.stream().map(v -> v == null ? null : v.toString()).collect(Collectors.toList())); return this; } + + @Override + public void setConversionService(ConversionService conversionService) { + values.setConversionService(conversionService); + } } diff --git a/http/src/main/java/io/micronaut/http/simple/SimpleHttpRequest.java b/http/src/main/java/io/micronaut/http/simple/SimpleHttpRequest.java index 1ff230ea410..70e466762e5 100644 --- a/http/src/main/java/io/micronaut/http/simple/SimpleHttpRequest.java +++ b/http/src/main/java/io/micronaut/http/simple/SimpleHttpRequest.java @@ -40,7 +40,7 @@ */ public class SimpleHttpRequest implements MutableHttpRequest { - private final MutableConvertibleValues attributes = new MutableConvertibleValuesMap<>(); + private final MutableConvertibleValuesMap attributes = new MutableConvertibleValuesMap<>(); private final SimpleCookies cookies = new SimpleCookies(ConversionService.SHARED); private final SimpleHttpHeaders headers = new SimpleHttpHeaders(ConversionService.SHARED); private final SimpleHttpParameters parameters = new SimpleHttpParameters(ConversionService.SHARED); @@ -126,4 +126,12 @@ public MutableConvertibleValues getAttributes() { public Optional getBody() { return (Optional) Optional.ofNullable(this.body); } + + @Override + public void setConversionService(ConversionService conversionService) { + attributes.setConversionService(conversionService); + cookies.setConversionService(conversionService); + headers.setConversionService(conversionService); + parameters.setConversionService(conversionService); + } } diff --git a/http/src/main/java/io/micronaut/http/simple/cookies/SimpleCookies.java b/http/src/main/java/io/micronaut/http/simple/cookies/SimpleCookies.java index 4df12292cc7..9e541248ec3 100644 --- a/http/src/main/java/io/micronaut/http/simple/cookies/SimpleCookies.java +++ b/http/src/main/java/io/micronaut/http/simple/cookies/SimpleCookies.java @@ -17,6 +17,7 @@ import io.micronaut.core.convert.ArgumentConversionContext; import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.ConversionServiceAware; import io.micronaut.http.cookie.Cookie; import io.micronaut.http.cookie.Cookies; @@ -29,9 +30,9 @@ * @author Vladimir Orany * @since 1.0 */ -public class SimpleCookies implements Cookies { +public class SimpleCookies implements Cookies, ConversionServiceAware { - private final ConversionService conversionService; + private ConversionService conversionService; private final Map cookies; /** @@ -90,4 +91,9 @@ public Cookie put(CharSequence name, Cookie cookie) { public void putAll(Map cookies) { this.cookies.putAll(cookies); } + + @Override + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } } diff --git a/http/src/main/resources/META-INF/services/io.micronaut.core.convert.TypeConverterRegistrar b/http/src/main/resources/META-INF/services/io.micronaut.core.convert.TypeConverterRegistrar index 60730083bd7..e18c5aab449 100644 --- a/http/src/main/resources/META-INF/services/io.micronaut.core.convert.TypeConverterRegistrar +++ b/http/src/main/resources/META-INF/services/io.micronaut.core.convert.TypeConverterRegistrar @@ -1 +1,2 @@ io.micronaut.http.MediaTypeConvertersRegistrar +io.micronaut.http.converters.SharedHttpConvertersRegistrar diff --git a/inject-groovy/src/test/groovy/io/micronaut/inject/generics/InjectWithWildcardSpec.groovy b/inject-groovy/src/test/groovy/io/micronaut/inject/generics/InjectWithWildcardSpec.groovy index 876b4f0440d..df65b9880a5 100644 --- a/inject-groovy/src/test/groovy/io/micronaut/inject/generics/InjectWithWildcardSpec.groovy +++ b/inject-groovy/src/test/groovy/io/micronaut/inject/generics/InjectWithWildcardSpec.groovy @@ -43,15 +43,15 @@ class InjectWithWildcardSpec extends Specification { static class WildCardInject { // tests injecting field @Inject - protected ConversionService conversionService + protected ConversionService conversionService // tests injecting constructor - WildCardInject(ConversionService conversionService) { + WildCardInject(ConversionService conversionService) { } // tests injection method @Inject - void setConversionService(ConversionService conversionService) { + void setConversionService(ConversionService conversionService) { this.conversionService = conversionService } } diff --git a/inject-java/src/test/groovy/io/micronaut/inject/ast/beans/BeanElementVisitorSpec.groovy b/inject-java/src/test/groovy/io/micronaut/inject/ast/beans/BeanElementVisitorSpec.groovy index 80ed964a00b..606040851a7 100644 --- a/inject-java/src/test/groovy/io/micronaut/inject/ast/beans/BeanElementVisitorSpec.groovy +++ b/inject-java/src/test/groovy/io/micronaut/inject/ast/beans/BeanElementVisitorSpec.groovy @@ -16,12 +16,11 @@ import io.micronaut.context.env.Environment; import io.micronaut.core.convert.ConversionService; import jakarta.inject.Inject; import jakarta.inject.Named; -import jakarta.inject.Singleton; @Prototype @Named("blah") class Test implements Runnable { - @Inject ConversionService conversionService; + @Inject ConversionService conversionService; @Inject void setEnvironment(Environment environment) { @@ -59,12 +58,11 @@ import io.micronaut.context.env.Environment; import io.micronaut.core.convert.ConversionService; import jakarta.inject.Inject; import jakarta.inject.Named; -import jakarta.inject.Singleton; @Prototype @Named("blah") class Test implements Runnable { - @Inject ConversionService conversionService; + @Inject ConversionService conversionService; @Inject void setEnvironment(Environment environment) { @@ -103,13 +101,11 @@ import io.micronaut.context.annotation.Prototype; import io.micronaut.context.env.Environment; import io.micronaut.core.convert.ConversionService; import jakarta.inject.Inject; -import jakarta.inject.Named; -import jakarta.inject.Singleton; @Prototype @Factory class TestFactory implements Runnable { - @Inject ConversionService conversionService; + @Inject ConversionService conversionService; @Inject void setEnvironment(Environment environment) { diff --git a/inject-java/src/test/groovy/io/micronaut/inject/generics/WildCardInject.java b/inject-java/src/test/groovy/io/micronaut/inject/generics/WildCardInject.java index 9c64209e067..477aa89a84e 100644 --- a/inject-java/src/test/groovy/io/micronaut/inject/generics/WildCardInject.java +++ b/inject-java/src/test/groovy/io/micronaut/inject/generics/WildCardInject.java @@ -22,15 +22,15 @@ public class WildCardInject { // tests injecting field @Inject - protected ConversionService conversionService; + protected ConversionService conversionService; // tests injecting constructor - public WildCardInject(ConversionService conversionService) { + public WildCardInject(ConversionService conversionService) { } // tests injection method @Inject - public void setConversionService(ConversionService conversionService) { + public void setConversionService(ConversionService conversionService) { this.conversionService = conversionService; } } diff --git a/inject/src/main/java/io/micronaut/context/AbstractInitializableBeanDefinition.java b/inject/src/main/java/io/micronaut/context/AbstractInitializableBeanDefinition.java index 32c8fd2819f..d8b35c213cd 100644 --- a/inject/src/main/java/io/micronaut/context/AbstractInitializableBeanDefinition.java +++ b/inject/src/main/java/io/micronaut/context/AbstractInitializableBeanDefinition.java @@ -759,7 +759,7 @@ public final T build(BeanResolutionContext resolutionContext, } boolean requiresConversion = value != null && !requiredArgument.getType().isInstance(value); if (requiresConversion) { - Optional converted = ConversionService.SHARED.convert(value, requiredArgument.getType(), ConversionContext.of(requiredArgument)); + Optional converted = context.getConversionService().convert(value, requiredArgument.getType(), ConversionContext.of(requiredArgument)); Object finalValue = value; value = converted.orElseThrow(() -> new BeanInstantiationException(resolutionContext, "Invalid value [" + finalValue + "] for argument: " + argumentName)); requiredArgumentValues.put(argumentName, value); diff --git a/inject/src/main/java/io/micronaut/context/AbstractParametrizedBeanDefinition.java b/inject/src/main/java/io/micronaut/context/AbstractParametrizedBeanDefinition.java index 6ebb06a1fc5..521e0d1f78e 100644 --- a/inject/src/main/java/io/micronaut/context/AbstractParametrizedBeanDefinition.java +++ b/inject/src/main/java/io/micronaut/context/AbstractParametrizedBeanDefinition.java @@ -23,7 +23,6 @@ import io.micronaut.core.annotation.AnnotationUtil; import io.micronaut.core.annotation.Internal; import io.micronaut.core.convert.ConversionContext; -import io.micronaut.core.convert.ConversionService; import io.micronaut.core.type.Argument; import io.micronaut.inject.BeanDefinition; import io.micronaut.inject.ParametrizedBeanFactory; @@ -106,7 +105,7 @@ public final T build(BeanResolutionContext resolutionContext, Object value = requiredArgumentValues.get(argumentName); boolean requiresConversion = value != null && !requiredArgument.getType().isInstance(value); if (requiresConversion) { - Optional converted = ConversionService.SHARED.convert(value, requiredArgument.getType(), ConversionContext.of(requiredArgument)); + Optional converted = context.getConversionService().convert(value, requiredArgument.getType(), ConversionContext.of(requiredArgument)); Object finalValue = value; value = converted.orElseThrow(() -> new BeanInstantiationException(resolutionContext, "Invalid value [" + finalValue + "] for argument: " + argumentName)); requiredArgumentValues.put(argumentName, value); diff --git a/inject/src/main/java/io/micronaut/context/ApplicationContext.java b/inject/src/main/java/io/micronaut/context/ApplicationContext.java index cdb9f011b29..4b965d04521 100644 --- a/inject/src/main/java/io/micronaut/context/ApplicationContext.java +++ b/inject/src/main/java/io/micronaut/context/ApplicationContext.java @@ -19,13 +19,12 @@ import io.micronaut.context.env.PropertyPlaceholderResolver; import io.micronaut.context.env.PropertySource; import io.micronaut.context.env.SystemPropertiesPropertySource; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; import io.micronaut.core.util.ArgumentUtils; import io.micronaut.core.util.StringUtils; import io.micronaut.core.value.PropertyResolver; -import io.micronaut.core.annotation.NonNull; -import io.micronaut.core.annotation.Nullable; import java.util.Collections; import java.util.Map; import java.util.function.Consumer; @@ -59,11 +58,6 @@ */ public interface ApplicationContext extends BeanContext, PropertyResolver, PropertyPlaceholderResolver { - /** - * @return The default conversion service - */ - @NonNull ConversionService getConversionService(); - /** * @return The application environment */ diff --git a/inject/src/main/java/io/micronaut/context/ApplicationContextConfiguration.java b/inject/src/main/java/io/micronaut/context/ApplicationContextConfiguration.java index 00ed9676c0e..0ebedf19b12 100644 --- a/inject/src/main/java/io/micronaut/context/ApplicationContextConfiguration.java +++ b/inject/src/main/java/io/micronaut/context/ApplicationContextConfiguration.java @@ -15,11 +15,10 @@ */ package io.micronaut.context; -import io.micronaut.core.convert.ConversionService; -import io.micronaut.core.io.scan.ClassPathResourceLoader; - import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.convert.MutableConversionService; +import io.micronaut.core.io.scan.ClassPathResourceLoader; import java.util.Collections; import java.util.List; @@ -85,12 +84,12 @@ default boolean isEnvironmentPropertySource() { } /** - * The default conversion service to use. + * The optional conversion service to use. * * @return The conversion service */ - default @NonNull ConversionService getConversionService() { - return ConversionService.SHARED; + default Optional getConversionService() { + return Optional.empty(); } /** diff --git a/inject/src/main/java/io/micronaut/context/BeanContext.java b/inject/src/main/java/io/micronaut/context/BeanContext.java index f87c18c155c..0a1a2faac4b 100644 --- a/inject/src/main/java/io/micronaut/context/BeanContext.java +++ b/inject/src/main/java/io/micronaut/context/BeanContext.java @@ -20,6 +20,7 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.attr.MutableAttributeHolder; +import io.micronaut.core.convert.ConversionServiceProvider; import io.micronaut.core.type.Argument; import io.micronaut.inject.BeanIdentifier; import io.micronaut.inject.validation.BeanDefinitionValidator; @@ -45,7 +46,8 @@ public interface BeanContext extends BeanDefinitionRegistry, ApplicationEventPublisher, AnnotationMetadataResolver, - MutableAttributeHolder { + MutableAttributeHolder, + ConversionServiceProvider { /** * Obtains the configuration for this context. diff --git a/inject/src/main/java/io/micronaut/context/DefaultApplicationContext.java b/inject/src/main/java/io/micronaut/context/DefaultApplicationContext.java index 97e00c7ced7..f3017274ef6 100644 --- a/inject/src/main/java/io/micronaut/context/DefaultApplicationContext.java +++ b/inject/src/main/java/io/micronaut/context/DefaultApplicationContext.java @@ -29,7 +29,7 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.convert.ArgumentConversionContext; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverter; import io.micronaut.core.convert.TypeConverterRegistrar; import io.micronaut.core.io.scan.ClassPathResourceLoader; @@ -62,7 +62,6 @@ */ public class DefaultApplicationContext extends DefaultBeanContext implements ApplicationContext { - private final ConversionService conversionService; private final ClassPathResourceLoader resourceLoader; private final ApplicationContextConfiguration configuration; private Environment environment; @@ -116,7 +115,6 @@ public DefaultApplicationContext(@NonNull ApplicationContextConfiguration config super(configuration); ArgumentUtils.requireNonNull("configuration", configuration); this.configuration = configuration; - this.conversionService = createConversionService(); this.resourceLoader = configuration.getResourceLoader(); } @@ -162,25 +160,15 @@ private boolean isBootstrapPropertySourceLocatorPresent() { return false; } - /** - * Creates the default conversion service. - * - * @return The conversion service - */ - protected @NonNull - ConversionService createConversionService() { - return ConversionService.SHARED; - } - @Override - public @NonNull - ConversionService getConversionService() { - return conversionService; + @NonNull + public MutableConversionService getConversionService() { + return getEnvironment(); } @Override - public @NonNull - Environment getEnvironment() { + @NonNull + public Environment getEnvironment() { if (environment == null) { environment = createEnvironment(configuration); } @@ -188,16 +176,23 @@ Environment getEnvironment() { } @Override - public synchronized @NonNull - ApplicationContext start() { + @NonNull + public synchronized ApplicationContext start() { startEnvironment(); return (ApplicationContext) super.start(); } + @Override + protected void registerConversionService() { + // Conversion service is represented by the environment + } + @Override public synchronized @NonNull ApplicationContext stop() { - return (ApplicationContext) super.stop(); + ApplicationContext stop = (ApplicationContext) super.stop(); + environment = null; + return stop; } @Override @@ -517,6 +512,7 @@ public String resolveRequiredPlaceholders(String str) throws ConfigurationExcept * @param beanContext The bean context */ protected void initializeTypeConverters(BeanContext beanContext) { + MutableConversionService mutableConversionService = getConversionService(); for (BeanRegistration typeConverterRegistration : beanContext.getBeanRegistrations(TypeConverter.class)) { TypeConverter typeConverter = typeConverterRegistration.getBean(); List> typeArguments = typeConverterRegistration.getBeanDefinition().getTypeArguments(TypeConverter.class); @@ -524,12 +520,12 @@ protected void initializeTypeConverters(BeanContext beanContext) { Class source = typeArguments.get(0).getType(); Class target = typeArguments.get(1).getType(); if (!(source == Object.class && target == Object.class)) { - getConversionService().addConverter(source, target, typeConverter); + mutableConversionService.addConverter(source, target, typeConverter); } } } for (TypeConverterRegistrar registrar : beanContext.getBeansOfType(TypeConverterRegistrar.class)) { - registrar.register(conversionService); + registrar.register(mutableConversionService); } } @@ -583,7 +579,7 @@ private static class BootstrapEnvironment extends DefaultEnvironment { private List propertySourceList; - BootstrapEnvironment(ClassPathResourceLoader resourceLoader, ConversionService conversionService, ApplicationContextConfiguration configuration, String... activeEnvironments) { + BootstrapEnvironment(ClassPathResourceLoader resourceLoader, MutableConversionService conversionService, ApplicationContextConfiguration configuration, String... activeEnvironments) { super(new ApplicationContextConfiguration() { @Override public Optional getDeduceEnvironments() { @@ -621,8 +617,8 @@ public List getEnvironmentVariableExcludes() { @NonNull @Override - public ConversionService getConversionService() { - return conversionService; + public Optional getConversionService() { + return Optional.of(conversionService); } @NonNull @@ -812,7 +808,7 @@ private BootstrapPropertySourceLocator resolveBootstrapPropertySourceLocator(Str private BootstrapEnvironment createBootstrapEnvironment(String... environmentNames) { return new BootstrapEnvironment( resourceLoader, - conversionService, + mutableConversionService, configuration, environmentNames); } diff --git a/inject/src/main/java/io/micronaut/context/DefaultApplicationContextBuilder.java b/inject/src/main/java/io/micronaut/context/DefaultApplicationContextBuilder.java index ae86c07eba2..22009c07f7b 100644 --- a/inject/src/main/java/io/micronaut/context/DefaultApplicationContextBuilder.java +++ b/inject/src/main/java/io/micronaut/context/DefaultApplicationContextBuilder.java @@ -46,21 +46,21 @@ * @since 1.0 */ public class DefaultApplicationContextBuilder implements ApplicationContextBuilder, ApplicationContextConfiguration { - private List singletons = new ArrayList<>(); - private List environments = new ArrayList<>(); - private List defaultEnvironments = new ArrayList<>(); - private List packages = new ArrayList<>(); - private Map properties = new LinkedHashMap<>(); - private List propertySources = new ArrayList<>(); - private Collection configurationIncludes = new HashSet<>(); - private Collection configurationExcludes = new HashSet<>(); + private final List singletons = new ArrayList<>(); + private final List environments = new ArrayList<>(); + private final List defaultEnvironments = new ArrayList<>(); + private final List packages = new ArrayList<>(); + private final Map properties = new LinkedHashMap<>(); + private final List propertySources = new ArrayList<>(); + private final Collection configurationIncludes = new HashSet<>(); + private final Collection configurationExcludes = new HashSet<>(); private Boolean deduceEnvironments = null; private ClassLoader classLoader = getClass().getClassLoader(); private boolean envPropertySource = true; - private List envVarIncludes = new ArrayList<>(); - private List envVarExcludes = new ArrayList<>(); + private final List envVarIncludes = new ArrayList<>(); + private final List envVarExcludes = new ArrayList<>(); private String[] args = new String[0]; - private Set> eagerInitAnnotated = new HashSet<>(3); + private final Set> eagerInitAnnotated = new HashSet<>(3); private String[] overrideConfigLocations; private boolean banner = true; private ClassPathResourceLoader classPathResourceLoader; @@ -89,7 +89,7 @@ public boolean isAllowEmptyProviders() { } @Override - @NonNull + @NonNull public ApplicationContextBuilder enableDefaultPropertySources(boolean areEnabled) { this.enableDefaultPropertySources = areEnabled; return this; diff --git a/inject/src/main/java/io/micronaut/context/DefaultBeanContext.java b/inject/src/main/java/io/micronaut/context/DefaultBeanContext.java index b5066573566..f86d9febba6 100644 --- a/inject/src/main/java/io/micronaut/context/DefaultBeanContext.java +++ b/inject/src/main/java/io/micronaut/context/DefaultBeanContext.java @@ -64,8 +64,7 @@ import io.micronaut.core.annotation.Nullable; import io.micronaut.core.annotation.Order; import io.micronaut.core.annotation.UsedByGeneratedCode; -import io.micronaut.core.convert.ConversionService; -import io.micronaut.core.convert.DefaultConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverter; import io.micronaut.core.convert.TypeConverterRegistrar; import io.micronaut.core.convert.value.MutableConvertibleValues; @@ -238,6 +237,9 @@ public > Stream reduce(Class beanType, S private Set, List>> beanPreDestroyEventListeners; private Set, List>> beanDestroyedEventListeners; + @Nullable + private MutableConversionService conversionService; + /** * Construct a new bean context using the same classloader that loaded this DefaultBeanContext class. */ @@ -333,11 +335,10 @@ public synchronized BeanContext start() { if (!isRunning()) { if (initializing.compareAndSet(false, true)) { - // Reset possibly modified shared context - ((DefaultConversionService) ConversionService.SHARED).reset(); if (LOG.isDebugEnabled()) { LOG.debug("Starting BeanContext"); } + registerConversionService(); finalizeConfiguration(); if (LOG.isDebugEnabled()) { String activeConfigurations = beanConfigurations @@ -361,6 +362,13 @@ public synchronized BeanContext start() { return this; } + /** + * Registers conversion service. + */ + protected void registerConversionService() { + conversionService = MutableConversionService.create(); + registerSingleton(MutableConversionService.class, conversionService, null, false); + } /** * The close method will shut down the context calling {@link jakarta.annotation.PreDestroy} hooks on loaded @@ -421,7 +429,7 @@ public synchronized BeanContext stop() { beanCreationEventListeners = null; beanPreDestroyEventListeners = null; beanDestroyedEventListeners = null; - ((DefaultConversionService) ConversionService.SHARED).reset(); + conversionService = null; terminating.set(false); running.set(false); } @@ -1034,6 +1042,7 @@ private Map resolveArgumentValues(BeanResolutionContext reso if (LOG.isTraceEnabled()) { LOG.trace("Creating bean for parameters: {}", ArrayUtils.toString(args)); } + MutableConversionService conversionService = getConversionService(); Argument[] requiredArguments = ((ParametrizedBeanFactory) definition).getRequiredArguments(); Map argumentValues = new LinkedHashMap<>(requiredArguments.length); BeanResolutionContext.Path currentPath = resolutionContext.getPath(); @@ -1047,7 +1056,7 @@ private Map resolveArgumentValues(BeanResolutionContext reso if (argumentType.isInstance(val) && !CollectionUtils.isIterableOrMap(argumentType)) { argumentValues.put(requiredArgument.getName(), val); } else { - argumentValues.put(requiredArgument.getName(), ConversionService.SHARED.convert(val, requiredArgument).orElseThrow(() -> + argumentValues.put(requiredArgument.getName(), conversionService.convert(val, requiredArgument).orElseThrow(() -> new BeanInstantiationException(resolutionContext, "Invalid bean @Argument [" + requiredArgument + "]. Cannot convert object [" + val + "] to required type: " + argumentType) )); } @@ -2427,6 +2436,7 @@ private Map getRequiredArgumentValues(@NonNull BeanResolutio } else { convertedValues = new LinkedHashMap<>(); } + MutableConversionService conversionService = getConversionService(); if (convertedValues != null) { for (Argument requiredArgument : requiredArguments) { String argumentName = requiredArgument.getName(); @@ -2440,7 +2450,7 @@ private Map getRequiredArgumentValues(@NonNull BeanResolutio if (requiredArgument.getType().isInstance(val)) { convertedValue = val; } else { - convertedValue = ConversionService.SHARED.convert(val, requiredArgument).orElseThrow(() -> + convertedValue = conversionService.convert(val, requiredArgument).orElseThrow(() -> new BeanInstantiationException(resolutionContext, "Invalid bean argument [" + requiredArgument + "]. Cannot convert object [" + val + "] to required type: " + requiredArgument.getType()) ); } @@ -3649,7 +3659,7 @@ public Optional getAttribute(CharSequence name, Class type) { if (type.isInstance(o)) { return Optional.of((T) o); } else if (o != null) { - return ConversionService.SHARED.convert(o, type); + return getConversionService().convert(o, type); } } return Optional.empty(); @@ -3684,6 +3694,11 @@ public void finalizeConfiguration() { readAllBeanDefinitionClasses(); } + @Override + public MutableConversionService getConversionService() { + return conversionService; + } + /** * @param The type * @param The return type diff --git a/inject/src/main/java/io/micronaut/context/converters/ContextConverterRegistrar.java b/inject/src/main/java/io/micronaut/context/converters/ContextConverterRegistrar.java index 291032f0444..e05b3fa4ff3 100644 --- a/inject/src/main/java/io/micronaut/context/converters/ContextConverterRegistrar.java +++ b/inject/src/main/java/io/micronaut/context/converters/ContextConverterRegistrar.java @@ -17,7 +17,7 @@ import io.micronaut.context.BeanContext; import io.micronaut.core.annotation.Internal; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverterRegistrar; import io.micronaut.core.reflect.ClassUtils; import jakarta.inject.Singleton; @@ -49,7 +49,7 @@ public class ContextConverterRegistrar implements TypeConverterRegistrar { } @Override - public void register(ConversionService conversionService) { + public void register(MutableConversionService conversionService) { conversionService.addConverter(String[].class, Class[].class, (object, targetType, context) -> { Class[] classes = Arrays .stream(object) diff --git a/inject/src/main/java/io/micronaut/context/env/DefaultEnvironment.java b/inject/src/main/java/io/micronaut/context/env/DefaultEnvironment.java index b83fd78d29f..43b1f98a309 100644 --- a/inject/src/main/java/io/micronaut/context/env/DefaultEnvironment.java +++ b/inject/src/main/java/io/micronaut/context/env/DefaultEnvironment.java @@ -20,6 +20,7 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.convert.ConversionContext; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverter; import io.micronaut.core.io.ResourceLoader; import io.micronaut.core.io.ResourceResolver; @@ -108,8 +109,8 @@ public class DefaultEnvironment extends PropertySourcePropertyResolver implement private final ClassLoader classLoader; private final Collection packages = new ConcurrentLinkedQueue<>(); private final BeanIntrospectionScanner annotationScanner; - private Collection configurationIncludes = new HashSet<>(3); - private Collection configurationExcludes = new HashSet<>(3); + private final Collection configurationIncludes = new HashSet<>(3); + private final Collection configurationExcludes = new HashSet<>(3); private final AtomicBoolean running = new AtomicBoolean(false); private Collection propertySourceLoaderList; private final Map loaderByFormatMap = new ConcurrentHashMap<>(); @@ -118,6 +119,7 @@ public class DefaultEnvironment extends PropertySourcePropertyResolver implement private final Boolean deduceEnvironments; private final ApplicationContextConfiguration configuration; private final Collection configLocations; + protected final MutableConversionService mutableConversionService; /** * Construct a new environment for the given configuration. @@ -125,7 +127,8 @@ public class DefaultEnvironment extends PropertySourcePropertyResolver implement * @param configuration The configuration */ public DefaultEnvironment(@NonNull ApplicationContextConfiguration configuration) { - super(configuration.getConversionService()); + super(configuration.getConversionService().orElseGet(MutableConversionService::create)); + this.mutableConversionService = (MutableConversionService) conversionService; this.configuration = configuration; this.resourceLoader = configuration.getResourceLoader(); @@ -302,24 +305,22 @@ public Map refreshAndDiff() { @Override public Optional convert(Object object, Class targetType, ConversionContext context) { - return conversionService.convert(object, targetType, context); + return mutableConversionService.convert(object, targetType, context); } @Override public boolean canConvert(Class sourceType, Class targetType) { - return conversionService.canConvert(sourceType, targetType); + return mutableConversionService.canConvert(sourceType, targetType); } @Override - public Environment addConverter(Class sourceType, Class targetType, TypeConverter typeConverter) { - conversionService.addConverter(sourceType, targetType, typeConverter); - return this; + public void addConverter(Class sourceType, Class targetType, TypeConverter typeConverter) { + mutableConversionService.addConverter(sourceType, targetType, typeConverter); } @Override - public Environment addConverter(Class sourceType, Class targetType, Function typeConverter) { - conversionService.addConverter(sourceType, targetType, typeConverter); - return this; + public void addConverter(Class sourceType, Class targetType, Function typeConverter) { + mutableConversionService.addConverter(sourceType, targetType, typeConverter); } @Override diff --git a/inject/src/main/java/io/micronaut/context/env/DefaultPropertyPlaceholderResolver.java b/inject/src/main/java/io/micronaut/context/env/DefaultPropertyPlaceholderResolver.java index 5894e91089c..869005f9c0b 100644 --- a/inject/src/main/java/io/micronaut/context/env/DefaultPropertyPlaceholderResolver.java +++ b/inject/src/main/java/io/micronaut/context/env/DefaultPropertyPlaceholderResolver.java @@ -53,7 +53,7 @@ public class DefaultPropertyPlaceholderResolver implements PropertyPlaceholderRe private static final char COLON = ':'; private final PropertyResolver environment; - private final ConversionService conversionService; + private final ConversionService conversionService; private final String prefix; private Collection expressionResolvers; diff --git a/inject/src/main/java/io/micronaut/context/env/Environment.java b/inject/src/main/java/io/micronaut/context/env/Environment.java index ff6c600b67f..11acfb4dbba 100644 --- a/inject/src/main/java/io/micronaut/context/env/Environment.java +++ b/inject/src/main/java/io/micronaut/context/env/Environment.java @@ -17,7 +17,7 @@ import io.micronaut.context.LifeCycle; import io.micronaut.core.annotation.Nullable; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.io.ResourceLoader; import io.micronaut.core.io.scan.BeanIntrospectionScanner; import io.micronaut.core.reflect.ClassUtils; @@ -53,7 +53,7 @@ * @author Graeme Rocher * @since 1.0 */ -public interface Environment extends PropertyResolver, LifeCycle, ConversionService, ResourceLoader { +public interface Environment extends PropertyResolver, LifeCycle, MutableConversionService, ResourceLoader { /** * Constant for the name micronaut. diff --git a/inject/src/main/java/io/micronaut/context/env/PropertyExpressionResolver.java b/inject/src/main/java/io/micronaut/context/env/PropertyExpressionResolver.java index 876fd9bbd0d..1c8b3e5f513 100644 --- a/inject/src/main/java/io/micronaut/context/env/PropertyExpressionResolver.java +++ b/inject/src/main/java/io/micronaut/context/env/PropertyExpressionResolver.java @@ -44,7 +44,7 @@ public interface PropertyExpressionResolver { */ @NonNull Optional resolve(@NonNull PropertyResolver propertyResolver, - @NonNull ConversionService conversionService, + @NonNull ConversionService conversionService, @NonNull String expression, @NonNull Class requiredType); diff --git a/inject/src/main/java/io/micronaut/context/env/PropertySourcePropertyResolver.java b/inject/src/main/java/io/micronaut/context/env/PropertySourcePropertyResolver.java index b36fe35424e..e694bf673c4 100644 --- a/inject/src/main/java/io/micronaut/context/env/PropertySourcePropertyResolver.java +++ b/inject/src/main/java/io/micronaut/context/env/PropertySourcePropertyResolver.java @@ -77,7 +77,7 @@ public class PropertySourcePropertyResolver implements PropertyResolver, AutoClo private static final Pattern RANDOM_PATTERN = Pattern.compile("\\$\\{" + RANDOM_PREFIX + "(" + RANDOM_UPPER_LIMIT + "|" + RANDOM_RANGE + ")?\\}"); private static final Object NO_VALUE = new Object(); private static final PropertyCatalog[] CONVENTIONS = {PropertyCatalog.GENERATED, PropertyCatalog.RAW}; - protected final ConversionService conversionService; + protected final ConversionService conversionService; protected final PropertyPlaceholderResolver propertyPlaceholderResolver; protected final Map propertySources = new ConcurrentHashMap<>(10); // properties are stored in an array of maps organized by character in the alphabet @@ -96,7 +96,7 @@ public class PropertySourcePropertyResolver implements PropertyResolver, AutoClo * * @param conversionService The {@link ConversionService} */ - public PropertySourcePropertyResolver(ConversionService conversionService) { + public PropertySourcePropertyResolver(ConversionService conversionService) { this.conversionService = conversionService; this.propertyPlaceholderResolver = new DefaultPropertyPlaceholderResolver(this, conversionService); } diff --git a/inject/src/main/java/io/micronaut/inject/annotation/AnnotationConvertersRegistrar.java b/inject/src/main/java/io/micronaut/inject/annotation/AnnotationConvertersRegistrar.java index 2805d19504a..924cddebc43 100644 --- a/inject/src/main/java/io/micronaut/inject/annotation/AnnotationConvertersRegistrar.java +++ b/inject/src/main/java/io/micronaut/inject/annotation/AnnotationConvertersRegistrar.java @@ -16,7 +16,7 @@ package io.micronaut.inject.annotation; import io.micronaut.core.annotation.Internal; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverterRegistrar; import io.micronaut.core.reflect.ClassUtils; @@ -36,7 +36,7 @@ public final class AnnotationConvertersRegistrar implements TypeConverterRegistrar { @Override - public void register(ConversionService conversionService) { + public void register(MutableConversionService conversionService) { conversionService.addConverter(io.micronaut.core.annotation.AnnotationValue.class, Annotation.class, (object, targetType, context) -> { Optional annotationClass = ClassUtils.forName(object.getAnnotationName(), targetType.getClassLoader()); return annotationClass.map(aClass -> AnnotationMetadataSupport.buildAnnotation(aClass, object)); diff --git a/inject/src/test/groovy/io/micronaut/context/env/PropertySourcePropertyResolverSpec.groovy b/inject/src/test/groovy/io/micronaut/context/env/PropertySourcePropertyResolverSpec.groovy index 08cdbe8d8d5..005710395ac 100644 --- a/inject/src/test/groovy/io/micronaut/context/env/PropertySourcePropertyResolverSpec.groovy +++ b/inject/src/test/groovy/io/micronaut/context/env/PropertySourcePropertyResolverSpec.groovy @@ -661,7 +661,7 @@ class PropertySourcePropertyResolverSpec extends Specification { @Override @NonNull Optional resolve(@NonNull PropertyResolver propertyResolver, - @NonNull ConversionService conversionService, + @NonNull ConversionService conversionService, @NonNull String expression, @NonNull Class requiredType) { assert requiredType == String.class @@ -706,7 +706,7 @@ class PropertySourcePropertyResolverSpec extends Specification { @Override @NonNull Optional resolve(@NonNull PropertyResolver propertyResolver, - @NonNull ConversionService conversionService, + @NonNull ConversionService conversionService, @NonNull String expression, @NonNull Class requiredType) { Optional.empty() diff --git a/jackson-databind/src/main/java/io/micronaut/jackson/databind/convert/JacksonConverterRegistrar.java b/jackson-databind/src/main/java/io/micronaut/jackson/databind/convert/JacksonConverterRegistrar.java index ec2d3fe7486..d14c72527ca 100644 --- a/jackson-databind/src/main/java/io/micronaut/jackson/databind/convert/JacksonConverterRegistrar.java +++ b/jackson-databind/src/main/java/io/micronaut/jackson/databind/convert/JacksonConverterRegistrar.java @@ -32,6 +32,7 @@ import io.micronaut.core.annotation.Nullable; import io.micronaut.core.convert.ArgumentConversionContext; import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverter; import io.micronaut.core.convert.TypeConverterRegistrar; import io.micronaut.core.convert.value.ConvertibleValues; @@ -59,7 +60,7 @@ public class JacksonConverterRegistrar implements TypeConverterRegistrar { private final BeanProvider objectMapper; - private final ConversionService conversionService; + private final ConversionService conversionService; /** * Default constructor. @@ -69,13 +70,13 @@ public class JacksonConverterRegistrar implements TypeConverterRegistrar { @Inject protected JacksonConverterRegistrar( BeanProvider objectMapper, - ConversionService conversionService) { + ConversionService conversionService) { this.objectMapper = objectMapper; this.conversionService = conversionService; } @Override - public void register(ConversionService conversionService) { + public void register(MutableConversionService conversionService) { conversionService.addConverter( ArrayNode.class, Object[].class, diff --git a/jackson-databind/src/main/java/io/micronaut/jackson/databind/convert/ObjectNodeConvertibleValues.java b/jackson-databind/src/main/java/io/micronaut/jackson/databind/convert/ObjectNodeConvertibleValues.java index ccbfd6391c2..8cfdfead5b5 100644 --- a/jackson-databind/src/main/java/io/micronaut/jackson/databind/convert/ObjectNodeConvertibleValues.java +++ b/jackson-databind/src/main/java/io/micronaut/jackson/databind/convert/ObjectNodeConvertibleValues.java @@ -43,13 +43,13 @@ public class ObjectNodeConvertibleValues implements ConvertibleValues { private final ObjectNode objectNode; - private final ConversionService conversionService; + private final ConversionService conversionService; /** * @param objectNode The node that maps to JSON object structure * @param conversionService To convert the JSON node into given type */ - public ObjectNodeConvertibleValues(ObjectNode objectNode, ConversionService conversionService) { + public ObjectNodeConvertibleValues(ObjectNode objectNode, ConversionService conversionService) { this.objectNode = objectNode; this.conversionService = conversionService; } diff --git a/json-core/src/main/java/io/micronaut/json/convert/JsonConverterRegistrar.java b/json-core/src/main/java/io/micronaut/json/convert/JsonConverterRegistrar.java index fa8b142e109..e6cdbf40bce 100644 --- a/json-core/src/main/java/io/micronaut/json/convert/JsonConverterRegistrar.java +++ b/json-core/src/main/java/io/micronaut/json/convert/JsonConverterRegistrar.java @@ -23,6 +23,7 @@ import io.micronaut.core.convert.ArgumentConversionContext; import io.micronaut.core.convert.ConversionContext; import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverter; import io.micronaut.core.convert.TypeConverterRegistrar; import io.micronaut.core.convert.value.ConvertibleValues; @@ -54,13 +55,13 @@ @Singleton public final class JsonConverterRegistrar implements TypeConverterRegistrar { private final BeanProvider objectCodec; - private final ConversionService conversionService; + private final ConversionService conversionService; private final BeanProvider beanPropertyBinder; @Inject public JsonConverterRegistrar( BeanProvider objectCodec, - ConversionService conversionService, + ConversionService conversionService, BeanProvider beanPropertyBinder ) { this.objectCodec = objectCodec; @@ -69,7 +70,7 @@ public JsonConverterRegistrar( } @Override - public void register(ConversionService conversionService) { + public void register(MutableConversionService conversionService) { conversionService.addConverter( JsonArray.class, Object[].class, diff --git a/json-core/src/main/java/io/micronaut/json/convert/JsonNodeConvertibleValues.java b/json-core/src/main/java/io/micronaut/json/convert/JsonNodeConvertibleValues.java index 71cf86cb3ae..0e3a8642423 100644 --- a/json-core/src/main/java/io/micronaut/json/convert/JsonNodeConvertibleValues.java +++ b/json-core/src/main/java/io/micronaut/json/convert/JsonNodeConvertibleValues.java @@ -42,13 +42,13 @@ public class JsonNodeConvertibleValues implements ConvertibleValues { private final JsonNode objectNode; - private final ConversionService conversionService; + private final ConversionService conversionService; /** * @param objectNode The node that maps to JSON object structure * @param conversionService To convert the JSON node into given type */ - public JsonNodeConvertibleValues(JsonNode objectNode, ConversionService conversionService) { + public JsonNodeConvertibleValues(JsonNode objectNode, ConversionService conversionService) { if (!objectNode.isObject()) { throw new IllegalArgumentException("Expected object node"); } @@ -83,4 +83,9 @@ public Optional get(CharSequence name, ArgumentConversionContext conve return conversionService.convert(jsonNode, conversionContext); } } + + @Override + public ConversionService getConversionService() { + return conversionService; + } } diff --git a/management/src/main/java/io/micronaut/management/endpoint/processors/AbstractEndpointRouteBuilder.java b/management/src/main/java/io/micronaut/management/endpoint/processors/AbstractEndpointRouteBuilder.java index e256bd8c430..01a6f649591 100644 --- a/management/src/main/java/io/micronaut/management/endpoint/processors/AbstractEndpointRouteBuilder.java +++ b/management/src/main/java/io/micronaut/management/endpoint/processors/AbstractEndpointRouteBuilder.java @@ -65,7 +65,7 @@ abstract class AbstractEndpointRouteBuilder extends DefaultRouteBuilder implemen */ AbstractEndpointRouteBuilder(ApplicationContext applicationContext, UriNamingStrategy uriNamingStrategy, - ConversionService conversionService, + ConversionService conversionService, EndpointDefaultConfiguration endpointDefaultConfiguration) { super(applicationContext, uriNamingStrategy, conversionService); this.beanContext = applicationContext; diff --git a/management/src/main/java/io/micronaut/management/endpoint/processors/DeleteEndpointRouteBuilder.java b/management/src/main/java/io/micronaut/management/endpoint/processors/DeleteEndpointRouteBuilder.java index 31278dc0fae..90982bbc3c3 100644 --- a/management/src/main/java/io/micronaut/management/endpoint/processors/DeleteEndpointRouteBuilder.java +++ b/management/src/main/java/io/micronaut/management/endpoint/processors/DeleteEndpointRouteBuilder.java @@ -44,7 +44,7 @@ public class DeleteEndpointRouteBuilder extends AbstractEndpointRouteBuilder { */ public DeleteEndpointRouteBuilder(ApplicationContext beanContext, UriNamingStrategy uriNamingStrategy, - ConversionService conversionService, + ConversionService conversionService, EndpointDefaultConfiguration endpointDefaultConfiguration) { super(beanContext, uriNamingStrategy, conversionService, endpointDefaultConfiguration); } diff --git a/management/src/main/java/io/micronaut/management/endpoint/processors/ReadEndpointRouteBuilder.java b/management/src/main/java/io/micronaut/management/endpoint/processors/ReadEndpointRouteBuilder.java index eca710da29d..5cec22fac24 100644 --- a/management/src/main/java/io/micronaut/management/endpoint/processors/ReadEndpointRouteBuilder.java +++ b/management/src/main/java/io/micronaut/management/endpoint/processors/ReadEndpointRouteBuilder.java @@ -44,7 +44,7 @@ public class ReadEndpointRouteBuilder extends AbstractEndpointRouteBuilder { */ public ReadEndpointRouteBuilder(ApplicationContext beanContext, UriNamingStrategy uriNamingStrategy, - ConversionService conversionService, + ConversionService conversionService, EndpointDefaultConfiguration endpointDefaultConfiguration) { super(beanContext, uriNamingStrategy, conversionService, endpointDefaultConfiguration); } diff --git a/management/src/main/java/io/micronaut/management/endpoint/processors/WriteEndpointRouteBuilder.java b/management/src/main/java/io/micronaut/management/endpoint/processors/WriteEndpointRouteBuilder.java index fcb0e06a9b8..18d99ccd7ff 100644 --- a/management/src/main/java/io/micronaut/management/endpoint/processors/WriteEndpointRouteBuilder.java +++ b/management/src/main/java/io/micronaut/management/endpoint/processors/WriteEndpointRouteBuilder.java @@ -47,7 +47,7 @@ public class WriteEndpointRouteBuilder extends AbstractEndpointRouteBuilder { */ public WriteEndpointRouteBuilder(ApplicationContext beanContext, UriNamingStrategy uriNamingStrategy, - ConversionService conversionService, + ConversionService conversionService, EndpointDefaultConfiguration endpointDefaultConfiguration) { super(beanContext, uriNamingStrategy, conversionService, endpointDefaultConfiguration); } diff --git a/router/src/main/java/io/micronaut/web/router/AbstractRouteMatch.java b/router/src/main/java/io/micronaut/web/router/AbstractRouteMatch.java index 0bef4d0c789..aae32611cb1 100644 --- a/router/src/main/java/io/micronaut/web/router/AbstractRouteMatch.java +++ b/router/src/main/java/io/micronaut/web/router/AbstractRouteMatch.java @@ -50,7 +50,7 @@ abstract class AbstractRouteMatch implements MethodBasedRouteMatch { protected final MethodExecutionHandle executableMethod; - protected final ConversionService conversionService; + protected final ConversionService conversionService; protected final DefaultRouteBuilder.AbstractRoute abstractRoute; protected final List consumedMediaTypes; protected final List producedMediaTypes; @@ -61,7 +61,7 @@ abstract class AbstractRouteMatch implements MethodBasedRouteMatch { * @param abstractRoute The abstract route builder * @param conversionService The conversion service */ - protected AbstractRouteMatch(DefaultRouteBuilder.AbstractRoute abstractRoute, ConversionService conversionService) { + protected AbstractRouteMatch(DefaultRouteBuilder.AbstractRoute abstractRoute, ConversionService conversionService) { this.abstractRoute = abstractRoute; //noinspection unchecked this.executableMethod = (MethodExecutionHandle) abstractRoute.targetMethod; @@ -208,7 +208,7 @@ public ReturnType getReturnType() { @Override public R invoke(Object... arguments) { - ConversionService conversionService = this.conversionService; + ConversionService conversionService = this.conversionService; Argument[] targetArguments = getArguments(); if (targetArguments.length == 0) { @@ -245,7 +245,7 @@ public R execute(Map argumentValues) { if (targetArguments.length == 0) { return executableMethod.invoke(); } else { - ConversionService conversionService = this.conversionService; + ConversionService conversionService = this.conversionService; Map uriVariables = getVariableValues(); List argumentList = new ArrayList<>(argumentValues.size()); diff --git a/router/src/main/java/io/micronaut/web/router/AnnotatedFilterRouteBuilder.java b/router/src/main/java/io/micronaut/web/router/AnnotatedFilterRouteBuilder.java index 40b1214f78a..70a83c13428 100644 --- a/router/src/main/java/io/micronaut/web/router/AnnotatedFilterRouteBuilder.java +++ b/router/src/main/java/io/micronaut/web/router/AnnotatedFilterRouteBuilder.java @@ -57,7 +57,7 @@ public AnnotatedFilterRouteBuilder( BeanContext beanContext, ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy, - ConversionService conversionService, + ConversionService conversionService, @Nullable ServerContextPathProvider contextPathProvider) { super(executionHandleLocator, uriNamingStrategy, conversionService); this.contextPathProvider = contextPathProvider; diff --git a/router/src/main/java/io/micronaut/web/router/AnnotatedMethodRouteBuilder.java b/router/src/main/java/io/micronaut/web/router/AnnotatedMethodRouteBuilder.java index cc3fa21523b..f0ebcef3e8e 100644 --- a/router/src/main/java/io/micronaut/web/router/AnnotatedMethodRouteBuilder.java +++ b/router/src/main/java/io/micronaut/web/router/AnnotatedMethodRouteBuilder.java @@ -54,7 +54,7 @@ public class AnnotatedMethodRouteBuilder extends DefaultRouteBuilder implements * @param uriNamingStrategy The URI naming strategy * @param conversionService The conversion service */ - public AnnotatedMethodRouteBuilder(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy, ConversionService conversionService) { + public AnnotatedMethodRouteBuilder(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy, ConversionService conversionService) { super(executionHandleLocator, uriNamingStrategy, conversionService); httpMethodsHandlers.put(Get.class, (RouteDefinition definition) -> { final BeanDefinition bean = definition.beanDefinition; diff --git a/router/src/main/java/io/micronaut/web/router/DefaultRouteBuilder.java b/router/src/main/java/io/micronaut/web/router/DefaultRouteBuilder.java index 1831c688ca6..6736bdbb72a 100644 --- a/router/src/main/java/io/micronaut/web/router/DefaultRouteBuilder.java +++ b/router/src/main/java/io/micronaut/web/router/DefaultRouteBuilder.java @@ -69,7 +69,7 @@ public abstract class DefaultRouteBuilder implements RouteBuilder { static final Object NO_VALUE = new Object(); protected final ExecutionHandleLocator executionHandleLocator; protected final UriNamingStrategy uriNamingStrategy; - protected final ConversionService conversionService; + protected final ConversionService conversionService; protected final Charset defaultCharset; private DefaultUriRoute currentParentRoute = null; @@ -99,7 +99,7 @@ public DefaultRouteBuilder(ExecutionHandleLocator executionHandleLocator, UriNam * @param uriNamingStrategy The URI naming strategy * @param conversionService The conversion service */ - public DefaultRouteBuilder(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy, ConversionService conversionService) { + public DefaultRouteBuilder(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy, ConversionService conversionService) { this.executionHandleLocator = executionHandleLocator; this.uriNamingStrategy = uriNamingStrategy; this.conversionService = conversionService; @@ -383,10 +383,10 @@ protected UriRoute buildRoute(HttpMethod httpMethod, String uri, MethodExecution private UriRoute buildRoute(String httpMethodName, HttpMethod httpMethod, String uri, MethodExecutionHandle executableHandle) { UriRoute route; if (currentParentRoute != null) { - route = new DefaultUriRoute(httpMethod, currentParentRoute.uriMatchTemplate.nest(uri), executableHandle, httpMethodName); + route = new DefaultUriRoute(httpMethod, currentParentRoute.uriMatchTemplate.nest(uri), executableHandle, httpMethodName, conversionService); currentParentRoute.nestedRoutes.add((DefaultUriRoute) route); } else { - route = new DefaultUriRoute(httpMethod, uri, executableHandle, httpMethodName); + route = new DefaultUriRoute(httpMethod, uri, executableHandle, httpMethodName, conversionService); } this.uriRoutes.add(route); @@ -418,7 +418,7 @@ protected UriRoute buildBeanRoute(String httpMethodName, HttpMethod httpMethod, abstract class AbstractRoute implements MethodBasedRoute, RouteInfo { protected final List>> conditions = new ArrayList<>(); protected final MethodExecutionHandle targetMethod; - protected final ConversionService conversionService; + protected final ConversionService conversionService; protected List consumesMediaTypes; protected List producesMediaTypes; protected String bodyArgumentName; @@ -442,7 +442,7 @@ abstract class AbstractRoute implements MethodBasedRoute, RouteInfo { * @param conversionService The conversion service * @param mediaTypes The media types */ - AbstractRoute(MethodExecutionHandle targetMethod, ConversionService conversionService, List mediaTypes) { + AbstractRoute(MethodExecutionHandle targetMethod, ConversionService conversionService, List mediaTypes) { this.targetMethod = targetMethod; this.conversionService = conversionService; this.consumesMediaTypes = mediaTypes; @@ -651,7 +651,7 @@ class DefaultErrorRoute extends AbstractRoute implements ErrorRoute { * @param targetMethod The target method execution handle * @param conversionService The conversion service */ - public DefaultErrorRoute(Class error, MethodExecutionHandle targetMethod, ConversionService conversionService) { + public DefaultErrorRoute(Class error, MethodExecutionHandle targetMethod, ConversionService conversionService) { this(null, error, targetMethod, conversionService); } @@ -664,7 +664,7 @@ public DefaultErrorRoute(Class error, MethodExecutionHandle public DefaultErrorRoute( Class originatingClass, Class error, MethodExecutionHandle targetMethod, - ConversionService conversionService) { + ConversionService conversionService) { super(targetMethod, conversionService, Collections.emptyList()); this.originatingClass = originatingClass; this.error = error; @@ -772,7 +772,7 @@ class DefaultStatusRoute extends AbstractRoute implements StatusRoute { * @param targetMethod The target method execution handle * @param conversionService The conversion service */ - public DefaultStatusRoute(HttpStatus status, MethodExecutionHandle targetMethod, ConversionService conversionService) { + public DefaultStatusRoute(HttpStatus status, MethodExecutionHandle targetMethod, ConversionService conversionService) { this(null, status, targetMethod, conversionService); } @@ -782,7 +782,7 @@ public DefaultStatusRoute(HttpStatus status, MethodExecutionHandle targetMethod, * @param targetMethod The target method execution handle * @param conversionService The conversion service */ - public DefaultStatusRoute(Class originatingClass, HttpStatus status, MethodExecutionHandle targetMethod, ConversionService conversionService) { + public DefaultStatusRoute(Class originatingClass, HttpStatus status, MethodExecutionHandle targetMethod, ConversionService conversionService) { super(targetMethod, conversionService, Collections.emptyList()); this.originatingClass = originatingClass; this.status = status; @@ -880,9 +880,13 @@ class DefaultUriRoute extends AbstractRoute implements UriRoute { * @param httpMethod The HTTP method * @param uriTemplate The URI Template as a {@link CharSequence} * @param targetMethod The target method execution handle + * @param conversionService The conversion service */ - DefaultUriRoute(HttpMethod httpMethod, CharSequence uriTemplate, MethodExecutionHandle targetMethod) { - this(httpMethod, uriTemplate, targetMethod, httpMethod.name()); + DefaultUriRoute(HttpMethod httpMethod, + CharSequence uriTemplate, + MethodExecutionHandle targetMethod, + ConversionService conversionService) { + this(httpMethod, uriTemplate, targetMethod, httpMethod.name(), conversionService); } /** @@ -890,9 +894,14 @@ class DefaultUriRoute extends AbstractRoute implements UriRoute { * @param uriTemplate The URI Template as a {@link CharSequence} * @param targetMethod The target method execution handle * @param httpMethodName The actual name of the method - may differ from {@link HttpMethod#name()} for non-standard http methods + * @param conversionService The conversion service */ - DefaultUriRoute(HttpMethod httpMethod, CharSequence uriTemplate, MethodExecutionHandle targetMethod, String httpMethodName) { - this(httpMethod, uriTemplate, MediaType.APPLICATION_JSON_TYPE, targetMethod, httpMethodName); + DefaultUriRoute(HttpMethod httpMethod, + CharSequence uriTemplate, + MethodExecutionHandle targetMethod, + String httpMethodName, + ConversionService conversionService) { + this(httpMethod, uriTemplate, MediaType.APPLICATION_JSON_TYPE, targetMethod, httpMethodName, conversionService); } /** @@ -900,9 +909,14 @@ class DefaultUriRoute extends AbstractRoute implements UriRoute { * @param uriTemplate The URI Template as a {@link CharSequence} * @param mediaType The Media type * @param targetMethod The target method execution handle + * @param conversionService The conversion service */ - DefaultUriRoute(HttpMethod httpMethod, CharSequence uriTemplate, MediaType mediaType, MethodExecutionHandle targetMethod) { - this(httpMethod, uriTemplate, mediaType, targetMethod, httpMethod.name()); + DefaultUriRoute(HttpMethod httpMethod, + CharSequence uriTemplate, + MediaType mediaType, + MethodExecutionHandle targetMethod, + ConversionService conversionService) { + this(httpMethod, uriTemplate, mediaType, targetMethod, httpMethod.name(), conversionService); } /** @@ -911,18 +925,28 @@ class DefaultUriRoute extends AbstractRoute implements UriRoute { * @param mediaType The Media type * @param targetMethod The target method execution handle * @param httpMethodName The actual name of the method - may differ from {@link HttpMethod#name()} for non-standard http methods + * @param conversionService The conversion service */ - DefaultUriRoute(HttpMethod httpMethod, CharSequence uriTemplate, MediaType mediaType, MethodExecutionHandle targetMethod, String httpMethodName) { - this(httpMethod, new UriMatchTemplate(uriTemplate), Collections.singletonList(mediaType), targetMethod, httpMethodName); + DefaultUriRoute(HttpMethod httpMethod, + CharSequence uriTemplate, + MediaType mediaType, + MethodExecutionHandle targetMethod, + String httpMethodName, + ConversionService conversionService) { + this(httpMethod, new UriMatchTemplate(uriTemplate), Collections.singletonList(mediaType), targetMethod, httpMethodName, conversionService); } /** * @param httpMethod The HTTP method * @param uriTemplate The URI Template as a {@link UriMatchTemplate} * @param targetMethod The target method execution handle + * @param conversionService The conversion service */ - DefaultUriRoute(HttpMethod httpMethod, UriMatchTemplate uriTemplate, MethodExecutionHandle targetMethod) { - this(httpMethod, uriTemplate, targetMethod, httpMethod.name()); + DefaultUriRoute(HttpMethod httpMethod, + UriMatchTemplate uriTemplate, + MethodExecutionHandle targetMethod, + ConversionService conversionService) { + this(httpMethod, uriTemplate, targetMethod, httpMethod.name(), conversionService); } /** @@ -930,9 +954,14 @@ class DefaultUriRoute extends AbstractRoute implements UriRoute { * @param uriTemplate The URI Template as a {@link UriMatchTemplate} * @param targetMethod The target method execution handle * @param httpMethodName The actual name of the method - may differ from {@link HttpMethod#name()} for non-standard http methods + * @param conversionService The conversion service */ - DefaultUriRoute(HttpMethod httpMethod, UriMatchTemplate uriTemplate, MethodExecutionHandle targetMethod, String httpMethodName) { - this(httpMethod, uriTemplate, Collections.singletonList(MediaType.APPLICATION_JSON_TYPE), targetMethod, httpMethodName); + DefaultUriRoute(HttpMethod httpMethod, + UriMatchTemplate uriTemplate, + MethodExecutionHandle targetMethod, + String httpMethodName, + ConversionService conversionService) { + this(httpMethod, uriTemplate, Collections.singletonList(MediaType.APPLICATION_JSON_TYPE), targetMethod, httpMethodName, conversionService); } /** @@ -940,9 +969,14 @@ class DefaultUriRoute extends AbstractRoute implements UriRoute { * @param uriTemplate The URI Template as a {@link UriMatchTemplate} * @param mediaTypes The media types * @param targetMethod The target method execution handle + * @param conversionService The conversion service */ - DefaultUriRoute(HttpMethod httpMethod, UriMatchTemplate uriTemplate, List mediaTypes, MethodExecutionHandle targetMethod) { - this(httpMethod, uriTemplate, mediaTypes, targetMethod, httpMethod.name()); + DefaultUriRoute(HttpMethod httpMethod, + UriMatchTemplate uriTemplate, + List mediaTypes, + MethodExecutionHandle targetMethod, + ConversionService conversionService) { + this(httpMethod, uriTemplate, mediaTypes, targetMethod, httpMethod.name(), conversionService); } /** @@ -951,9 +985,14 @@ class DefaultUriRoute extends AbstractRoute implements UriRoute { * @param mediaTypes The media types * @param targetMethod The target method execution handle * @param httpMethodName The actual name of the method - may differ from {@link HttpMethod#name()} for non-standard http methods + * @param conversionService The conversion service */ - DefaultUriRoute(HttpMethod httpMethod, UriMatchTemplate uriTemplate, List mediaTypes, MethodExecutionHandle targetMethod, String httpMethodName) { - super(targetMethod, ConversionService.SHARED, mediaTypes); + DefaultUriRoute(HttpMethod httpMethod, UriMatchTemplate uriTemplate, + List mediaTypes, + MethodExecutionHandle targetMethod, + String httpMethodName, + ConversionService conversionService) { + super(targetMethod, conversionService, mediaTypes); this.httpMethod = httpMethod; this.uriMatchTemplate = uriTemplate; this.httpMethodName = httpMethodName; diff --git a/router/src/main/java/io/micronaut/web/router/DefaultUriRouteMatch.java b/router/src/main/java/io/micronaut/web/router/DefaultUriRouteMatch.java index 89ee549a1b2..9349cc21ed3 100644 --- a/router/src/main/java/io/micronaut/web/router/DefaultUriRouteMatch.java +++ b/router/src/main/java/io/micronaut/web/router/DefaultUriRouteMatch.java @@ -55,7 +55,7 @@ class DefaultUriRouteMatch extends AbstractRouteMatch implements Uri */ DefaultUriRouteMatch(UriMatchInfo matchInfo, DefaultRouteBuilder.DefaultUriRoute uriRoute, - Charset defaultCharset, ConversionService conversionService + Charset defaultCharset, ConversionService conversionService ) { super(uriRoute, conversionService); this.uriRoute = uriRoute; diff --git a/router/src/main/java/io/micronaut/web/router/ErrorRouteMatch.java b/router/src/main/java/io/micronaut/web/router/ErrorRouteMatch.java index 245cbfb485a..7e7f0e04c9f 100644 --- a/router/src/main/java/io/micronaut/web/router/ErrorRouteMatch.java +++ b/router/src/main/java/io/micronaut/web/router/ErrorRouteMatch.java @@ -46,7 +46,7 @@ class ErrorRouteMatch extends AbstractRouteMatch { * @param abstractRoute The abstract route * @param conversionService The conversion service */ - ErrorRouteMatch(Throwable error, DefaultRouteBuilder.AbstractRoute abstractRoute, ConversionService conversionService) { + ErrorRouteMatch(Throwable error, DefaultRouteBuilder.AbstractRoute abstractRoute, ConversionService conversionService) { super(abstractRoute, conversionService); this.error = error; this.variables = new LinkedHashMap<>(); diff --git a/router/src/main/java/io/micronaut/web/router/StatusRouteMatch.java b/router/src/main/java/io/micronaut/web/router/StatusRouteMatch.java index f42d363d973..5470fcb0ab9 100644 --- a/router/src/main/java/io/micronaut/web/router/StatusRouteMatch.java +++ b/router/src/main/java/io/micronaut/web/router/StatusRouteMatch.java @@ -40,7 +40,7 @@ class StatusRouteMatch extends AbstractRouteMatch { * @param abstractRoute The abstract route * @param conversionService The conversion service */ - StatusRouteMatch(HttpStatus httpStatus, DefaultRouteBuilder.AbstractRoute abstractRoute, ConversionService conversionService) { + StatusRouteMatch(HttpStatus httpStatus, DefaultRouteBuilder.AbstractRoute abstractRoute, ConversionService conversionService) { super(abstractRoute, conversionService); this.httpStatus = httpStatus; this.requiredArguments = new ArrayList<>(Arrays.asList(getArguments())); diff --git a/router/src/test/groovy/io/micronaut/context/router/EachBeanRouteBuilderSpec.groovy b/router/src/test/groovy/io/micronaut/context/router/EachBeanRouteBuilderSpec.groovy index c181cf4e248..932c06056e5 100644 --- a/router/src/test/groovy/io/micronaut/context/router/EachBeanRouteBuilderSpec.groovy +++ b/router/src/test/groovy/io/micronaut/context/router/EachBeanRouteBuilderSpec.groovy @@ -59,7 +59,7 @@ class EachBeanRouteBuilderSpec extends Specification { MyRouteBuilder(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy, - ConversionService conversionService, + ConversionService conversionService, BeanContext beanContext) { super(executionHandleLocator, uriNamingStrategy, conversionService) diff --git a/runtime/src/main/java/io/micronaut/reactive/flow/converters/FlowConverterRegistrar.java b/runtime/src/main/java/io/micronaut/reactive/flow/converters/FlowConverterRegistrar.java index cd967a23292..20e06a19983 100644 --- a/runtime/src/main/java/io/micronaut/reactive/flow/converters/FlowConverterRegistrar.java +++ b/runtime/src/main/java/io/micronaut/reactive/flow/converters/FlowConverterRegistrar.java @@ -16,7 +16,7 @@ package io.micronaut.reactive.flow.converters; import io.micronaut.context.annotation.Requires; -import io.micronaut.core.convert.ConversionService; +import io.micronaut.core.convert.MutableConversionService; import io.micronaut.core.convert.TypeConverterRegistrar; import jakarta.inject.Singleton; import kotlinx.coroutines.flow.Flow; @@ -34,7 +34,7 @@ @Requires(classes = {Flux.class, ReactiveFlowKt.class}) public class FlowConverterRegistrar implements TypeConverterRegistrar { @Override - public void register(ConversionService conversionService) { + public void register(MutableConversionService conversionService) { // Flow conversionService.addConverter(Flow.class, Flux.class, flow -> Flux.from(ReactiveFlowKt.asPublisher(flow)) diff --git a/runtime/src/main/java/io/micronaut/runtime/http/codec/TextPlainCodec.java b/runtime/src/main/java/io/micronaut/runtime/http/codec/TextPlainCodec.java index 5ef14fa348d..beab892b8a9 100644 --- a/runtime/src/main/java/io/micronaut/runtime/http/codec/TextPlainCodec.java +++ b/runtime/src/main/java/io/micronaut/runtime/http/codec/TextPlainCodec.java @@ -59,15 +59,19 @@ public class TextPlainCodec implements MediaTypeCodec { private final Charset defaultCharset; private final List additionalTypes; + private final ConversionService conversionService; /** - * @param defaultCharset The default charset used for serialization and deserialization - * @param codecConfiguration The configuration for the codec + * @param defaultCharset The default charset used for serialization and deserialization + * @param codecConfiguration The configuration for the codec + * @param conversionService The conversion service */ @Inject public TextPlainCodec(@Value("${" + ApplicationConfiguration.DEFAULT_CHARSET + "}") Optional defaultCharset, - @Named(CONFIGURATION_QUALIFIER) @Nullable CodecConfiguration codecConfiguration) { + @Named(CONFIGURATION_QUALIFIER) @Nullable CodecConfiguration codecConfiguration, + ConversionService conversionService) { this.defaultCharset = defaultCharset.orElse(StandardCharsets.UTF_8); + this.conversionService = conversionService; if (codecConfiguration != null) { this.additionalTypes = codecConfiguration.getAdditionalTypes(); } else { @@ -76,10 +80,12 @@ public TextPlainCodec(@Value("${" + ApplicationConfiguration.DEFAULT_CHARSET + " } /** - * @param defaultCharset The default charset used for serialization and deserialization + * @param defaultCharset The default charset used for serialization and deserialization + * @param conversionService The conversion service */ - public TextPlainCodec(Charset defaultCharset) { + public TextPlainCodec(Charset defaultCharset, ConversionService conversionService) { this.defaultCharset = defaultCharset != null ? defaultCharset : StandardCharsets.UTF_8; + this.conversionService = conversionService; this.additionalTypes = Collections.emptyList(); } @@ -97,7 +103,7 @@ public T decode(Argument type, ByteBuffer buffer) throws CodecExceptio if (CharSequence.class.isAssignableFrom(type.getType())) { return (T) text; } - return ConversionService.SHARED + return conversionService .convert(text, type) .orElseThrow(() -> new CodecException("Cannot decode byte buffer with value [" + text + "] to type: " + type)); } @@ -108,7 +114,7 @@ public T decode(Argument type, byte[] bytes) throws CodecException { if (CharSequence.class.isAssignableFrom(type.getType())) { return (T) text; } - return ConversionService.SHARED + return conversionService .convert(text, type) .orElseThrow(() -> new CodecException("Cannot decode bytes with value [" + text + "] to type: " + type)); } diff --git a/runtime/src/test/groovy/io/micronaut/runtime/ConversionServiceIsResetSpec.groovy b/runtime/src/test/groovy/io/micronaut/runtime/ConversionServiceIsResetSpec.groovy index 7bd5ee40bc8..d5c9d61bd3b 100644 --- a/runtime/src/test/groovy/io/micronaut/runtime/ConversionServiceIsResetSpec.groovy +++ b/runtime/src/test/groovy/io/micronaut/runtime/ConversionServiceIsResetSpec.groovy @@ -18,6 +18,7 @@ package io.micronaut.runtime import io.micronaut.context.ApplicationContext import io.micronaut.core.convert.ConversionContext import io.micronaut.core.convert.ConversionService +import io.micronaut.core.convert.MutableConversionService import io.micronaut.core.convert.TypeConverter import spock.lang.Specification @@ -32,7 +33,7 @@ class ConversionServiceIsResetSpec extends Specification { return Optional.of(new B()) } } - ctx.getBean(ConversionService).addConverter(A, B, typeConverter) + ctx.getBean(MutableConversionService).addConverter(A, B, typeConverter) when: def result = ctx.getBean(ConversionService).convert(new A(), B) diff --git a/runtime/src/test/groovy/io/micronaut/runtime/converters/time/TimeConverterRegistrarSpec.groovy b/runtime/src/test/groovy/io/micronaut/runtime/converters/time/TimeConverterRegistrarSpec.groovy index 830825cb871..ec1804d12ee 100644 --- a/runtime/src/test/groovy/io/micronaut/runtime/converters/time/TimeConverterRegistrarSpec.groovy +++ b/runtime/src/test/groovy/io/micronaut/runtime/converters/time/TimeConverterRegistrarSpec.groovy @@ -16,7 +16,7 @@ package io.micronaut.runtime.converters.time import io.micronaut.core.convert.ConversionService -import io.micronaut.core.convert.DefaultConversionService +import io.micronaut.core.convert.DefaultMutableConversionService import spock.lang.Specification import spock.lang.Unroll @@ -35,7 +35,7 @@ class TimeConverterRegistrarSpec extends Specification { @Unroll void "test convert duration #val"() { given: - ConversionService conversionService = new DefaultConversionService() + ConversionService conversionService = new DefaultMutableConversionService() new TimeConverterRegistrar().register(conversionService) @@ -55,7 +55,7 @@ class TimeConverterRegistrarSpec extends Specification { @Unroll void "test converts a #sourceObject.class.name to a #targetType.name"() { given: - ConversionService conversionService = new DefaultConversionService() + ConversionService conversionService = new DefaultMutableConversionService() new TimeConverterRegistrar().register(conversionService) expect: diff --git a/runtime/src/test/groovy/io/micronaut/runtime/http/codec/TextPlainCodecSpec.groovy b/runtime/src/test/groovy/io/micronaut/runtime/http/codec/TextPlainCodecSpec.groovy index eb879916109..f30f866d256 100644 --- a/runtime/src/test/groovy/io/micronaut/runtime/http/codec/TextPlainCodecSpec.groovy +++ b/runtime/src/test/groovy/io/micronaut/runtime/http/codec/TextPlainCodecSpec.groovy @@ -16,6 +16,7 @@ package io.micronaut.runtime.http.codec import io.micronaut.context.ApplicationContext +import io.micronaut.core.convert.MutableConversionService import io.micronaut.core.io.buffer.ByteBuffer import io.micronaut.core.io.buffer.ByteBufferFactory import io.micronaut.http.MediaType @@ -26,7 +27,7 @@ import java.nio.charset.StandardCharsets class TextPlainCodecSpec extends Specification { - @Shared TextPlainCodec codec = new TextPlainCodec(StandardCharsets.UTF_8) + @Shared TextPlainCodec codec = new TextPlainCodec(StandardCharsets.UTF_8, MutableConversionService.create()) void "test the buffer min and max are correct for special characters"() { given: diff --git a/src/main/docs/guide/appendix/breaks.adoc b/src/main/docs/guide/appendix/breaks.adoc index 8a2127546ba..211dcdaf1c5 100644 --- a/src/main/docs/guide/appendix/breaks.adoc +++ b/src/main/docs/guide/appendix/breaks.adoc @@ -50,6 +50,11 @@ Annotations with the retention CLASS are not available in the annotation metadat Interceptors with multiple interceptor bindings annotations now require the same set of annotations to be present at the intercepted point. In the Micronaut 3 an interceptor with multiple binding annotations would need at least one of the binding annotations to be present at the intercepted point. +==== `ConversionService` and `ConversionService.SHARED` is no longer mutable + +New type converters can be added to api:core.convert.MutableConversionService[] retrieved from the bean context or by declaring a bean of type api:core.convert.TypeConverter[]. +To register a type converter into `ConversionService.SHARED`, the registration needs to be done via the service loader. + == 3.3.0 - The <> is now disabled by default. To enable it, you must update your endpoint config: diff --git a/src/main/docs/guide/config/customTypeConverter.adoc b/src/main/docs/guide/config/customTypeConverter.adoc index 94f32456cb3..076ffd96b15 100644 --- a/src/main/docs/guide/config/customTypeConverter.adoc +++ b/src/main/docs/guide/config/customTypeConverter.adoc @@ -21,3 +21,5 @@ snippet::io.micronaut.docs.config.converters.MapToLocalDateConverter[tags="impor <1> The class implements api:core.convert.TypeConverter[] which has two generic arguments, the type you are converting from, and the type you are converting to <2> The implementation delegates to the default shared conversion service to convert the values from the Map used to create a `LocalDate` <3> If an exception occurs during binding, call `reject(..)` which propagates additional information to the container + +NOTE: It's possible to add a custom type converter into `ConversionService.SHARED` by registering it via the service loader. diff --git a/src/main/docs/guide/httpServer/binding.adoc b/src/main/docs/guide/httpServer/binding.adoc index ab80a21759c..11f6b608c18 100644 --- a/src/main/docs/guide/httpServer/binding.adoc +++ b/src/main/docs/guide/httpServer/binding.adoc @@ -90,7 +90,7 @@ WARNING: Since Java does not retain argument names in bytecode, you must compile Generally any type that can be converted from a String representation to a Java type via the link:{api}/io/micronaut/core/convert/ConversionService.html[ConversionService] API can be bound to. -This includes most common Java types, however additional link:{api}/io/micronaut/core/convert/TypeConverter.html[TypeConverter] instances can be registered by creating `@Singleton` beans of type `TypeConverter`. +This includes most common Java types, however additional link:{api}/io/micronaut/core/convert/TypeConverter.html[TypeConverter] instances can be registered via the service loader or by creating beans of type `TypeConverter`, The handling of nullability deserves special mention. Consider for example the following example: diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/docs/config/converters/MapToLocalDateConverter.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/docs/config/converters/MapToLocalDateConverter.groovy index 22a0a54363d..5fb7d0a3f92 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/docs/config/converters/MapToLocalDateConverter.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/docs/config/converters/MapToLocalDateConverter.groovy @@ -15,18 +15,20 @@ */ package io.micronaut.docs.config.converters -// tag::imports[] +import io.micronaut.context.annotation.Prototype import io.micronaut.core.convert.ConversionContext + +// tag::imports[] + import io.micronaut.core.convert.ConversionService import io.micronaut.core.convert.TypeConverter -import jakarta.inject.Singleton import java.time.DateTimeException import java.time.LocalDate // end::imports[] // tag::class[] -@Singleton +@Prototype class MapToLocalDateConverter implements TypeConverter { // <1> @Override Optional convert(Map propertyMap, Class targetType, ConversionContext context) { diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/docs/http/server/bind/annotation/ShoppingCartRequestArgumentBinder.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/docs/http/server/bind/annotation/ShoppingCartRequestArgumentBinder.groovy index 7ee2304587b..7af744a118c 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/docs/http/server/bind/annotation/ShoppingCartRequestArgumentBinder.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/docs/http/server/bind/annotation/ShoppingCartRequestArgumentBinder.groovy @@ -17,11 +17,11 @@ import jakarta.inject.Singleton class ShoppingCartRequestArgumentBinder implements AnnotatedRequestArgumentBinder { //<1> - private final ConversionService conversionService + private final ConversionService conversionService private final JacksonObjectSerializer objectSerializer ShoppingCartRequestArgumentBinder( - ConversionService conversionService, + ConversionService conversionService, JacksonObjectSerializer objectSerializer) { this.conversionService = conversionService this.objectSerializer = objectSerializer diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/config/converters/MapToLocalDateConverter.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/config/converters/MapToLocalDateConverter.kt index 1b65c027717..c4b3c050665 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/config/converters/MapToLocalDateConverter.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/config/converters/MapToLocalDateConverter.kt @@ -16,6 +16,7 @@ package io.micronaut.docs.config.converters // tag::imports[] +import io.micronaut.context.annotation.Prototype import io.micronaut.core.convert.ConversionContext import io.micronaut.core.convert.ConversionService import io.micronaut.core.convert.TypeConverter @@ -26,7 +27,7 @@ import jakarta.inject.Singleton // end::imports[] // tag::class[] -@Singleton +@Prototype class MapToLocalDateConverter : TypeConverter, LocalDate> { // <1> override fun convert(propertyMap: Map<*, *>, targetType: Class, context: ConversionContext): Optional { val day = ConversionService.SHARED.convert(propertyMap["day"], Int::class.java) diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/http/server/bind/annotation/ShoppingCartRequestArgumentBinder.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/http/server/bind/annotation/ShoppingCartRequestArgumentBinder.kt index 24d995b295d..c6f7c3653b8 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/http/server/bind/annotation/ShoppingCartRequestArgumentBinder.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/http/server/bind/annotation/ShoppingCartRequestArgumentBinder.kt @@ -13,7 +13,7 @@ import jakarta.inject.Singleton @Singleton class ShoppingCartRequestArgumentBinder( - private val conversionService: ConversionService<*>, + private val conversionService: ConversionService, private val objectSerializer: JacksonObjectSerializer ) : AnnotatedRequestArgumentBinder { //<1> diff --git a/test-suite/src/test/java/io/micronaut/docs/config/converters/MapToLocalDateConverter.java b/test-suite/src/test/java/io/micronaut/docs/config/converters/MapToLocalDateConverter.java index 04696212fa8..9de76daf8ee 100644 --- a/test-suite/src/test/java/io/micronaut/docs/config/converters/MapToLocalDateConverter.java +++ b/test-suite/src/test/java/io/micronaut/docs/config/converters/MapToLocalDateConverter.java @@ -16,11 +16,12 @@ package io.micronaut.docs.config.converters; // tag::imports[] + +import io.micronaut.context.annotation.Prototype; import io.micronaut.core.convert.ConversionContext; import io.micronaut.core.convert.ConversionService; import io.micronaut.core.convert.TypeConverter; -import jakarta.inject.Singleton; import java.time.DateTimeException; import java.time.LocalDate; import java.util.Map; @@ -28,7 +29,7 @@ // end::imports[] // tag::class[] -@Singleton +@Prototype public class MapToLocalDateConverter implements TypeConverter { // <1> @Override public Optional convert(Map propertyMap, Class targetType, ConversionContext context) { diff --git a/test-suite/src/test/java/io/micronaut/docs/http/server/bind/annotation/ShoppingCartRequestArgumentBinder.java b/test-suite/src/test/java/io/micronaut/docs/http/server/bind/annotation/ShoppingCartRequestArgumentBinder.java index 202dd021199..57554138262 100644 --- a/test-suite/src/test/java/io/micronaut/docs/http/server/bind/annotation/ShoppingCartRequestArgumentBinder.java +++ b/test-suite/src/test/java/io/micronaut/docs/http/server/bind/annotation/ShoppingCartRequestArgumentBinder.java @@ -17,10 +17,10 @@ public class ShoppingCartRequestArgumentBinder implements AnnotatedRequestArgumentBinder { //<1> - private final ConversionService conversionService; + private final ConversionService conversionService; private final JacksonObjectSerializer objectSerializer; - public ShoppingCartRequestArgumentBinder(ConversionService conversionService, + public ShoppingCartRequestArgumentBinder(ConversionService conversionService, JacksonObjectSerializer objectSerializer) { this.conversionService = conversionService; this.objectSerializer = objectSerializer; diff --git a/websocket/src/main/java/io/micronaut/websocket/bind/WebSocketStateBinderRegistry.java b/websocket/src/main/java/io/micronaut/websocket/bind/WebSocketStateBinderRegistry.java index e9367d0fac8..b892f17cf27 100644 --- a/websocket/src/main/java/io/micronaut/websocket/bind/WebSocketStateBinderRegistry.java +++ b/websocket/src/main/java/io/micronaut/websocket/bind/WebSocketStateBinderRegistry.java @@ -51,12 +51,13 @@ public class WebSocketStateBinderRegistry implements ArgumentBinderRegistry sessionBinder = (context, source) -> () -> Optional.of(source.getSession()); this.byType.put(WebSocketSession.class, sessionBinder); - this.queryValueArgumentBinder = new QueryValueArgumentBinder<>(ConversionService.SHARED); + this.queryValueArgumentBinder = new QueryValueArgumentBinder<>(conversionService); } @Override