How to make Jersey / Moxy more restrictive when unmarshaling JSON? - jax-rs

I'm currently using Jersey and Moxy in Glassfish 4. Is there a way to tell Jersey/Moxy to refuse a HTTP request if its JSON content is not valid (i.e. it contains more objects than it should when binding JSON to a POJO) ?

I would create my own subclass of MOXyJsonProvider (see: http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html). Then in that subclass I would override the preReadFrom method. In that method I would set an Unmarshaller.Listener.
#Override
protected void preReadFrom(Class<Object> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
Unmarshaller unmarshaller) throws JAXBException {
Unmarshaller.Listener ul = new YourUnmarshallerListener();
unmarshaller.setListener(ul);
}
Then Unmarshaller.Listener would then count each time an object was unmarshalled and error out if too many are read.

Related

Why jackson is not serializing this?

#Data
public class IdentificacaoBiometricaDto {
private Integer cdIdentifBiom;
private String nrMatricula;
private String deImpressaoDigital;
private Integer cdFilialAtualizacao;
}
I am using retrofit 2.6.1, jackson 2.9.9 and lombok 1.8.10.
The exception is:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class br.com.clamed.modelo.loja.dto.central.IdentificacaoBiometricaDto and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:313)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:71)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:33)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:400)
at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1392)
at com.fasterxml.jackson.databind.ObjectWriter._configAndWriteValue(ObjectWriter.java:1120)
at com.fasterxml.jackson.databind.ObjectWriter.writeValueAsBytes(ObjectWriter.java:1017)
at retrofit2.converter.jackson.JacksonRequestBodyConverter.convert(JacksonRequestBodyConverter.java:34)
at retrofit2.converter.jackson.JacksonRequestBodyConverter.convert(JacksonRequestBodyConverter.java:24)
at retrofit2.ParameterHandler$Body.apply(ParameterHandler.java:355)
... 14 more
The object mapper:
return new ObjectMapper().registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
I am setting all fields, when passing it to a request body, retrofit fails because jackson could not serialize the object.
Retrofit call:
#POST("/usuario/v1.0/cadastraBiometria")
Call<IdentificacaoBiometricaDto> cadastraBiometria(#Body IdentificacaoBiometricaDto identificacaoBiometricaDto);
Rest service:
#RestController
#RequestMapping("/usuario")
public class UsuarioController {
#PostMapping(value = "/v1.0/cadastraBiometria")
public ResponseEntity<IdentificacaoBiometricaDto> cadastraBiometria(#RequestBody IdentificacaoBiometricaDto identificacaoBiometricaDto) {
}
}
Update:
If I change the retrofit converter to Gson it works;
If I serialize it using Jackson directly, it works;
Removing lombok makes no difference;
Found the problem. The biometric reader library was causing this. For some reason it's incompatible with openjdk-11 and is causing all sort of unrelated problems.
Yes, very weird. But the lib is very poorly done.

how to validate header params using Bean Validation and jax-rs

I have jax-rs resources and each have a same header. what is the best way to validate that the header is present using Bean Validation. I know about #HeaderParam but I don't want to change all my methods in all resources to include the header param.
It's as simple as implementing javax.ws.rs.container.ContainerRequestFilter. For example:
#Provider
public class ContentTypeValidatorFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext reqContext) {
String contentType = getHeader(reqContext, "Content-Type");
// Content-type validation, but you can valid as many headers as you want.
if (Objects.isNull(contentType)) {
throw new InvalidRequestException("Content-Type header is missing");
}
}
private String getHeader(ContainerRequestContext requestContext, String header) {
return requestContext.getHeaders().getFirst(header);
}
}
Later, to handle this exception gracefully just implement ExceptionMapper for this InvalidRequestException.
The above filter will be applied globally. But if you want to exclude some endpoints, then make use of #NameBinding to annotate your custom annotation and apply it only to specific endpoints.

How to provide a custom MessageBodyWriter for Strings in CXF

I have registered a custom MessageBodyWriter<Object> implementation in my JAX-RS application. This writer can convert various types, including strings.
The custom converter is successfully used for other types, but for strings, CXF does not consider it: It does not even call isWriteable. (This was different in CXF 2.x, so there seems to have been a regression in CXF 3.x.)
Stepping through the CXF 3.1.11 code, I see that in the ProviderFactory.messageWriters list has two entries (StringTextProvider, JAXBElementTypedProvider) before my custom provider. The first one wants to convert strings, and being first in the list, it is preferred by CXF.
How can I change this to make my provider the preferred provider for strings? E.g. is it possible to drop the StringTextProvider? Or is it possible to reorder the list so that my provider comes first?
I found out that subclassing StringTextProvider and registering that class works:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class CustomeStringProvider extends StringTextProvider {
#Override
public void writeTo(String object, Class<?> type, Type genType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream outputStream) throws IOException {
// ...
}
}
I got the idea for this approach from looking at the implementation of ProviderFactory.MessageBodyWriterComparator, which checks class hierarchies for ordering converters.

How to manually set a single bean property with Jackson?

I'm trying to manually update a bean (it is an argument to a JAX-RS resource method). The value of the field to be set in the bean is to be deserialized from JSON, contextually.
I want to do:
ObjectMapper objectMapper = new ObjectMapper();
// .... <configured, ClassIntrospector obtained for type> ...
BeanProperty prop;
// ... <bean property resolved through ClassIntrospector> ...
AnnotatedMember mutator = prop.getMutator();
JsonFactory jf = new JsonFactory();
JsonParser parser = jf.createParser(textProp);
Object value = objectMapper.getDeserializationContext().readValue(parser, mutator.getRawType());
mutator.setValue(beanInstance, value);
The problem is that Jackson is throwing a NullPointerException:
at com.fasterxml.jackson.databind.DeserializationContext.getTypeFactory(DeserializationContext.java:251)
at com.fasterxml.jackson.databind.DeserializationContext.readValue(DeserializationContext.java:758)
I confirmed with the debugger that the _config field of my DeserializationContext is null, and that this is what is being accessed during my code sequence.
So, what gives? How can I configure this properly so that this works? (Or is there some other way to manually deserialize a JSON fragment to a given type, respecting the JAX-RS resource context / classes?)

Jackson unrecognized field exception but field is in JSON

I am using Spring Integration to consume a message with a JSON Payload.
In my spring context I have
<integration:channel id="jsonToMyMessageConverterChannel"/>
<integration:json-to-object-transformer
type="com.acme.messaging.message.MyMessage"
input-channel="jsonToMyMessageConverterChannel"
output-channel="myMessageUpdateChannel"/>
My message related objects are:
MyMessage.java
#JsonIgnoreProperties(ignoreUnknown=true)
public class MyMessage {
#JsonProperty
private String timestamp;
#JsonProperty("msgs")
private List<Message> messages;
// Getters and Setters...
}
Message.java
#JsonIgnoreProperties(ignoreUnknown=true)
public class Message {
#JsonProperty
private Integer msgId;
#JsonProperty("msgText")
private String text;
// Getters and Setters...
}
When the json transformer attempts to convert the message to an object it fails with
Caused by: org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "msgs" (Class com.acme.messaging.message.MyMessage), not marked as ignorable
The JSON payload definitely has msgs which is an array that has objects which represent the Message.java class.
Can any one suggest reasons why the exception occurs given that the JSON has the field that is being complained about and the class itself is also annotated to ignore unknown fields?
Update
After some debugging it looks like the #JsonProperty("msgs") annotations aren't being use, for some reason.
This works fine for me...
#Test
public void test() throws Exception {
MyMessages mm = new MyMessages();
MyMessage m = new MyMessage();
m.setMsgId(1);
m.setText("foo");
mm.setMessages(Arrays.asList(m));
mm.setTimestamp("123");
#SuppressWarnings("deprecation")
ObjectToJsonTransformer otjt = new ObjectToJsonTransformer(new ObjectMapper());
Message<?> message = new GenericMessage<MyMessages>(mm);
message = otjt.transform(message);
System.out.println(message);
#SuppressWarnings("deprecation")
JsonToObjectTransformer<MyMessages> jtot = new JsonToObjectTransformer<MyMessages>(MyMessages.class, new ObjectMapper());
message = jtot.transform(message);
mm = (MyMessages) message.getPayload();
System.out.println(mm.getTimestamp());
System.out.println(mm.getMessages().get(0).getText());
}
(I changed your classnames slightly to avoid colliding with Message<?>)
Resulting in...
[Payload={"timestamp":"123","msgs":[{"msgId":1,"msgText":"foo"}]}][Headers={timestamp=1373997151738, id=f2425f36-a500-4aee-93a4-e7e0240ce0f1, content-type=application/json}]
123
foo
Do you have both jackson 1.x (codehaus) and 2.x (fasterxml) on the classpath, and using Spring Integration 3.0.0?
If they're both on the classpath, SI will use Jackson 2.x, by default, (which won't understand 1.x annotations).
Or, I guess - are you using Jackson2 (fasterxml) annotations? Spring Integration 2.x uses Jackson 1 (codehaus).
EDIT:
In order to support both versions of Jackson, you can annotate the class with both annotations...
#JsonProperty("msgs")
#com.fasterxml.jackson.annotation.JsonProperty("msgs")
public List<MyMessage> messages;