I'm trying to setup a simple Jersey JAX-RS application using MOXy for JAXB JSON support and I wanted to customise the unmarshaller. I did the following:
#Provider
public class CustomProvider extends ConfigurableMoxyJsonProvider{
#Override
protected void preReadFrom(..., Unmarshaller unmarshaller) throws JAXBException{
super.preReadFrom(...);
System.out.println("preReadFrom entered");
unmarshaller.setEventHandler(new ValidationEventHandler(){
#Override
public boolean handleEvent(ValidationEvent event){
System.out.println("Entered handleEvent");
return false;
}
});
}
}
I wrote an override for preReadFrom and set an event handler on the unmarshaller. When I pass an invalid JSON body, print statement in preReadFrom executes but not the one in event handler. So the provider is registered properly but the event handler is not being called.
What might cause this issue?
What I want to achieve is when a user passes extraneous attributes in the JSON body, I want to throw an error (By default, these attributes are ignored). Searching on various websites, adding an event handler is the only way to do that. It would be great if I can achieve this in a different way too.
I assume System.out.println("preReadFrom entered"); is getting called, and your CustomProvider is actually registered and used. Because in Weblogic e.g even if you register a different provider, still default ConfigurableMoxyJsonProvider is getting called unless you disable Moxy.
If first assumption is correct, then for sure you will be getting public boolean handleEvent(ValidationEvent event) called for validations like if your Pojo, attribute is of numeric and you are passing String in json.
For UnmappedElements, I have seen that Moxy, "ignores" the warning for Json. What that means is if your pojo is like below:
public class Emp {
public Emp() {
super();
}
private int id ;
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
And your Json is like: {"id" : 11, "name1" : 111} then your handleEvent won't be called. Reason being the Moxy uses org.eclipse.persistence.internal.oxm.record.UnmarshalRecordImpl.startUnmappedElement() that has a check to see if the media type is xml prior to issuing a warning to the event handler.
So how to solve it for throw. I may not know the best of answer but here is my solution :
In your preReadFrom method add your custom UnmappedHandlerClass like below
protected void preReadFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
Unmarshaller unmarshaller) throws JAXBException {
System.out.println("preReadFrom entered");
super.preReadFrom(type, genericType, annotations, MediaType.WILDCARD_TYPE, httpHeaders, unmarshaller);
System.out.println("preReadFrom returned from super");
//new code
if(unmarshaller instanceof org.eclipse.persistence.jaxb.JAXBUnmarshaller) {
org.eclipse.persistence.jaxb.JAXBUnmarshaller moxyUn = ( org.eclipse.persistence.jaxb.JAXBUnmarshaller)unmarshaller;
moxyUn.getXMLUnmarshaller().setUnmappedContentHandlerClass(CustomUnmapped.class);
}
//your existing code
unmarshaller.setEventHandler(new ValidationEventHandler() {
#Override
public boolean handleEvent(ValidationEvent event) {
System.out.println("Entered handleEvent");
return false;
}
});
}
And you can use Customunmapped class like this:
class CustomUnmapped extends org.eclipse.persistence.internal.oxm.unmapped.DefaultUnmappedContentHandler {
#Override
public void startElement(String p1, String p2, String p3, Attributes p4) throws SAXException {
throw new SAXNotRecognizedException(p1);
}
}
That shall work. But before trying 3, just make sure that your a) CustomProvider is actually being called by adding a breakpoint, or System.out.. statement. and b) Your handleEvent is called for generic errors like passing a string x in json, for number field.
Related
I have 2 #RequestParam parameters in my Controller. I want to set the Required value of both the parameters based on a Condition. The condition could be like, if one of the parameter is passed, the other has to passed. So set the required of other as true and vice-versa. Otherwise set both false if none of the parameters are passed.
#RestController
public class TestController {
#GetMapping("/test")
public void test(#RequestParam(value = "a", required = (b !=null) String a,
#RequestParam(value = "b", required = (a !=null) ) String b,) {
{
}
}
The syntax of using the variable name inside #RequestParam() is wrong, but I wanted to explain what I want.
You can do it using one of the 2 following ways:
Using Spring AOP and create a surrounding aspect for that request
mapping
Using HandlerInterceptorAdapter to intercept the requests for a given URI
1. Using Spring AOP
Create an annotation like the following:
public #interface RequestParameterPairValidation {
}
Then you can annotate your request mapping method with it:
#GetMapping("/test")
#RequestParameterPairValidation
public void test(
#RequestParam(value = "a", required = false) String a,
#RequestParam(value = "b", required = false) String b) {
// API code goes here...
}
Create an aspect around the annotation. Something like:
#Aspect
#Component
public class RequestParameterPairValidationAspect {
#Around("#annotation(x.y.z.RequestParameterPairValidation) && execution(public * *(..))")
public Object time(final ProceedingJoinPoint joinPoint) throws Throwable {
Object[] requestMappingArgs = joinPoint.getArgs();
String a = (String) requestMappingArgs[0];
String b = (String) requestMappingArgs[1];
boolean requestIsValid = //... execute validation logic here
if (requestIsValid) {
return joinPoint.proceed();
} else {
throw new IllegalArgumentException("illegal request");
}
}
}
Note that it would be a good option to return 400 BAD REQUEST here since the request was not valid. Depends on the context, of course, but this is a general rule of thumb to start with.
2. Using HandlerInterceptorAdapter
Create a new interceptor mapping to your desired URI (in this case /test):
#Configuration
public class CustomInterceptor extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry
.addInterceptor(new CustomRequestParameterPairInterceptor())
.addPathPatterns("/test");
}
}
Define the logic for validation inside the custom interceptor:
public class CustomRequestParameterPairInterceptor extends HandlerInterceptorAdapter {
#Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object obj, Exception exception) throws Exception {
}
#Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object obj, ModelAndView modelAndView) throws Exception {
}
#Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
// Run your validation logic here
}
}
I would say the 2nd option is the best one since you can directly control the answer of the request. In this case it might be a 400 BAD REQUEST, or anything else that makes more sense in your case.
You can use Optional here in an intelligent manner here like this:
#GetMapping("/test")
#RequestParameterPairValidation
public void test(#RequestParam("a") Optional<String> a,
#RequestParam("b") Optional<String> b){
String aVal = a.isPresent() ? a.get() : null;
String bVal = b.isPresent() ? b.get() : null;
//go for service call here based on your logic
}
I hope this works for your requirement.
You can use Java EE #Size Validation annotation with Spring (but you must have a Java EE validation API implementor on the classpath, i.e hibernate ). With hibernate, you can import this dependency using maven
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.10.Final</version>
</dependency>
Then the entire thing becomes:
#RestController
#Validated
public class TestController {
#GetMapping("/test")
public void test(#RequestParam(value = "a", required = true ) #Size(min=1) String a,
#RequestParam(value = "b", required = true) #Size(min=1) String b) {
{
}
}
In Java you can pass only constants as parameters of any annotation.
That's why it's impossible to do it this way.
However, you can validate all that kind of things in the method itself.
I have the following entity:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = EntityConstants.PARTNER)
public class FilePartner
{
#XmlAttribute(name = EntityConstants.IDENTIFIER, required = true)
#XmlJavaTypeAdapter(RestResourceIdJaxbAdapter.class)
private String identifier;
...
}
Here is the jaxb adapter:
public class RestResourceIdJaxbAdapter extends XmlAdapter<String, String>
{
#Override
public String unmarshal(String v) throws Exception
{
if (v != null && v.contains("/"))
{
// throw new ApiException(Status.BAD_REQUEST, RestErrorMessages.BAD_REQUEST_SUFFIX, "Identifier must not contain slashes");
return v.replaceAll("/", "");
}
return v;
}
#Override
public String marshal(String v) throws Exception
{
return v;
}
}
I have a jaxrs service that accepts POST requests with body FilePartner:
#POST
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response createPartner(FilePartner partner,
#Context UriInfo uriInfo,
#HeaderParam(HttpHeaders.ACCEPT) String acceptHeader)
throws ApiException
{
...
}
What I want to achieve is to forbid the usage of slashes '/' in the identifier attribute of the FilePartner entity.
Today I am doing this using some jaxb adapter which simply strips all slashes from the id when unmarshalling.
Instead, what I would like is to return an appropriate BAD_REQUEST exception to the user.
I tried throwing exception in the unmarshal method of the jaxb adapter but seems that jaxrs is swallowing it and simply setting my identifier to null.
If we want to override this behavior I think I must create a new #Provider and register a special ValidationEventHandler in the unmarshaller that the javax.ws.rs.ext.MessageBodyReader creates.
Unfortunately, this is impossible unless I define an explicit dependency to a JAX-RS implementation which I want to avoid.
Are there any other options to restrict the usage of slashes in the identifier attribute, without defining an explicit dependency to jersey/resteasy and without handling the restriction in the #POST method of the service?
To your rescue comes ReaderInterceptor
Don't do any special handling using #XmlJavaTypeAdapter. Register a ReaderInterceptor with your Application class (if in jersey2) or in web.xml if earlier.
import java.io.IOException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;
javax.ws.rs.ext.ReaderInterceptor
#Provider
public class ValidationInterceptor implements ReaderInterceptor {
public ValidationInterceptor() {
super();
}
#Override
public Object aroundReadFrom(ReaderInterceptorContext readerInterceptorContext) throws IOException,
WebApplicationException {
Object o = readerInterceptorContext.proceed();
if (o instanceof FilePartner&& ((FilePartner) o).getIndentifier().contains("/")) {
throw new WebApplicationException(Response.status(400)
.entity("Identifier must not contain a slash")
.build());
}
return o;
}
}
And register the interceptor to your Application in override of public Set<Class<?>> getClasses() method something like classes.add(ValidationInterceptor.class);
Hope that helps.
Dropwizard registers jersey component A. I want to replace this component with my custom version. Is it possible?
from the docs:
https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/message-body-workers.html#providers-selection
This means:
If you have a custom provider, your custom provider will be used.
By example:
Say I have a user class:
public class User {
#JsonProperty("name")
private String name;
#JsonProperty("password")
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
We have a resource returning the User:
#Path("/hello/world2")
#Produces(MediaType.APPLICATION_JSON)
public class MsgBodyWriterTest {
#GET
#Path("/v1")
#Produces(MediaType.APPLICATION_JSON)
public User test() {
User u = new User();
u.setName("Test");
return u;
}
}
And we have a MessageBodyWriter that deals with this User object:
#Provider
public class UserMsgBodyWriter implements MessageBodyWriter<User> {
#Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type == User.class;
}
#Override
public long getSize(User t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
// TODO Auto-generated method stub
return 0;
}
#Override
public void writeTo(User t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
throws IOException, WebApplicationException {
System.out.println("Use custom Provider");
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(entityStream, t);
}
}
We then hook things together in the main by registering the resource and the provider:
public class Starter extends Application<Configuration> {
#Override
public void run(Configuration configuration, Environment environment) throws Exception {
environment.jersey().register(MsgBodyWriterTest.class);
environment.jersey().register(UserMsgBodyWriter.class);
}
public static void main(String[] args) throws Exception {
new Starter().run("server", "/Users/artur/dev/repo/dw-test/src/main/resources/configuration.yaml");
}
}
When started, it will publish the jersey resource, and we can then access it:
arturk:~ artur$ curl -XGET "localhost:9085/hello/world2/v1"
This will then log:
Use custom Provider
0:0:0:0:0:0:0:1 - - [02/Jun/2016:09:21:31 +0000] "GET /hello/world2/v1 HTTP/1.1" 200 31 "-" "curl/7.43.0" 30
Note the output: "Use custom Provider". This is simply to demonstrate that the right provider has been called.
I hope this is what your looking for.
In short, the default provider that is hooked in comes from Jaxb. it deals with all json types and enables default beans serialising. Without it, your json will not know how to be parsed. You don't want to get rid of it, but rather add to it.
By creating your own provider, the selection will take care of choosing the right thing for you. By default, jersey will sort custom providers before default providers. So as long as your isWriteable() method is implemented correctly, your provider will be chosen to do the work.
EDIT:
to answer your actual question. If you MUST get rid of the DW registered provider, there is a way for that too. You will have to overwrite the default server factory. Look at:
AbstractServerFactory#createAppServlet. This is where the default providers are hooked in. Again though, if you simply register your own, the default will never be used and you'll save yourself the work of creating a server factory (which is also fairly easy and straight forward in case you want to).
Cheers,
Artur
I am trying to get the annotation values. This is my scenario as follows:
This is the annotation I declared.
#Retention(RetentionPolicy.RUNTIME)
public #interface PluginMessage {
String name();
String version();
}
This is the class the uses the annotation for some values
#PluginMessage(name = "RandomName", version = "1")
public class Response{
private Date Time;
}
This is a generic interface which will be used in the next code snippet.
public interface ResponseListener<E> {
void onReceive(E response);
}
I Invoke this by calling the following code:
addListener(new ResponseListener<Response>() {
#Override
public void onReceive(Response response) {
System.out.println();
}
});
This is the implementation of the addListener method:
public <E> void addListener(ResponseListener<E> responseListener) {
Annotation[] annotations = responseListener.getClass().getAnnotations();
}
The annotations are always empty, any idea of what I am doing wrong? I am trying to get the value of them here.
You may get annotations here:
.addListener(new ResponseListener<Response>() {
public void onReceive(Response response) {
final Annotation[] annotations = response.getClass().getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("annotation.toString() = " + annotation.toString());
}
}
});
Your .addListener implementation makes no sense. Instead of getting annotations from ResponseListener(which has no annotations) instance, you have to add listener to listeners pool. Then you have to call listener.onReceive(...) for each listener when you will receive the response. I believe something like that should be implemented there.
I'm using Spring for form input and validation. The form controller's command contains the model that's being edited. Some of the model's attributes are a custom type. For example, Person's social security number is a custom SSN type.
public class Person {
public String getName() {...}
public void setName(String name) {...}
public SSN getSocialSecurtyNumber() {...}
public void setSocialSecurtyNumber(SSN ssn) {...}
}
and wrapping Person in a Spring form edit command:
public class EditPersonCommand {
public Person getPerson() {...}
public void setPerson(Person person) {...}
}
Since Spring doesn't know how to convert text to a SSN, I register a customer editor with the form controller's binder:
public class EditPersonController extends SimpleFormController {
protected void initBinder(HttpServletRequest req, ServletRequestDataBinder binder) {
super.initBinder(req, binder);
binder.registerCustomEditor(SSN.class, "person.ssn", new SsnEditor());
}
}
and SsnEditor is just a custom java.beans.PropertyEditor that can convert text to a SSN object:
public class SsnEditor extends PropertyEditorSupport {
public String getAsText() {...} // converts SSN to text
public void setAsText(String str) {
// converts text to SSN
// throws IllegalArgumentException for invalid text
}
}
If setAsText encounters text that is invalid and can't be converted to a SSN, then it throws IllegalArgumentException (per PropertyEditor setAsText's specification). The issue I'm having is that the text to object conversion (via PropertyEditor.setAsText()) takes place before my Spring validator is called. When setAsText throws IllegalArgumentException, Spring simply displays the generic error message defined in errors.properties. What I want is a specific error message that depends on the exact reason why the entered SSN is invalid. PropertyEditor.setAsText() would determine the reason. I've tried embedded the error reason text in IllegalArgumentException's text, but Spring just treats it as a generic error.
Is there a solution to this? To repeat, what I want is the specific error message generated by the PropertyEditor to surface to the error message on the Spring form. The only alternative I can think of is to store the SSN as text in the command and perform validation in the validator. The text to SSN object conversion would take place in the form's onSubmit. This is less desirable as my form (and model) has many properties and I don't want to have to create and maintain a command that has each and every model attribute as a text field.
The above is just an example, my actual code isn't Person/SSN, so there's no need to reply with "why not store SSN as text..."
You're trying to do validation in a binder. That's not the binder's purpose. A binder is supposed to bind request parameters to your backing object, nothing more. A property editor converts Strings to objects and vice versa - it is not designed to do anything else.
In other words, you need to consider separation of concerns - you're trying to shoehorn functionality into an object that was never meant to do anything more than convert a string into an object and vice versa.
You might consider breaking up your SSN object into multiple, validateable fields that are easily bound (String objects, basic objects like Dates, etc). This way you can use a validator after binding to verify that the SSN is correct, or you can set an error directly. With a property editor, you throw an IllegalArgumentException, Spring converts it to a type mismatch error because that's what it is - the string doesn't match the type that is expected. That's all that it is. A validator, on the other hand, can do this. You can use the spring bind tag to bind to nested fields, as long as the SSN instance is populated - it must be initialized with new() first. For instance:
<spring:bind path="ssn.firstNestedField">...</spring:bind>
If you truly want to persist on this path, however, have your property editor keep a list of errors - if it is to throw an IllegalArgumentException, add it to the list and then throw the IllegalArgumentException (catch and rethrow if needed). Because you can construct your property editor in the same thread as the binding, it will be threadsafe if you simply override the property editor default behavior - you need to find the hook it uses to do binding, and override it - do the same property editor registration you're doing now (except in the same method, so that you can keep the reference to your editor) and then at the end of the binding, you can register errors by retrieving the list from your editor if you provide a public accessor. Once the list is retrieved you can process it and add your errors accordingly.
As said:
What I want is the specific error message generated by the PropertyEditor to surface to the error message on the Spring form
Behind the scenes, Spring MVC uses a BindingErrorProcessor strategy for processing missing field errors, and for translating a PropertyAccessException to a FieldError. So if you want to override default Spring MVC BindingErrorProcessor strategy, you must provide a BindingErrorProcessor strategy according to:
public class CustomBindingErrorProcessor implements DefaultBindingErrorProcessor {
public void processMissingFieldError(String missingField, BindException errors) {
super.processMissingFieldError(missingField, errors);
}
public void processPropertyAccessException(PropertyAccessException accessException, BindException errors) {
if(accessException.getCause() instanceof IllegalArgumentException)
errors.rejectValue(accessException.getPropertyChangeEvent().getPropertyName(), "<SOME_SPECIFIC_CODE_IF_YOU_WANT>", accessException.getCause().getMessage());
else
defaultSpringBindingErrorProcessor.processPropertyAccessException(accessException, errors);
}
}
In order to test, Let's do the following
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
binder.registerCustomEditor(SSN.class, new PropertyEditorSupport() {
public String getAsText() {
if(getValue() == null)
return null;
return ((SSN) getValue()).toString();
}
public void setAsText(String value) throws IllegalArgumentException {
if(StringUtils.isBlank(value))
return;
boolean somethingGoesWrong = true;
if(somethingGoesWrong)
throw new IllegalArgumentException("Something goes wrong!");
}
});
}
Now our Test class
public class PersonControllerTest {
private PersonController personController;
private MockHttpServletRequest request;
#BeforeMethod
public void setUp() {
personController = new PersonController();
personController.setCommandName("command");
personController.setCommandClass(Person.class);
personController.setBindingErrorProcessor(new CustomBindingErrorProcessor());
request = new MockHttpServletRequest();
request.setMethod("POST");
request.addParameter("ssn", "somethingGoesWrong");
}
#Test
public void done() {
ModelAndView mav = personController.handleRequest(request, new MockHttpServletResponse());
BindingResult bindingResult = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + "command");
FieldError fieldError = bindingResult.getFieldError("ssn");
Assert.assertEquals(fieldError.getMessage(), "Something goes wrong!");
}
}
regards,
As a follow up to #Arthur Ronald's answer, this is how I ended up implementing this:
On the controller:
setBindingErrorProcessor(new CustomBindingErrorProcessor());
And then the binding error processor class:
public class CustomBindingErrorProcessor extends DefaultBindingErrorProcessor {
public void processPropertyAccessException(PropertyAccessException accessException,
BindingResult bindingResult) {
if(accessException.getCause() instanceof IllegalArgumentException){
String fieldName = accessException.getPropertyChangeEvent().getPropertyName();
String exceptionError = accessException.getCause().getMessage();
FieldError fieldError = new FieldError(fieldName,
"BINDING_ERROR",
fieldName + ": " + exceptionError);
bindingResult.addError(fieldError);
}else{
super.processPropertyAccessException(accessException, bindingResult);
}
}
}
So the processor method's signature takes a BindingResult instead of a BindException on this version.
This sounds similar to an issue I had with NumberFormatExceptions when the value for an integer property could not be bound if, say, a String was entered in the form. The error message on the form was a generic message for that exception.
The solution was to add my own message resource bundle to my application context and add my own error message for type mismatches on that property. Perhaps you can do something similar for IllegalArgumentExceptions on a specific field.
I believe you could just try to put this in your message source:
typeMismatch.person.ssn=Wrong SSN format