5

I send the following JSON request body to my controller:

{"Game": {"url": "asd"}}

where Game is my model class, annotated with @XmlRootElement (and some JPA annotations which are not important in this context).

The controller:

@PUT
@Path("/{name}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createRow(
    @PathParam("name") String name, 
    Game gameData) throws Exception{

    Game.createRow(gameData); // + exception handling etc.
}

Now, I understood that when Game gameData parameter of the controller method is created, my setters from the model class are called. The setter that requires attention is:

public void setUrl(String url) throws Exception{
  String regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
  Pattern pattern = Pattern.compile(regex);

  System.out.println("URL: " + url);
  if ( url == null || url.length() == 0) {
    throw new Exception("The url of the game is mandatory!");
  } else {
    Matcher matcher = pattern.matcher(url);
    if (!matcher.matches()) { 
      throw new Exception("The url is invalid! Please check its syntax!");
    } else {
      this.url = url;
    }
  }
}

What happens is that, during the deserialization of the JSON string to the Game object, the The url is invalid! exception is thrown, but only in the console of TomEE. What I want to do is to send this error to the client.

If I use an exception which extends WebApplicationException instead of the generic Exception, then I get an exception in the client, but not the one about the validity of the url. Instead, after the deserialization, gameData.url is NULL, and when I try to create a Game instance with the data from gameData, the setter will be called like gameToBeCreated.set(NULL), and I will get the exception Url is mandatory, when actually an URL was sent from the client, but with bad syntax. It was not NULL when sent from client.

So, can I somehow intercept the exceptions thrown when the automatic unmarshalling happens and forward them to the client?

3 Answers3

5

Interception Exceptions with JAXB

A ValidationEventHandler is used to intercept the exceptions that occur during unmarshalling. If you want to collect all the errors that occur you can use a ValidationEventCollector.

Java Model

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Foo {

    private String bar;

    public String getBar() {
        return bar;
    }

    public void setBar(String bar) {
        throw new RuntimeException("Always throw an error");
    }

}

Demo

import java.io.StringReader;
import javax.xml.bind.*;
import javax.xml.bind.util.ValidationEventCollector;

public class Demo {

    private static String XML = "<foo><bar>Hello World</bar></foo>";

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Foo.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.unmarshal(new StringReader(XML));

        ValidationEventCollector vec = new ValidationEventCollector();
        unmarshaller.setEventHandler(vec);
        unmarshaller.unmarshal(new StringReader(XML));
        System.out.println(vec.getEvents().length);
    }

}

Output

1

Integrating with JAX-RS

MessageBodyReader

You could create an instance of MessageBodyReader where you can leverage a ValidationEventHandler during the unmarshal process. Below is a link giving an example of how to implement a MessageBodyReader.

ContextResolver<Unmarshaller>

Slightly higher level, you could implement ContextResolver<Unmarshaller> and have the Unmarshaller returned have the appropriate ValidationEventHandler on it. It would look something like the following:

import javax.ws.rs.core.*;
import javax.ws.rs.ext.*;
import javax.xml.bind.*;
import javax.xml.bind.helpers.DefaultValidationEventHandler;

@Provider
public class UnmarshallerResolver implements ContextResolver<Unmarshaller> {

    @Context Providers providers;

    @Override
    public Unmarshaller getContext(Class<?> type) {
        try {
            ContextResolver<JAXBContext> resolver = providers.getContextResolver(JAXBContext.class, MediaType.APPLICATION_XML_TYPE);
            JAXBContext jaxbContext;
            if(null == resolver || null == (jaxbContext = resolver.getContext(type))) {
                jaxbContext = JAXBContext.newInstance(type);
            }
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            unmarshaller.setEventHandler(new DefaultValidationEventHandler());
            return unmarshaller;
        } catch(JAXBException e) {
            throw new RuntimeException(e);
        }
    }

}
Community
  • 1
  • 1
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • is there another option which doesn't include the implementation of a `MessageBodyReader`? I would like to use the automatic unmarshalling and just send, somehow, the thrown exception to the client. – Sorin Adrian Carbunaru Dec 06 '13 at 15:35
  • @SorinAdrianCarbunaru - Implementing `ContextResolver` may work better for you. – bdoughan Dec 20 '13 at 04:01
  • we are using TomEE and, unfortunately, Apache CXF does not support `@Provider`... a colleague with more experience than me tried to register the provider programmatically and he failed... – Sorin Adrian Carbunaru Dec 23 '13 at 15:37
  • RestEasy 3.0.7.FINAL does not understand ContextResolver See: http://grepcode.com/file/repo1.maven.org/maven2/org.jboss.resteasy/resteasy-jaxrs/3.0.7.Final/org/jboss/resteasy/spi/ResteasyProviderFactory.java#ResteasyProviderFactory.registerProviderInstance%28java.lang.Object%2Cjava.util.Map%2Cjava.lang.Integer%2Cboolean%29 The MessageBodyReader approach works though – mdzh Oct 23 '17 at 14:58
1

It seems that there isn't a built-in solution.

My solution (must be adopted to your code and can be improved (submit response-code), of course):

Create an ErrorList class

This class will serve to store the errors, which occure in the setters of the game model class

    import javax.ws.rs.WebApplicationException;
    import javax.ws.rs.core.Response;

    public class ErrorList {

    private String errorString;
    private String errorObject; 

    public ErrorList() {
    }

    public ErrorList(String errorString, String errorObject) {
        this.setErrorString(errorString);
        this.setErrorObject(errorObject);
    }

    public String getErrorString() {
        return errorString;
    }

    public void setErrorString(String errorString) {
        this.errorString = errorString;
    }

    public String getErrorObject() {
        return errorObject;
    }

    public void setErrorObject(String errorObject) {
        this.errorObject = errorObject;
    }

    public Response sendError() {
        throw new WebApplicationException(
                Response.status(400)
                .header("Access-Control-Allow-Origin", "*")
                .entity(this.getErrorString() + " caused by " + this.getErrorObject())
                .build());  
    }
}

Add a new list property to the game model class

    @XmlTransient
    private ArrayList<ErrorList> errorList = new ArrayList<ErrorList>();

Now you can check for errors in your setters and add errors to the list

    public void setUrl(String url) {        
      String regex = "\\b(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
      Pattern pattern = Pattern.compile(regex);

      if (url == null || url.length() == 0) {
        // Create a new element for the errorlist with your explanations
        ErrorList error = new ErrorList("ERROR_PROPERTY","Game.url"); 
        // Add it to the errorList of the instance
        this.errorList.add(error);      
        return;
      }

      Matcher matcher = pattern.matcher(url);
      if (!matcher.matches()) {
        // Create a new element for the errorlist with your explanations
        ErrorList error = new ErrorList("ERROR_PROPERTY","Game.url"); 
        // Add it to the errorList of the instance
        this.errorList.add(error);  
        return;
      }

      this.url = url;
    }

Add a method to the game model class, that checks if the errorList is populated

public static void checkErrorList(Game gameData) {
    if (gameData.errorList != null && !gameData.errorList.isEmpty()) {
        gameData.errorList.get(0).sendError();
    }       
}

Finally

Now you can add

    checkErrorList(yourGameInstance);

to your methods at the beginning, for example

    public static Response createRow(Game gameData) {
      checkErrorList(gameData);
      ...
    }

and whenever you setSomething, you should call it again, before you try to send the data to a db or so.

    public static Response createRow(Game gameData, String newUrl) {
      checkErrorList(gameData);
      ...
      gameData.setUrl(newUrl);
      checkErrorList(gameData);
    }

It's not as fancy as it would be with a build-in function, but at least it get's the job done.

And yes, I'm new to (JAVA/)JAXB.

The approach could be simplified by leaving out the ErrorList class and use a one-dimensional ArrayList inside of the game class.

  • I suppose this would work, but, as you said, it is not a "fancy" solution. I am still waiting for an answer related to the automatic unmarshalling. Either one which shows me how to solve the problem the way I want, or one which simply confirms that what I want is not possible. – Sorin Adrian Carbunaru Dec 19 '13 at 13:43
0

Throwing exceptions to perform flow control and validation logic of known possible outcomes is not recommended. Throwing an exception is akin to a goto statement in other languages, telling the flow control I want to automatically go here. Sounds like you could include the URL status/validation in the Response and call an isUrlValid method like this to accomplish what you need:

public void setUrl(String url){
      this.url = url;
}

public boolean isUrlValid(){
  String regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
  Pattern pattern = Pattern.compile(regex);

  System.out.println("URL: " + this.url);
  Matcher matcher = pattern.matcher(this.url);
  return matcher.matches();
}

public boolean isUrlEmpty(){
  return this.url == null || this.url.length() == 0;
}

Response:

@PUT
@Path("/{name}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createRow(
    @PathParam("name") String name, 
    Game gameData){

  if(!gameData.isUrlEmpty() && gameDate.isUrlValid()){
      Game.createRow(gameData); 
  }

  String json = //convert Game entity to json or include error msgs in JSON
  return Response.ok(json, MediaType.APPLICATION_JSON).build();

}

Sam Nunnally
  • 2,291
  • 2
  • 19
  • 30
  • the issue would be that we want to keep the validations on the setters... we believe that it is a setter's duty to do the validations... – Sorin Adrian Carbunaru Dec 23 '13 at 15:31
  • Seems like a lot of work and extra complication, though i guess we all have different beliefs :) Based on your example, if you continue down this path I would definitely create a specific exception class or use an existing specific exception rather than the generic exception class. – Sam Nunnally Dec 23 '13 at 15:56
  • I have a custom exception extending `WebApplicationException`.Before posting this I was performing some experiments with both `Exception` and my custom exception and the last experiment was probably with `Exception`. – Sorin Adrian Carbunaru Dec 23 '13 at 16:51