4

I'm developing a REST service using JAX-RS with Java 6 and Jersey 1.8 and my server is JBoss 5.1.0 GA, I've started with a simple endpoint that receives a parameter and return a null value.

Following this article, I mapped my expected exceptions with ExceptionMapper to three classes: AppException for exceptions within the scope of the project, NotFoundException for exceptions of records not found and GenericException for any other exception outside the expected errors.

I can return the exceptions as application/json responses just fine, but when I try to return a response as an application/xml, I get an HTML page from my server with HTTP 500 status code.

For example, if I don't send the required parameter to my endpoint, I get a HTTP 400 status code with its JSON response correctly:

{
    "status": 400,
    "message": "org.xml.sax.SAXParseException: Premature end of file.",
    "developerMessage": "javax.ws.rs.WebApplicationException: org.xml.sax.SAXParseException: Premature end of file.... 40 more\r\n"
}

But if I try to return a XML response I get this HTML page:

<html>
    <head>
        <title>JBoss Web/2.1.3.GA - Informe de Error</title>
    </head>
    <body>
        <h1>Estado HTTP 500 - </h1>
        <HR size="1" noshade="noshade">
            <p>
                <b>type</b> Informe de estado
            </p>
            <p>
                <b>mensaje</b>
                <u></u>
            </p>
            <p>
                <b>descripción</b>
                <u>El servidor encontró un error interno () que hizo que no pudiera rellenar este requerimiento.</u>
            </p>
            <HR size="1" noshade="noshade">
                <h3>JBoss Web/2.1.3.GA</h3>
     </body>
  </html>

My ErrorMessage class looks as described in the article I linked:

import java.lang.reflect.InvocationTargetException;

import javax.ws.rs.core.Response;

import org.apache.commons.beanutils.*;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import com.sun.jersey.api.NotFoundException;

@XmlRootElement(name = "errorMessage")
public class ErrorMessage {

    @XmlElement(name = "status")
    int status;

    @XmlElement(name = "message")
    String message;

    @XmlElement(name = "developerMessage")
    String developerMessage;

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getDeveloperMessage() {
        return developerMessage;
    }

    public void setDeveloperMessage(String developerMessage) {
        this.developerMessage = developerMessage;
    }

    public ErrorMessage(AppException ex) {
        try {
            BeanUtils.copyProperties(this, ex);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    public ErrorMessage(NotFoundException ex) {
        this.status = Response.Status.NOT_FOUND.getStatusCode();
        this.message = ex.getMessage();
    }

    public ErrorMessage() {
    }

    @Override
    public String toString(){
        return "ErrorMessage{" + "status=" + status + ", message='" + message + '\'' + ", developerMessage='" + developerMessage + "'}";
    }
}

Why am I unable to return a XML response when specified?

This is the GenericExceptionMapper class I'm using

@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {

    @Override
    public Response toResponse(Throwable exception) {

        ErrorMessage errorMessage = new ErrorMessage();     
        setHttpStatus(exception, errorMessage);
        errorMessage.setMessage(exception.getMessage());
        StringWriter errorStackTrace = new StringWriter();
        exception.printStackTrace(new PrintWriter(errorStackTrace));
        errorMessage.setDeveloperMessage(errorStackTrace.toString());
        // The only change is the MediaType.APPLICATION_XML, originally it was MediaType.APPLICATION_JSON
        return Response.status(errorMessage.getStatus()).entity(errorMessage).type(MediaType.APPLICATION_XML).build();
    }
    private void setHttpStatus(Throwable ex, ErrorMessage errorMessage) {
        if(ex instanceof WebApplicationException ) { 
            errorMessage.setStatus(((WebApplicationException)ex).getResponse().getStatus());
        } else {
            errorMessage.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
        }
    }

}
Uriel Arvizu
  • 1,876
  • 6
  • 37
  • 97
  • An HTTP 500 status usually means an exception occurred on the server. Look in your server log for that exception, and include the entire stack trace (including all "Caused By" sections) in your question. – VGR Feb 04 '15 at 19:01
  • If you're getting a JBoss generated error page, your code must have thrown an uncaught exception that wasn't handled (or not handled properly) by the `ExceptionMapper` in the Jersey framework. What was the exception? My first guess would be that you don't have any appropriate `MessageBodyWriter` configured for the `application/xml` response `Content-Type` – Alex Feb 04 '15 at 19:04
  • @VGR the log from JBoss only throws one line which is the one that should be caught by ExceptionMapper `2015-02-04 13:37:22,229 ERROR [STDERR] (http-localhost%2F127.0.0.1-8080-1) [Fatal Error] :-1:-1: Premature end of file.` – Uriel Arvizu Feb 04 '15 at 19:39
  • @Alex but my `toResponse` method is the same as in the article I linked, I only change the type from **MediaType.APPLICATION_JSON** to **MediaType.APPLICATION_XML**, when it's *JSON* I get an error response in *JSON* just like I want, but when it's *XML* I get the *HTML* error page. See my edited answer to check my class for mapping the exception, you'll see it's the same as the one in the article. – Uriel Arvizu Feb 04 '15 at 19:42
  • This article doesn't appear to deal with proper configuration of Jersey. The JDK nor Jersey have native support for reading/writing JSON or XML. In Jersey parlance, you need to have the proper `MessageBodyWriter` implementations on your Classpath and registered with Jersey to support writing various MediaType based responses (i.e. JSON or XML). Check the real documentation for details on how to make XML work: https://jersey.java.net/documentation/1.18/xml.html – Alex Feb 04 '15 at 19:52
  • Yes I read the documentation and that's how I got to know ExceptionMapper, the article has an example on how to use it to return it in JSON linked at the end of it. – Uriel Arvizu Feb 04 '15 at 23:07

1 Answers1

4

Providing the @XmlAccessorType(XmlAccessType.FIELD) above your ErrorMessage class should solve the problem :

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class ErrorMessage {

    /** contains the same HTTP Status code returned by the server */
    @XmlElement(name = "status")
    int status;

This JAXB annotation will tell the server to bind attributes instead of getters to your XML. Otherwise an error will be generated as this causes your XML response to have duplicate properties for XML binding(like duplicate code elements).

WHY?

By default, if @XmlAccessorType on a class is absent, and none of its super classes is annotated with @XmlAccessorType, then the following default on the class is assumed:

@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)

Next if we look into the documentation on XmlAccessType to understand PUBLIC_MEMBER here is what it has to say:

Every public getter/setter pair and every public field will be automatically bound to XML, unless annotated by XmlTransient.

So the code of ErrorMessage became conflicting as XmlElement was also used to map JavaBean property to XML. Therefore for example the property code was not only bound by XmlElement but also by it's getter method.

To overcome this problem we have two options:

Option 1:We can use XmlAccessType.FIELD so that only the fields are used and getters are omitted.

Option 2:Simply remove the XmlElement annotations from the class in which case the getters will only decide the binding.

Also, don't forget to update the toResponse message of your mapper class to reflect the output is XML:

@Override
    public Response toResponse(AppException ex) {
        return Response.status(ex.getStatus())
                .entity(new ErrorMessage(ex))
                .type(MediaType.APPLICATION_XML).
                build();
    }
ZakiMak
  • 2,072
  • 2
  • 17
  • 26
  • That did it, although I don't understand why would duplicated tag be generated, could you elaborate on that in your answer? – Uriel Arvizu Feb 04 '15 at 23:08
  • By default properties (getters/setters) are used as the access type. You have the fields annotated, so both the fields and properties are used. Using the property `getStatus/setStatus`, by convention, the element name should be `status`, the same name as the filed you have, hence the double. When you specify the access type as field, then the properties won't be used. – Paul Samsotha Feb 04 '15 at 23:51
  • 1
    @UrielArvizu I have updated the answer to provide more details on the problem and solution. Hopefully it will make it clearer. – ZakiMak Feb 05 '15 at 05:50
  • 1
    That was one of the best explainations I've ever gotten on an answer here, wish there was a way to promote this easily since small details like this can puzzle some of us. – Uriel Arvizu Feb 05 '15 at 16:20