Merge branch '6.0.x'
# Conflicts: # framework-docs/modules/ROOT/pages/integration/observability.adoc # spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java
This commit is contained in:
commit
0bf85af8e9
|
@ -2,8 +2,8 @@
|
||||||
= Observability Support
|
= Observability Support
|
||||||
|
|
||||||
Micrometer defines an https://micrometer.io/docs/observation[Observation concept that enables both Metrics and Traces] in applications.
|
Micrometer defines an https://micrometer.io/docs/observation[Observation concept that enables both Metrics and Traces] in applications.
|
||||||
Metrics support offers a way to create timers, gauges or counters for collecting statistics about the runtime behavior of your application.
|
Metrics support offers a way to create timers, gauges, or counters for collecting statistics about the runtime behavior of your application.
|
||||||
Metrics can help you to track error rates, usage patterns, performance and more.
|
Metrics can help you to track error rates, usage patterns, performance, and more.
|
||||||
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
|
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
|
||||||
|
|
||||||
Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured.
|
Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured.
|
||||||
|
@ -39,16 +39,16 @@ https://micrometer.io/docs/concepts#_naming_meters[to the format preferred by th
|
||||||
[[observability.concepts]]
|
[[observability.concepts]]
|
||||||
== Micrometer Observation concepts
|
== Micrometer Observation concepts
|
||||||
|
|
||||||
If you are not familiar with Micrometer Observation, here's a quick summary of the new concepts you should know about.
|
If you are not familiar with Micrometer Observation, here's a quick summary of the concepts you should know about.
|
||||||
|
|
||||||
* `Observation` is the actual recording of something happening in your application. This is processed by `ObservationHandler` implementations to produce metrics or traces.
|
* `Observation` is the actual recording of something happening in your application. This is processed by `ObservationHandler` implementations to produce metrics or traces.
|
||||||
* Each observation has a corresponding `ObservationContext` implementation; this type holds all the relevant information for extracting metadata for it.
|
* Each observation has a corresponding `ObservationContext` implementation; this type holds all the relevant information for extracting metadata for it.
|
||||||
In the case of an HTTP server observation, the context implementation could hold the HTTP request, the HTTP response, any Exception thrown during processing...
|
In the case of an HTTP server observation, the context implementation could hold the HTTP request, the HTTP response, any exception thrown during processing, and so forth.
|
||||||
* Each `Observation` holds `KeyValues` metadata. In the case of a server HTTP observation, this could be the HTTP request method, the HTTP response status...
|
* Each `Observation` holds `KeyValues` metadata. In the case of an HTTP server observation, this could be the HTTP request method, the HTTP response status, and so forth.
|
||||||
This metadata is contributed by `ObservationConvention` implementations which should declare the type of `ObservationContext` they support.
|
This metadata is contributed by `ObservationConvention` implementations which should declare the type of `ObservationContext` they support.
|
||||||
* `KeyValues` are said to be "low cardinality" if there is a low, bounded number of possible values for the `KeyValue` tuple (HTTP method is a good example).
|
* `KeyValues` are said to be "low cardinality" if there is a low, bounded number of possible values for the `KeyValue` tuple (HTTP method is a good example).
|
||||||
Low cardinality values are contributed to metrics only.
|
Low cardinality values are contributed to metrics only.
|
||||||
High cardinality values are on the other hand unbounded (for example, HTTP request URIs) and are only contributed to Traces.
|
Conversely, "high cardinality" values are unbounded (for example, HTTP request URIs) and are only contributed to traces.
|
||||||
* An `ObservationDocumentation` documents all observations in a particular domain, listing the expected key names and their meaning.
|
* An `ObservationDocumentation` documents all observations in a particular domain, listing the expected key names and their meaning.
|
||||||
|
|
||||||
|
|
||||||
|
@ -66,16 +66,16 @@ Each instrumented component will provide two extension points:
|
||||||
=== Using custom Observation conventions
|
=== Using custom Observation conventions
|
||||||
|
|
||||||
Let's take the example of the Spring MVC "http.server.requests" metrics instrumentation with the `ServerHttpObservationFilter`.
|
Let's take the example of the Spring MVC "http.server.requests" metrics instrumentation with the `ServerHttpObservationFilter`.
|
||||||
This observation is using a `ServerRequestObservationConvention` with a `ServerRequestObservationContext`; custom conventions can be configured on the Servlet filter.
|
This observation uses a `ServerRequestObservationConvention` with a `ServerRequestObservationContext`; custom conventions can be configured on the Servlet filter.
|
||||||
If you would like to customize the metadata produced with the observation, you can extend the `DefaultServerRequestObservationConvention` for your requirements:
|
If you would like to customize the metadata produced with the observation, you can extend the `DefaultServerRequestObservationConvention` for your requirements:
|
||||||
|
|
||||||
include-code::./ExtendedServerRequestObservationConvention[]
|
include-code::./ExtendedServerRequestObservationConvention[]
|
||||||
|
|
||||||
If you want full control, you can then implement the entire convention contract for the observation you're interested in:
|
If you want full control, you can implement the entire convention contract for the observation you're interested in:
|
||||||
|
|
||||||
include-code::./CustomServerRequestObservationConvention[]
|
include-code::./CustomServerRequestObservationConvention[]
|
||||||
|
|
||||||
You can also achieve similar goals using a custom `ObservationFilter` - adding or removing key values for an observation.
|
You can also achieve similar goals using a custom `ObservationFilter` – adding or removing key values for an observation.
|
||||||
Filters do not replace the default convention and are used as a post-processing component.
|
Filters do not replace the default convention and are used as a post-processing component.
|
||||||
|
|
||||||
include-code::./ServerRequestObservationFilter[]
|
include-code::./ServerRequestObservationFilter[]
|
||||||
|
@ -111,22 +111,22 @@ By default, the following `KeyValues` are created:
|
||||||
[[observability.http-server]]
|
[[observability.http-server]]
|
||||||
== HTTP Server instrumentation
|
== HTTP Server instrumentation
|
||||||
|
|
||||||
HTTP server exchanges observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
|
HTTP server exchange observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
|
||||||
|
|
||||||
[[observability.http-server.servlet]]
|
[[observability.http-server.servlet]]
|
||||||
=== Servlet applications
|
=== Servlet applications
|
||||||
|
|
||||||
Applications need to configure the `org.springframework.web.filter.ServerHttpObservationFilter` Servlet filter in their application.
|
Applications need to configure the `org.springframework.web.filter.ServerHttpObservationFilter` Servlet filter in their application.
|
||||||
It is using the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
It uses the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||||
|
|
||||||
This will only record an observation as an error if the `Exception` has not been handled by the web Framework and has bubbled up to the Servlet filter.
|
This will only record an observation as an error if the `Exception` has not been handled by the web framework and has bubbled up to the Servlet filter.
|
||||||
Typically, all exceptions handled by Spring MVC's `@ExceptionHandler` and xref:web/webmvc/mvc-ann-rest-exceptions.adoc[`ProblemDetail` support] will not be recorded with the observation.
|
Typically, all exceptions handled by Spring MVC's `@ExceptionHandler` and xref:web/webmvc/mvc-ann-rest-exceptions.adoc[`ProblemDetail` support] will not be recorded with the observation.
|
||||||
You can, at any point during request processing, set the error field on the `ObservationContext` yourself:
|
You can, at any point during request processing, set the error field on the `ObservationContext` yourself:
|
||||||
|
|
||||||
include-code::./UserController[]
|
include-code::./UserController[]
|
||||||
|
|
||||||
NOTE: Because the instrumentation is done at the Servlet Filter level, the observation scope only covers the filters ordered after this one as well as the handling of the request.
|
NOTE: Because the instrumentation is done at the Servlet Filter level, the observation scope only covers the filters ordered after this one as well as the handling of the request.
|
||||||
Typically, the Servlet container error handling is done at a lower level and won't have any active observation nor span.
|
Typically, Servlet container error handling is performed at a lower level and won't have any active observation or span.
|
||||||
For this use case, a container-specific implementation is required, such as a `org.apache.catalina.Valve` for Tomcat; this is outside of the scope of this project.
|
For this use case, a container-specific implementation is required, such as a `org.apache.catalina.Valve` for Tomcat; this is outside of the scope of this project.
|
||||||
|
|
||||||
By default, the following `KeyValues` are created:
|
By default, the following `KeyValues` are created:
|
||||||
|
@ -189,9 +189,9 @@ By default, the following `KeyValues` are created:
|
||||||
|
|
||||||
|
|
||||||
[[observability.http-client]]
|
[[observability.http-client]]
|
||||||
== HTTP Client instrumentation
|
== HTTP Client Instrumentation
|
||||||
|
|
||||||
HTTP client exchanges observations are created with the name `"http.client.requests"` for blocking and reactive clients.
|
HTTP client exchange observations are created with the name `"http.client.requests"` for blocking and reactive clients.
|
||||||
Unlike their server counterparts, the instrumentation is implemented directly in the client so the only required step is to configure an `ObservationRegistry` on the client.
|
Unlike their server counterparts, the instrumentation is implemented directly in the client so the only required step is to configure an `ObservationRegistry` on the client.
|
||||||
|
|
||||||
[[observability.http-client.resttemplate]]
|
[[observability.http-client.resttemplate]]
|
||||||
|
@ -200,7 +200,7 @@ Unlike their server counterparts, the instrumentation is implemented directly in
|
||||||
Applications must configure an `ObservationRegistry` on `RestTemplate` instances to enable the instrumentation; without that, observations are "no-ops".
|
Applications must configure an `ObservationRegistry` on `RestTemplate` instances to enable the instrumentation; without that, observations are "no-ops".
|
||||||
Spring Boot will auto-configure `RestTemplateBuilder` beans with the observation registry already set.
|
Spring Boot will auto-configure `RestTemplateBuilder` beans with the observation registry already set.
|
||||||
|
|
||||||
Instrumentation is using the `org.springframework.http.client.observation.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
Instrumentation uses the `org.springframework.http.client.observation.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
||||||
|
|
||||||
.Low cardinality Keys
|
.Low cardinality Keys
|
||||||
[cols="a,a"]
|
[cols="a,a"]
|
||||||
|
@ -229,7 +229,7 @@ Instrumentation is using the `org.springframework.http.client.observation.Client
|
||||||
Applications must configure an `ObservationRegistry` on the `WebClient` builder to enable the instrumentation; without that, observations are "no-ops".
|
Applications must configure an `ObservationRegistry` on the `WebClient` builder to enable the instrumentation; without that, observations are "no-ops".
|
||||||
Spring Boot will auto-configure `WebClient.Builder` beans with the observation registry already set.
|
Spring Boot will auto-configure `WebClient.Builder` beans with the observation registry already set.
|
||||||
|
|
||||||
Instrumentation is using the `org.springframework.web.reactive.function.client.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
Instrumentation uses the `org.springframework.web.reactive.function.client.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
||||||
|
|
||||||
.Low cardinality Keys
|
.Low cardinality Keys
|
||||||
[cols="a,a"]
|
[cols="a,a"]
|
||||||
|
|
|
@ -54,6 +54,7 @@ import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
import static org.assertj.core.api.Assertions.entry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link DefaultConversionService}.
|
* Unit tests for {@link DefaultConversionService}.
|
||||||
|
@ -324,8 +325,8 @@ class DefaultConversionServiceTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void numberToNumberNotSupportedNumber() {
|
void numberToNumberNotSupportedNumber() {
|
||||||
assertThatExceptionOfType(ConversionFailedException.class).isThrownBy(() ->
|
assertThatExceptionOfType(ConversionFailedException.class)
|
||||||
conversionService.convert(1, CustomNumber.class));
|
.isThrownBy(() -> conversionService.convert(1, CustomNumber.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -342,28 +343,32 @@ class DefaultConversionServiceTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertArrayToCollectionInterface() {
|
void convertArrayToCollectionInterface() {
|
||||||
Collection<?> result = conversionService.convert(new String[] {"1", "2", "3"}, Collection.class);
|
@SuppressWarnings("unchecked")
|
||||||
|
Collection<String> result = conversionService.convert(new String[] {"1", "2", "3"}, Collection.class);
|
||||||
assertThat(result).isEqualTo(List.of("1", "2", "3"));
|
assertThat(result).isEqualTo(List.of("1", "2", "3"));
|
||||||
|
assertThat(result).isExactlyInstanceOf(ArrayList.class).containsExactly("1", "2", "3");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertArrayToSetInterface() {
|
void convertArrayToSetInterface() {
|
||||||
Collection<?> result = conversionService.convert(new String[] {"1", "2", "3"}, Set.class);
|
@SuppressWarnings("unchecked")
|
||||||
assertThat(result).isEqualTo(Set.of("1", "2", "3"));
|
Collection<String> result = conversionService.convert(new String[] {"1", "2", "3"}, Set.class);
|
||||||
|
assertThat(result).isExactlyInstanceOf(LinkedHashSet.class).containsExactly("1", "2", "3");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertArrayToListInterface() {
|
void convertArrayToListInterface() {
|
||||||
List<?> result = conversionService.convert(new String[] {"1", "2", "3"}, List.class);
|
@SuppressWarnings("unchecked")
|
||||||
assertThat(result).isEqualTo(List.of("1", "2", "3"));
|
List<String> result = conversionService.convert(new String[] {"1", "2", "3"}, List.class);
|
||||||
|
assertThat(result).isExactlyInstanceOf(ArrayList.class).containsExactly("1", "2", "3");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertArrayToCollectionGenericTypeConversion() throws Exception {
|
void convertArrayToCollectionGenericTypeConversion() throws Exception {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<Integer> result = (List<Integer>) conversionService.convert(new String[] {"1", "2", "3"}, TypeDescriptor
|
List<Integer> result = (List<Integer>) conversionService.convert(new String[] {"1", "2", "3"},
|
||||||
.valueOf(String[].class), new TypeDescriptor(getClass().getDeclaredField("genericList")));
|
TypeDescriptor.valueOf(String[].class), new TypeDescriptor(getClass().getDeclaredField("genericList")));
|
||||||
assertThat(result).isEqualTo(List.of(1, 2, 3));
|
assertThat(result).isExactlyInstanceOf(ArrayList.class).containsExactly(1, 2, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -371,15 +376,13 @@ class DefaultConversionServiceTests {
|
||||||
String[] source = {"1", "3", "4"};
|
String[] source = {"1", "3", "4"};
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Stream<Integer> result = (Stream<Integer>) this.conversionService.convert(source,
|
Stream<Integer> result = (Stream<Integer>) this.conversionService.convert(source,
|
||||||
TypeDescriptor.valueOf(String[].class),
|
TypeDescriptor.valueOf(String[].class), new TypeDescriptor(getClass().getDeclaredField("genericStream")));
|
||||||
new TypeDescriptor(getClass().getDeclaredField("genericStream")));
|
|
||||||
assertThat(result).containsExactly(1, 3, 4);
|
assertThat(result).containsExactly(1, 3, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void spr7766() throws Exception {
|
void spr7766() throws Exception {
|
||||||
ConverterRegistry registry = (conversionService);
|
conversionService.addConverter(new ColorConverter());
|
||||||
registry.addConverter(new ColorConverter());
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<Color> colors = (List<Color>) conversionService.convert(new String[] {"ffffff", "#000000"},
|
List<Color> colors = (List<Color>) conversionService.convert(new String[] {"ffffff", "#000000"},
|
||||||
TypeDescriptor.valueOf(String[].class),
|
TypeDescriptor.valueOf(String[].class),
|
||||||
|
@ -389,14 +392,15 @@ class DefaultConversionServiceTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertArrayToCollectionImpl() {
|
void convertArrayToCollectionImpl() {
|
||||||
ArrayList<?> result = conversionService.convert(new String[] {"1", "2", "3"}, ArrayList.class);
|
@SuppressWarnings("unchecked")
|
||||||
assertThat(result).isEqualTo(List.of("1", "2", "3"));
|
ArrayList<String> result = conversionService.convert(new String[] {"1", "2", "3"}, ArrayList.class);
|
||||||
|
assertThat(result).isExactlyInstanceOf(ArrayList.class).containsExactly("1", "2", "3");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertArrayToAbstractCollection() {
|
void convertArrayToAbstractCollection() {
|
||||||
assertThatExceptionOfType(ConversionFailedException.class).isThrownBy(() ->
|
assertThatExceptionOfType(ConversionFailedException.class)
|
||||||
conversionService.convert(new String[]{"1", "2", "3"}, AbstractList.class));
|
.isThrownBy(() -> conversionService.convert(new String[]{"1", "2", "3"}, AbstractList.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -465,8 +469,7 @@ class DefaultConversionServiceTests {
|
||||||
@Test
|
@Test
|
||||||
void convertObjectToArray() {
|
void convertObjectToArray() {
|
||||||
Object[] result = conversionService.convert(3L, Object[].class);
|
Object[] result = conversionService.convert(3L, Object[].class);
|
||||||
assertThat(result).hasSize(1);
|
assertThat(result).containsExactly(3L);
|
||||||
assertThat(result[0]).isEqualTo(3L);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -506,15 +509,17 @@ class DefaultConversionServiceTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertStringToCollection() {
|
void convertStringToCollection() {
|
||||||
List<?> result = conversionService.convert("1,2,3", List.class);
|
@SuppressWarnings("unchecked")
|
||||||
assertThat(result).isEqualTo(List.of("1", "2", "3"));
|
List<String> result = conversionService.convert("1,2,3", List.class);
|
||||||
|
assertThat(result).containsExactly("1", "2", "3");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertStringToCollectionWithElementConversion() throws Exception {
|
void convertStringToCollectionWithElementConversion() throws Exception {
|
||||||
List<?> result = (List<?>) conversionService.convert("1,2,3", TypeDescriptor.valueOf(String.class),
|
@SuppressWarnings("unchecked")
|
||||||
|
List<Integer> result = (List<Integer>) conversionService.convert("1,2,3", TypeDescriptor.valueOf(String.class),
|
||||||
new TypeDescriptor(getClass().getField("genericList")));
|
new TypeDescriptor(getClass().getField("genericList")));
|
||||||
assertThat(result).isEqualTo(List.of(1, 2, 3));
|
assertThat(result).containsExactly(1, 2, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -539,17 +544,14 @@ class DefaultConversionServiceTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertCollectionToObjectAssignableTarget() throws Exception {
|
void convertCollectionToObjectAssignableTarget() throws Exception {
|
||||||
Collection<String> source = new ArrayList<>();
|
Collection<String> source = List.of("foo");
|
||||||
source.add("foo");
|
|
||||||
Object result = conversionService.convert(source, new TypeDescriptor(getClass().getField("assignableTarget")));
|
Object result = conversionService.convert(source, new TypeDescriptor(getClass().getField("assignableTarget")));
|
||||||
assertThat(result).isEqualTo(source);
|
assertThat(result).isSameAs(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertCollectionToObjectWithCustomConverter() {
|
void convertCollectionToObjectWithCustomConverter() {
|
||||||
List<String> source = new ArrayList<>();
|
List<String> source = List.of("A", "B");
|
||||||
source.add("A");
|
|
||||||
source.add("B");
|
|
||||||
conversionService.addConverter(List.class, ListWrapper.class, ListWrapper::new);
|
conversionService.addConverter(List.class, ListWrapper.class, ListWrapper::new);
|
||||||
ListWrapper result = conversionService.convert(source, ListWrapper.class);
|
ListWrapper result = conversionService.convert(source, ListWrapper.class);
|
||||||
assertThat(result.getList()).isSameAs(source);
|
assertThat(result.getList()).isSameAs(source);
|
||||||
|
@ -557,8 +559,9 @@ class DefaultConversionServiceTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertObjectToCollection() {
|
void convertObjectToCollection() {
|
||||||
List<?> result = conversionService.convert(3L, List.class);
|
@SuppressWarnings("unchecked")
|
||||||
assertThat(result).isEqualTo(List.of(3L));
|
List<Long> result = conversionService.convert(3L, List.class);
|
||||||
|
assertThat(result).containsExactly(3L);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -607,7 +610,7 @@ class DefaultConversionServiceTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertByteArrayToWrapperArray() {
|
void convertByteArrayToWrapperArray() {
|
||||||
byte[] byteArray = new byte[] {1, 2, 3};
|
byte[] byteArray = {1, 2, 3};
|
||||||
Byte[] converted = conversionService.convert(byteArray, Byte[].class);
|
Byte[] converted = conversionService.convert(byteArray, Byte[].class);
|
||||||
assertThat(converted).isEqualTo(new Byte[]{1, 2, 3});
|
assertThat(converted).isEqualTo(new Byte[]{1, 2, 3});
|
||||||
}
|
}
|
||||||
|
@ -667,21 +670,18 @@ class DefaultConversionServiceTests {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<Integer> bar = (List<Integer>) conversionService.convert(null,
|
List<Integer> bar = (List<Integer>) conversionService.convert(null,
|
||||||
TypeDescriptor.valueOf(LinkedHashSet.class), new TypeDescriptor(getClass().getField("genericList")));
|
TypeDescriptor.valueOf(LinkedHashSet.class), new TypeDescriptor(getClass().getField("genericList")));
|
||||||
assertThat((Object) bar).isNull();
|
assertThat(bar).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
void convertCollectionToCollectionNotGeneric() {
|
void convertCollectionToCollectionNotGeneric() {
|
||||||
Set<String> foo = new LinkedHashSet<>();
|
Set<String> foo = new LinkedHashSet<>();
|
||||||
foo.add("1");
|
foo.add("1");
|
||||||
foo.add("2");
|
foo.add("2");
|
||||||
foo.add("3");
|
foo.add("3");
|
||||||
List bar = (List) conversionService.convert(foo, TypeDescriptor.valueOf(LinkedHashSet.class), TypeDescriptor
|
List bar = (List) conversionService.convert(foo, TypeDescriptor.valueOf(LinkedHashSet.class), TypeDescriptor.valueOf(List.class));
|
||||||
.valueOf(List.class));
|
assertThat(bar).containsExactly("1", "2", "3");
|
||||||
assertThat(bar.get(0)).isEqualTo("1");
|
|
||||||
assertThat(bar.get(1)).isEqualTo("2");
|
|
||||||
assertThat(bar.get(2)).isEqualTo("3");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -694,34 +694,25 @@ class DefaultConversionServiceTests {
|
||||||
Collection values = map.values();
|
Collection values = map.values();
|
||||||
List<Integer> bar = (List<Integer>) conversionService.convert(values,
|
List<Integer> bar = (List<Integer>) conversionService.convert(values,
|
||||||
TypeDescriptor.forObject(values), new TypeDescriptor(getClass().getField("genericList")));
|
TypeDescriptor.forObject(values), new TypeDescriptor(getClass().getField("genericList")));
|
||||||
assertThat(bar).hasSize(3);
|
assertThat(bar).containsExactly(1, 2, 3);
|
||||||
assertThat(bar.get(0)).isEqualTo(1);
|
|
||||||
assertThat(bar.get(1)).isEqualTo(2);
|
|
||||||
assertThat(bar.get(2)).isEqualTo(3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void collection() {
|
void collection() {
|
||||||
List<String> strings = new ArrayList<>();
|
List<String> strings = List.of("3", "9");
|
||||||
strings.add("3");
|
|
||||||
strings.add("9");
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<Integer> integers = (List<Integer>) conversionService.convert(strings,
|
List<Integer> integers = (List<Integer>) conversionService.convert(strings,
|
||||||
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Integer.class)));
|
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Integer.class)));
|
||||||
assertThat(integers.get(0)).isEqualTo(3);
|
assertThat(integers).containsExactly(3, 9);
|
||||||
assertThat(integers.get(1)).isEqualTo(9);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertMapToMap() throws Exception {
|
void convertMapToMap() throws Exception {
|
||||||
Map<String, String> foo = new HashMap<>();
|
Map<String, String> foo = Map.of("1", "BAR", "2", "BAZ");
|
||||||
foo.put("1", "BAR");
|
|
||||||
foo.put("2", "BAZ");
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<Integer, Foo> map = (Map<Integer, Foo>) conversionService.convert(foo,
|
Map<Integer, Foo> map = (Map<Integer, Foo>) conversionService.convert(foo,
|
||||||
TypeDescriptor.forObject(foo), new TypeDescriptor(getClass().getField("genericMap")));
|
TypeDescriptor.forObject(foo), new TypeDescriptor(getClass().getField("genericMap")));
|
||||||
assertThat(map.get(1)).isEqualTo(Foo.BAR);
|
assertThat(map).contains(entry(1, Foo.BAR), entry(2, Foo.BAZ));
|
||||||
assertThat(map.get(2)).isEqualTo(Foo.BAZ);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -729,8 +720,9 @@ class DefaultConversionServiceTests {
|
||||||
Map<String, Integer> hashMap = new LinkedHashMap<>();
|
Map<String, Integer> hashMap = new LinkedHashMap<>();
|
||||||
hashMap.put("1", 1);
|
hashMap.put("1", 1);
|
||||||
hashMap.put("2", 2);
|
hashMap.put("2", 2);
|
||||||
List<?> converted = conversionService.convert(hashMap.values(), List.class);
|
@SuppressWarnings("unchecked")
|
||||||
assertThat(converted).isEqualTo(List.of(1, 2));
|
List<Integer> converted = conversionService.convert(hashMap.values(), List.class);
|
||||||
|
assertThat(converted).containsExactly(1, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -741,8 +733,7 @@ class DefaultConversionServiceTests {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<Integer, Integer> integers = (Map<Integer, Integer>) conversionService.convert(strings,
|
Map<Integer, Integer> integers = (Map<Integer, Integer>) conversionService.convert(strings,
|
||||||
TypeDescriptor.map(Map.class, TypeDescriptor.valueOf(Integer.class), TypeDescriptor.valueOf(Integer.class)));
|
TypeDescriptor.map(Map.class, TypeDescriptor.valueOf(Integer.class), TypeDescriptor.valueOf(Integer.class)));
|
||||||
assertThat(integers.get(3)).isEqualTo(9);
|
assertThat(integers).contains(entry(3, 9), entry(6, 31));
|
||||||
assertThat(integers.get(6)).isEqualTo(31);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -751,25 +742,25 @@ class DefaultConversionServiceTests {
|
||||||
foo.setProperty("1", "BAR");
|
foo.setProperty("1", "BAR");
|
||||||
foo.setProperty("2", "BAZ");
|
foo.setProperty("2", "BAZ");
|
||||||
String result = conversionService.convert(foo, String.class);
|
String result = conversionService.convert(foo, String.class);
|
||||||
assertThat(result).contains("1=BAR");
|
assertThat(result).contains("1=BAR", "2=BAZ");
|
||||||
assertThat(result).contains("2=BAZ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertStringToProperties() {
|
void convertStringToProperties() {
|
||||||
Properties result = conversionService.convert("a=b\nc=2\nd=", Properties.class);
|
Properties result = conversionService.convert("""
|
||||||
assertThat(result).hasSize(3);
|
a=b
|
||||||
assertThat(result.getProperty("a")).isEqualTo("b");
|
c=2
|
||||||
assertThat(result.getProperty("c")).isEqualTo("2");
|
d=""", Properties.class);
|
||||||
assertThat(result.getProperty("d")).isEmpty();
|
assertThat(result).contains(entry("a", "b"), entry("c", "2"), entry("d", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertStringToPropertiesWithSpaces() {
|
void convertStringToPropertiesWithLeadingSpaces() {
|
||||||
Properties result = conversionService.convert(" foo=bar\n bar=baz\n baz=boop", Properties.class);
|
Properties result = conversionService.convert("""
|
||||||
assertThat(result.get("foo")).isEqualTo("bar");
|
\s foo=bar
|
||||||
assertThat(result.get("bar")).isEqualTo("baz");
|
\s bar=baz
|
||||||
assertThat(result.get("baz")).isEqualTo("boop");
|
\s baz=boo""", Properties.class);
|
||||||
|
assertThat(result).contains(entry("foo", "bar"), entry("bar", "baz"), entry("baz", "boo"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// generic object conversion
|
// generic object conversion
|
||||||
|
@ -838,8 +829,8 @@ class DefaultConversionServiceTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertObjectToObjectNoValueOfMethodOrConstructor() {
|
void convertObjectToObjectNoValueOfMethodOrConstructor() {
|
||||||
assertThatExceptionOfType(ConverterNotFoundException.class).isThrownBy(() ->
|
assertThatExceptionOfType(ConverterNotFoundException.class)
|
||||||
conversionService.convert(3L, SSN.class));
|
.isThrownBy(() -> conversionService.convert(3L, SSN.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -870,21 +861,20 @@ class DefaultConversionServiceTests {
|
||||||
@Test
|
@Test
|
||||||
void convertStringToCharArray() {
|
void convertStringToCharArray() {
|
||||||
char[] converted = conversionService.convert("a,b,c", char[].class);
|
char[] converted = conversionService.convert("a,b,c", char[].class);
|
||||||
assertThat(converted).isEqualTo(new char[]{'a', 'b', 'c'});
|
assertThat(converted).containsExactly('a', 'b', 'c');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void convertStringToCustomCharArray() {
|
void convertStringToCustomCharArray() {
|
||||||
conversionService.addConverter(String.class, char[].class, String::toCharArray);
|
conversionService.addConverter(String.class, char[].class, String::toCharArray);
|
||||||
char[] converted = conversionService.convert("abc", char[].class);
|
char[] converted = conversionService.convert("abc", char[].class);
|
||||||
assertThat(converted).isEqualTo(new char[] {'a', 'b', 'c'});
|
assertThat(converted).containsExactly('a', 'b', 'c');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
void multidimensionalArrayToListConversionShouldConvertEntriesCorrectly() {
|
void multidimensionalArrayToListConversionShouldConvertEntriesCorrectly() {
|
||||||
String[][] grid = new String[][] {new String[] {"1", "2", "3", "4"}, new String[] {"5", "6", "7", "8"},
|
String[][] grid = new String[][] {{"1", "2", "3", "4"}, {"5", "6", "7", "8"}, {"9", "10", "11", "12"}};
|
||||||
new String[] {"9", "10", "11", "12"}};
|
|
||||||
List<String[]> converted = conversionService.convert(grid, List.class);
|
List<String[]> converted = conversionService.convert(grid, List.class);
|
||||||
String[][] convertedBack = conversionService.convert(converted, String[][].class);
|
String[][] convertedBack = conversionService.convert(converted, String[][].class);
|
||||||
assertThat(convertedBack).isEqualTo(grid);
|
assertThat(convertedBack).isEqualTo(grid);
|
||||||
|
@ -893,10 +883,10 @@ class DefaultConversionServiceTests {
|
||||||
@Test
|
@Test
|
||||||
void convertCannotOptimizeArray() {
|
void convertCannotOptimizeArray() {
|
||||||
conversionService.addConverter(Byte.class, Byte.class, source -> (byte) (source + 1));
|
conversionService.addConverter(Byte.class, Byte.class, source -> (byte) (source + 1));
|
||||||
byte[] byteArray = new byte[] {1, 2, 3};
|
byte[] byteArray = {1, 2, 3};
|
||||||
byte[] converted = conversionService.convert(byteArray, byte[].class);
|
byte[] converted = conversionService.convert(byteArray, byte[].class);
|
||||||
assertThat(converted).isNotSameAs(byteArray);
|
assertThat(converted).isNotSameAs(byteArray);
|
||||||
assertThat(converted).isEqualTo(new byte[]{2, 3, 4});
|
assertThat(converted).containsExactly(2, 3, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue