Manually configure Jackson Module for Spring WebTestClient - spring

Prerequisites
Spring Boot: 2.0.3.RELEASE
Spring REST Docs: 2.0.1.RELEASE
Spring Auto REST Docs: 2.0.2-SNAPSHOT - Which is a local build of the current master.
The GeoJsonModule is configured in the Main Application:
import org.springframework.data.mongodb.core.geo.GeoJsonModule;
#SpringBootApplication
public class MyApplication {
[...]
#Bean
public GeoJsonModule registerGeoJsonModule() {
return new GeoJsonModule();
}
[...]
}
I am using Spring Auto REST Docs and therefore cannot use the #AutoConfigure-Annotation (Or atleast I don't know how).
Current Implementation
I configure the WebTestClient like so:
WebTestClient
.bindToApplicationContext(context)
.configureClient()
.filter(
WebTestClientRestDocumentation
.documentationConfiguration(restDocumentation)
.snippets()
.withDefaults(
WebTestClientInitializer.prepareSnippets(context),
CliDocumentation.curlRequest(),
HttpDocumentation.httpRequest(),
HttpDocumentation.httpResponse(),
AutoDocumentation.requestFields(),
AutoDocumentation.responseFields(),
AutoDocumentation.pathParameters(),
AutoDocumentation.requestParameters(),
AutoDocumentation.description(),
AutoDocumentation.methodAndPath(),
AutoDocumentation.section()
)
)
.build();
However when using the WebTestClient like this I get the following error:
org.springframework.core.codec.CodecException: Type definition error: [simple type, class org.springframework.data.mongodb.core.geo.GeoJsonPoint]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.mongodb.core.geo.GeoJsonPoint` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.example.MyClass["location"])
So I looked around and found, that the WebTestClientAutoConfiguration and SpringBootWebTestClientBuilderCustomizer are additionally customizing Codecs.
Changing the initialization to include the customizers fixes the deserialization issue and it works.
Collection<CodecCustomizer> customizers = context.getBeansOfType(CodecCustomizer.class).values();
SpringBootWebTestClientBuilderCustomizer builderCustomizer = new SpringBootWebTestClientBuilderCustomizer(customizers);
builderCustomizer.customize(builder);
builder.build();
However I do not know if this is the correct way of configuring the WebTestClient and if it works entirely or if something is still broken I just did not discover yet.
Somehow I don't think that this is the intended way and I would like to know how the WebTestClient can be configured properly.

Related

Multiple FeignClients with different Configuration

I am using FeignClient to call external components, so far without any issue.
When trying to create a new FeignClient in the same package that should handle as a response a complex Object including LocalDateTime as a property it failed with the
Expected BEGIN_ARRAY but was STRING at line 1 column {whatever column the LocalDateTime was}
At first I thought that adding the #JsonDeserializer(LocalDateTimeDeserializer.class) above the LocalDateTime property would work, but it didn't, forcing me to understand that FeignClient Deserialization had an issue.
I implemented a Custom Decoder to handle this, registering the JavaTimeModule in ObjectMapper which works.
#Configuration
public class AdapterConfiguration {
#Bean
public Decoder feignDecoder(ObjectProvider<HttpMessageConverterCustomizer> customizers) {
MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(JsonMapper.builder()
.disable(
MapperFeature.USE_ANNOTATIONS)
.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.addModule(
new JavaTimeModule())
.build());
HttpMessageConverters httpMessageConverters = new HttpMessageConverters(jacksonConverter);
org.springframework.beans.factory.ObjectFactory<HttpMessageConverters> messageConverters =
() -> httpMessageConverters;
return new ResponseEntityDecoder(new SpringDecoder(messageConverters, customizers));
}
However, it forces the other FeignClients to use that Decoder, which is unwanted because it fails parsing correct responses (throws DecoderException).
After carefully examining the documentation, it states that:
FooConfiguration does not need to be annotated with #Configuration. However, if it is, then take care to exclude it from any #ComponentScan that would otherwise include this configuration as it will become the default source for feign.Decoder, feign.Encoder, feign.Contract, etc., when specified. This can be avoided by putting it in a separate, non-overlapping package from any #ComponentScan or #SpringBootApplication, or it can be explicitly excluded in #ComponentScan.
And thus, I removed the #Configuration annotation and added the config in the #FeignClient that I wanted. However, this client does not use the decoder properly.
My current Structure is:
- FeignConfig.java, with Custom Decoder
- FeignClientA.java (with the need of custom decoder) with the following specifications:
#FeignClient(
name = "feign-client-A",
url = "${myUrl}", configuration = {FeignConfig.class})
- FeignClientB (which **works **with the default decoder and **NOT **with the Custom Decoder) with the following specifications:
#FeignClient(name = "feign-client-B", url = "${myOtherUrl}")
I cannot understand what I am missing and why I cant customize each feign client to have separate decoders.

What does Spring use to do deserialization?

I have the following class:
data class Thing(val lines: List<String>)
The JSON representation is:
{
"lines": [
"something",
"something else"
]
}
Spring WebFlux can successfully parse this with the following:
// Parse the JSON as an object and return it.
request -> ServerResponse.ok().body(request.bodyToMono(Thing::class.java)
However, using Jackson directly with either of the following techniques fails:
val mapper = ObjectMapper()
val item = mapper.readValue<Thing>("""{"lines":["something","something else"]}""")
ServerResponse.ok().body(request.bodyToMono(Map::class.java)
.map { map ->
val mapper = ObjectMapper()
val tmp = mapper.convertValue(map, Thing::class.java)
}
The error is:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `Thing` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
I thought that Spring was using Jackson to do its transformation. And I thought that Jackson could cope with such basic transformations from JSON -> POJOs. Using a #JsonDeserialize class obviously works. So why does the first example work and the second not?
Spring uses Jackson but it registers custom handlers and modules when it creates its default instance of ObjectMapper.
Jackson has special handling for constructors that take a single argument. This was done to support classes like UUID and URI. To instruct Jackson to not use this technique, annotate your constructor with #JsonCreator.
data class Thing #JsonCreator constructor(val lines: List<String>)
I have not reviewed Spring's reactive code so I do not know what or if it does something to disable Jackson's special handling.

How to set header variables in GraphQL-SPQR

I'm running a GraphQL API using GraphQL-SPQR and Spring Boot.
At the moment, I am throwing RuntimeExceptions to return GraphQL errors. I have a customExceptionHandler that implements DataFetcherExceptionHandler that returns errors in the correct format, as shown below:
class CustomExceptionHandler : DataFetcherExceptionHandler {
override fun onException(handlerParameters: DataFetcherExceptionHandlerParameters?): DataFetcherExceptionHandlerResult {
// get exception
var exception = handlerParameters?.exception
val locations = listOf(handlerParameters?.sourceLocation)
val path = listOf(handlerParameters?.path?.segmentName)
// create a GraphQLError from your exception
if (exception !is GraphQLError) {
exception = CustomGraphQLError(exception?.localizedMessage, locations, path)
}
// cast to GraphQLError
exception as CustomGraphQLError
exception.locations = locations
exception.path = path
val errors = listOf<GraphQLError>(exception)
return DataFetcherExceptionHandlerResult.Builder().errors(errors).build()
}
}
I use the CustomExceptionHandler as follows (in my main application class):
#Bean
fun graphQL(schema: GraphQLSchema): GraphQL {
return GraphQL.newGraphQL(schema)
.queryExecutionStrategy(AsyncExecutionStrategy(CustomExceptionHandler()))
.mutationExecutionStrategy(AsyncSerialExecutionStrategy(CustomExceptionHandler()))
.build()
}
I'd like to set a header variable for a UUID that corresponds to the exception, for logging purposes. How would I do that?
Even better, is it possible to create a Spring Bean that puts the UUID in the header for all queries and mutations?
Thanks!
when you're using spring boot, there's two options:
you're using the spring boot graphql spqr starter (which brings it's own controller to handle all graphQL requests)
you're using plain graphql-spqr and have your own controller to handle GraphQL requests
In any case, you've got a few options:
Making your CustomExceptionHandler a Spring Bean and Autowiring HttpServletResponse
That would probably be the easiest way to go - and it would probably work in any case: You could simply make your CustomExceptionHandler a Spring bean and have it autowire the HttpServletRequest - in the handler method, you could then set it to whatever you would like it to be. Here's some dummy code in Java (sorry, I am not proficient enough in Kotlin):
#Component
class CustomExceptionHandler implements DataFetcherExceptionHandler {
private final HttpServletResponse response;
public CustomExceptionHandler(HttpServletResponse response) {
this.response = response;
}
#Override
public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
response.setHeader("X-Request-ID", UUID.randomUUID().toString());
// ... your actual error handling code
}
}
This is going to work because spring will realise that HttpServletRequest differs for each request. It will therefore inject a dynamic proxy into your error handler that will point to the actual HttpServletResponse instance for every request.
I would argue, that it's not the most elegant way, but it will certainly solve your problem.
for the graphql-spqr spring boot starter
There's a default controller implementation that is used in projects using this starter. That controller will handle every graphql request that you receive. You can customise it, by implementing your own GraphQLExecutor and making it a spring bean. That executor is responsible to call the GraphQL engine, pass the parameters in and output the response. Here's the default implementation, that you might want to base your work on.
Similarly to the previous solution, you could autowire the HttpServletResponse in that class and set a HTTP Response header.
That solution would allow you to decide, if you want to set a request id in all cases, or just in specific error cases. (graphql.execute returns an object from which you can get the information if and what errors existed)
when using graphql-spqr without the spring boot starter
Locate your GraphQL controller, add an argument to that method of type HttpServletRequest - and then add headers to that as you prefer (see previous section on some more specific suggestions)

How to fix java.lang.IllegalStateException when using spring-data-neo4j

I have a simple test project where checking spring-data-neo4j with spring boot version: 2.1.0.RELEASE (https://github.com/tomkasp/neo4j-playground/blob/master/src/main/java/com/athleticspot/neo4jplayground/domain/AthleteRepository.java)
spring-data-neo4j (version: 5.1.4.RELEASE) dependency is injected by spring-boot-starter-data-neo4j.
My goal was to create a repository method which fetches data with containing and ingnorecase functionalities. In order to do that I've created below method within repository:
public interface AthleteRepository extends CrudRepository<Athlete, Long> {
List<Athlete> findByNameContainingIgnoreCase(String name);
}
When I run above functions I'm getting:
java.lang.IllegalStateException: Unable to ignore case of java.lang.String types, the property 'name' must reference a String
at org.springframework.util.Assert.state(Assert.java:73) ~[spring-core-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.data.neo4j.repository.query.filter.PropertyComparisonBuilder.applyCaseInsensitivityIfShouldIgnoreCase(PropertyComparisonBuilder.java:101) ~[spring-data-neo4j-5.1.2.RELEASE.jar:5.1.2.RELEASE]
Doesn't spring-data-neo4j support Containing and IgnoreCase together? Am I missing something?
At the moment it seems not possible because the referenced org.springframework.data.neo4j.repository.query.filter.PropertyComparisonBuilder seems to allow ignoring case only for "SIMPLE_PROERTY" (is, or equals). See method canIgnoreCase in same class:
private boolean canIgnoreCase(Part part) {
return part.getType() == SIMPLE_PROPERTY && String.class.equals(part.getProperty().getLeafType());
}
Fix is coming with spring 5.2 (Moore): https://jira.spring.io/browse/DATAGRAPH-1190

Null Pointer Exception In Spring Proxy Class and Kotlin

I am facing some problems with kotlin in conjunction with spring.
I have a controller bean (without an interface btw) which has an auto-wired service bean via the primary constructor.
It works perfectly unless I use caching annotations for the controller. Apparently springs caching generates a proxy class under the hood which deals with the caching.
My code looks like this:
#RestController
#RequestMapping("/regions/")
open class RegionController #Autowired constructor(val service: RegionService) {
#RequestMapping("{id}", method = arrayOf(RequestMethod.GET))
#Cacheable(cacheNames = arrayOf("regions"))
fun get(#PathVariable id: Long): RegionResource {
return this.service.get(id)
}
}
The problem now is a null pointer exception when the method is executed, actually this.service is null which technically is not possible as it is a nonnull variable in kotlin.
I assume that class proxies generated by spring initialize the class with null values instead of the autowired bean. This must be a common pitfall using kotlin and spring. How did you circumvent this problem?
In Kotlin both classes and members are final by default.
For the proxying library (CGLIB, javaassist) to be able to proxy a method it has to be declared non final and in a non final class (since those libraries implement proxying by subclassing). Change your controller method to:
#RequestMapping("{id}", method = arrayOf(RequestMethod.GET))
#Cacheable(cacheNames = arrayOf("regions"))
open fun get(#PathVariable id: Long): RegionResource {
return this.service.get(id)
}
You probably see a warning in console regarding RegionController methods not being subject to proxying.
The Kotlin compiler plugin
The Kotlin team has acknowledged this difficulty and created a plugin that marks the standard AOP proxy candidates e.g. #Component with open.
You can enable the plugin by in your build.gradle:
plugins {
id "org.jetbrains.kotlin.plugin.spring" version "1.1.60"
}
Soon this might not be a problem any longer.
There is work in progress that any lib (including spring for example) can specify a list of annotations a file in META-INF. Once a class is annotated with one of these, it will default to open for the class itself and all its functions. This is also true for classes inheriting from an annotated class.
For more details, have a look at https://github.com/Kotlin/KEEP/pull/40#issuecomment-250773204

Resources