Can I control how spring controller method arguments are instantiated? - java

Consider the following interface/object hierarchy in a spring project:
public interface MyInterface {
//method defenitions
}
#Component
#Scope(SCOPE_PROTOTYPE)
public class MyClass implements MyInterface {
//method implementations
}
I use MyClass in a controller method where it is read from the requests body:
#RequestMapping(method = POST, value = "/posturi", consumes = "application/json")
public void createEntity(#RequestBody MyClass myClass) {
//handle request
}
The jackson library is used to read json data and convert it to a java object.
I would like to change the type of the parameter in the controller method from MyClass to MyInterface. This does not seem to work since the interface can't be instantiated with the new operator. But it could be created like this:
MyInterface instance = applicationContext.getBean(MyInterface.class);
Is it possible to make spring/jackson instantiate the object this way? I would like to do this so that my controller does not need to be aware of what implementation is used.

It should be possible with Converters. See documentation http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/validation.html. Question is, how do you know which class you return by converter? Rather rethink your design to use POJOs in input.

I have solved this now and the concept is quite simple but the implementation can be a bit tricky. As I understand it, you can annotate any type with #RequestBody as long as you provide a HttpMessageConverter that can convert from a http request to your desired type.
So the solution is:
Implement a HttpMessageConverter
Configure spring so that your HttpMessageConverter is used.
The second part can be a bit tricky. This is because spring adds a bunch of default HttpMessageConverter that can handle common types such as strings, integers, dates and I want these to continue to function as usual. Another problem is that if jackson is on the path, spring also adds a MappingJackson2HttpMessageConverter for generic json handling such as converting to concrete objects, maps and so on. Spring will use the first HttpMessageConverter it finds that claims to be able to convert to your type. The MappingJackson2HttpMessageConverter claims to be able to do so for my objects, but it is not able to, so it fails and the request fails. This could be considered a bug...
The chain that I wanted was:
Springs default HttpMessageConverters.
My own HttpMessageConverter
The MappingJackson2HttpMessageConverter
I found two ways to acheive this. First, you can declare this explicitly through xml.
<mvc:annotation-driven>
<mvc:message-converters>
<!-- All converters in specific order here -->
</mvc:message-converters>
</mvc:annotation-driven>
The downside of this is that if the default HttpMessageConverter chain changes in later releases, it will not change for your configuration.
Another way to do it is to programatically insert your own HttpMessageConverter before the MappingJackson2HttpMessageConverter.
#Configuration
public class MyConfiguration {
#Autowired
private RequestMappingHandlerAdapter adapter;
#Autowired
private MyHttpMessageConverter myHttpMessageConverter;
#PostConstruct
private void modify() {
List<HttpMessageConverter<?>> messageConverters = adapter.getMessageConverters();
int insertLocation = messageConverters.size() - 1;
for (int i = 0; i < messageConverters.size(); i++) {
Object messageConverter = messageConverters.get(i);
if (messageConverter instanceof MappingJackson2HttpMessageConverter) {
insertLocation = i;
}
}
messageConverters.add(insertLocation, myHttpMessageConverter);
}
}
The second alternative will continue to use the "default configuration" even if it changes in later releases. I consider it a bit hacky and not at all elegant but the reason I think it is a valid soulution is that there seems to be flaws in the MappingJackson2HttpMessageConverter claiming to be able to convert to types it cannot convert to. And also that you cannot explicitly add a HttpMessageConverter to a specific position in the chain.
For now I am going with the second option but how you do is up to you...

Related

Checker framework - problem with MapStruct converters

I am trying to add checker framework's Nullness checker to our project, however I am having problem with our MapStruct converters.
Converter example
Lets say I have a converter from User to UserDto like following:
#MapperConfig(componentModel = SPRING)
public interface UserToUserDtoConverter
extends org.springframework.core.convert.converter.Converter<User, UserDto> {
}
Which generates the following implementation:
#Override
public UserDto convert(User source) {
if ( source == null ) {
return null;
}
UserDtoBuilder<?, ?> userDto = UserDto.builder();
userDto.id(source.getId());
return userDto.build();
}
Problem
Now the problem is that Checker framework complains about the return null;, as the implementation does not have #Nullable above the convert method.
Another problem is when the converter uses other converters that are autowired here, which results in initialization.field.uninitialized error.
Things I have tried
Now I know that I could simply ignore converters completely with -AskipDefs, however I would still like it to let it check that there won't be a problem with assigning #Nullable value from User to #NonNull value in UserDto (and vice versa, which could leave a hole in the project).
Another solution that came to my mind was adding #SuppressWarning annotation for these error codes in the converter interface, however mapstruct is not capable of propagating any annotation to the implementation if I am not mistaken mapstruct-issues.
Stub files won't help here either.
Is here some kind of solution for handling the generated code?

JSON Binding #JsonbTypeDeserializer annotation ignored on enums?

I'm converting a JAXB application to JSON-B and I've run into an issue while trying to deserialize a Java enum using a custom JsonbDeserializer inside one of my tests.
The original JSON I need to deserialize contains ints referencing the enum's constants. Therefore my custom JsonbDeserializer needs to take the int and return the enum constant with the matching ordinal. It looks like this:
#JsonbTypeDeserializer(Region.RegionDeserializer.class)
public enum Region implements BaseEnum {
REGION_A,
REGION_B;
static final class RegionDeserializer implements JsonbDeserializer<Region> {
// deserialize() method returns REGION_A for 0 and REGION_B for 1.
}
}
Then I run it like this:
try (var jsonb = JsonbBuilder.create()) {
var result = jsonb.fromJson(text, Region.class);
} catch (final Exception ex) {
fail(ex);
}
Unfortunately, here's what I get back:
java.lang.IllegalArgumentException: No enum constant Region.1
at java.base/java.lang.Enum.valueOf(Enum.java:266)
at org.eclipse.yasson.internal.serializer.EnumTypeDeserializer.deserialize(EnumTypeDeserializer.java:40)
As you can see, RegionDeserializer is not used. Instead, the default enum deserializer is used. Looking into the JSON-B docs, I see I should register the deserializer manually:
JsonbConfig config = new JsonbConfig()
.withDeserializer(RegionDeserializer.class);
Jsonb jsonb = JsonbBuilder.create(config);
...
And when I do that, the code in fact works. But here's my question - what can I do to have the JsonbTypeDeserializer annotation registered automatically? Considering I have a lot of enums I need custom deserializers for, registering them manually really doesn't scale.
EDIT 1: I have tried to use #JsonbCreator-annotated static method instead, and the result was the same. The default enum deserializer was still used.
The JSON-B specification mentions both ways of registering the custom deserializer:
There are two ways how to register JsonbSerializer/JsonbDeserializer:
Using JsonbConfig::withSerializers/JsonbConfig::withDeserializers method;
Annotating a type with JsonbSerializer/JsonbDeserializer annotation.
The fact that the annotation does not work is a bug. I could reproduce this on Yasson 1.0.6, but not on Yasson 2.0.0-M1. Perhaps updating to the latest version solves your problem?

Java Webclient: How to serialize to Pascal JSON?

I am trying to interface with another system that is has extremely specific integration parameters. They don't have any code written to ignore case sensitivity and, long story short, for a post request I am trying to make, they are expecting a JSON body with field names in Pascal case instead of Camel Case and the request fails without Pascal. We are using WebClient to send integration calls so we can support reactive flows in our code. As far as I've been able to tell, when I use WebClient to serialize to JSON, the request is being converted to use Camel Case, which I would normally want.
How can I serialize this to Pascal instead? Everything I try to research about this ends up landing me in .NET land, but I'm not writing this in C#. I'm writing it in Java.
//For example:
{"originTypeCode":"US","camelCaseFieldName":"FAILED"} // FAILURE
{"OriginTypeCode":"US","PascalFieldName":"SUCCESS"} // SUCCESS
I have two ideas:
1) This seems less ideal, but perhaps more intuitive. The idea is to convert the object I'm trying to post to JSON first, then with a parser convert all the fields from Camel Case to Pascal, then try and post that with my WebClient method. This doesn't seem like the most ideal way to do this. I'd imagine there is probably something a lot cleaner.
2) The second idea is that my WebClient instance serializes using a Jackson serializer. I think if I were to create a new Bean of WebClient/Jackson ObjectMapper, maybe I can write a custom converter to use specifically for this integration flow. This seems like it is perhaps cleaner, but digging through WebClient and it's build methods, it's difficult to figure out how to accomplish this. Below I'm posting the beans as I have them currently defined. Digging into this kind of thing is pretty new to me, so I'm not sure what would need to be changed or where. The WebClient bean is from a WebClientConfig class and the ObjectMapper is from my JacksonConfig class.
#Bean
public WebClient webClient() {
return WebClient.builder().clientConnector(getClientHttpConnector()).build();
}
#Bean
#Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return objectMapper;
}
I am definitely open to other suggestions as well.
The comments from #GriffeyDog have helped me figure out what I need to do. For anyone that stumbles across this, the solution was the following:
If you want certain classes to serialize in specific ways, you can annotate the class itself with the annotation #JsonNaming, and then specify a naming strategy, a list of which can be found here: https://java-focus.com/jackson-property-naming-strategy/. For my use case, I used PropertyNamingStrategy.UpperCamelCaseStrategy.
#JsonNaming(value = PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class MyPascalSerializedClass {}
//All fields in this class will serialize to "UpperCamelCase" instead of "normalCamelCase".
Additionally, if you wanted to specify certain fields, you can use the #JsonProperty annotation to override even the class annotation. For example, I had a field within my class that had to map to a JSON format that didn't fit to any standard convention, so I was able to use this.
#JsonProperty("ULDNumber")
private String uldNumber
//This field will serialize to the specified "ULDNumber".
This is all part of the com.fasterxml.jackson library. For further documentation you can refer to the link above and the following: https://fasterxml.github.io/jackson-databind/javadoc/2.7/com/fasterxml/jackson/databind/PropertyNamingStrategy.UpperCamelCaseStrategy.html

How to wrap Spring beans

I am new to spring framework..
Here is my question: How i can wrap beans in runtime onto another class?
I have classes as follows for every data struct && java type:
#Component
public class ByteCodec extends Codec<Byte> {
public ByteCodec() {
super(Byte.class);
}
public void encode(... buffer, Byte object) {
buffer.writeByte(object);
}
public Byte decode(... buffer) {
return buffer.readByte();
}
}
and this class is a managed spring singleton.
I need to wrap that codec by next class:
class OptionalCodec<T> extends Codec<Boolean> {
public OptionalCodec(Codec<T> clazz) {
}
... some implementation of encode && decode method's ...
}
How i can do this?
Hint: i want automatic wrap in RUNTIME for every Codec instance..
And how to extend Autowired annotation, like that:
#AutowireCodec(targetClass=Integer.class, canBeNull=false)
private Codec<Integer> codec;
And how to do registry of the all runtime-created codecs with map:
Map<*MyCodecInfoClass*, Codec>
??
Thanks for any replies!
Spring allows you to inject beans by types and generic types out of the box, so for your use case I don't think it is really necessary to create a new Autowired annotation. You can simply use the existing #Autowired like this:
#Autowired
private Codec<Byte> codec;
Just keep in mind that if you define more than one bean for the same generic type and you use the code above, you'll get an error because more than one bean exist with that definition. You could get around that injecting a collection instead of a single object, for example:
#Autowired
private List<Codec<Byte>> byteCodecs;
Or if you want all codecs, regardless its generic type you can simply do something like this:
#Autowired
private List<Codec<?>> allCodecs;
Regarding your question on instance wrapping, I'm not sure if I fully understand what you're trying to achieve but you can inject a codec into another codec like I stated above, or you can take a look to Spring AOP and use it to wrap calls to your beans: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html
I'm still not sure what would be the purpose of of the OptionalCodec as it will accept booleans on the encode/decode methods. The OptionalCodec is a different class than the base Codec interface, so if you do:
#Autowired
private OptionalCodec<Byte> codec;
Will inject the optional Byte codec. If you do:
#Autowired
private Codec<Byte> codec;
Will inject the original Byte codec. But if you do:
#Autowired
private Codec<Boolean> codec;
It will match all the OptionalCodec beans (because the type signature for an OptionalCodec is Codec<Boolean>) and throw an error as it will not be able to pick a single one.
That said, if you really need to fine tune the autowiring of same type of beans I suggest you to check this relevant section of Spring documentation where annotations like #Primary and #Qualifier are explained, and let you do exactly that: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-autowired-annotation-qualifiers

Adding JAR with ObjectMapper makes my ObjectMapper non-discoverable

How can I make my object mapper work in situation when there is another object mapper defined in jar from dependencies ?
I'm trying to use Swagger with Jersey 2 which is being run under Jetty. The problem is that as soon as I add Swagger JAX-RX jar into classpath my object mapper is not discovered therefore I lose custom serialization of my objects.
Here is how my object mapper defined
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
}
I've posted issue to Swagger's maintainers where you could read details.
After hours of debugging in internals of Jersey I found that Swagger's own object mapper com.wordnik.swagger.jaxrs.json.JacksonJsonProvider calls super.setMapper(commonMapper) that sets non-null value to ProviderBase._mapperConfig._mapper. Later when http request handler attempts to serialize instance of my class call ends up in ProviderBase.locateMapper which has following body
public MAPPER locateMapper(Class<?> type, MediaType mediaType)
{
// First: were we configured with a specific instance?
MAPPER m = _mapperConfig.getConfiguredMapper();
if (m == null) {
// If not, maybe we can get one configured via context?
m = _locateMapperViaProvider(type, mediaType);
if (m == null) {
// If not, let's get the fallback default instance
m = _mapperConfig.getDefaultMapper();
}
}
return m;
}
in correct code-flow _mapperConfig.getConfiguredMapper() returns null which subsequently causes invocation of _locateMapperViaProvider which finds my custom mapper. With Swagger it defaults to com.fasterxml.jackson.jaxrs.json.JsonMapperConfigurator and my custom json serializers never get invoked.
I created small project which reproduces this problem here.
How would you guys suggest to fix this ? I could probably specify deserializer on each property of type TTLocalDate but it'll pollute the code :(
As noted by fehguy in the issue report, using the latest Swagger release and using the SwaggerSerializers should fix this issue. Where previously the Swagger JacksonJsonProvider would be used for all serializions, the SwaggerSerializers is only used for the Swagger model object
public class SwaggerSerializers implements MessageBodyWriter<Swagger> {
#Override
public boolean isWriteable(Class type, Type genericType, Annotation[] annotations,
MediaType mediaType) {
return Swagger.class.isAssignableFrom(type);
}

Categories

Resources