4
votes

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());
        }
    }

}
1
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
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
@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
@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
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: jersey.java.net/documentation/1.18/xml.html - Alex

1 Answers

4
votes

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();
    }