Im using the restlet framework to manager a projects API. It seems that by default error responses are formatted in HTML. How can I change that so that by default ALL error responses are in JSON format?
I've tried adding a custom converter which works great for the entity responses but not for error responses.
We have 110+ endpoints that support application/json so ideally I would like to just set the default errors to always return as JSON. The default converter works for all methods that return an actual entity.
#Get("json")
#Produces("application/json")
public User represent() {
...
return result;
}
But the ResourceException thrown by this method returns HTML.
If you are sure about the format your service is going to produce then you can annotate your service class with #Produces annotation at class level. Then you will not be required to define the same for each and every method.
Also, once #Produces is defined at class level and you want to change response format for a particular method then you can annotate that particular method for other format.
Try Below code..
public Response represent(){
try{
}catch(Exception ex){
return Response.status(500)
.entity(new ExceptionMessage("500", ex.getMessage()))
.type(MediaType.APPLICATION_JSON).
build();
}
return Response.status(Response.Status.OK).entity(result).build();
}
And have below Model class for exception message.
#XmlRootElement
class ExceptionMessage{
private String statusCode;
private String errorMessage;
public ExceptionMessage() {
}
public ExceptionMessage(String statusCode, String errorMessage) {
this.statusCode = statusCode;
this.errorMessage = errorMessage;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getStatusCode() {
return statusCode;
}
public void setStatusCode(String statusCode) {
this.statusCode = statusCode;
}
}
This is the link dedicated to Restlet.
Related
I have a service method that returns a UserDto object. However there are several situations where the request to the controller would return a HTTP status other than 200 but the service can only return null or the UserDto.
Is it bad practice to move some of the business logic to the controller and call the repository directly in order to return more detailed error messages, since the service cannot pass back an error message to the contoller?
You can do a bit better by having the service throw business exception, and the controller react on that. For example, a CustomerService could throw a `CustomerNotFoundException', and the controller could turn that into an appropriate HTTP status code, like this:
#ExceptionHandler({ CustomerNotFoundException.class })
public ResponseEntity handleException(CustomerNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
I recommend against moving the business logic to the controller, since the controller is more of an infrastructure component than domain logic. Also, consider adding another protocol, for example, a binary protocol, which wouldn't use the controller layer. You might miss your validations or business rules.
You can also use #ControllerAdvice from Spring to handle such cases, take a look at the below code.I hope this should help you return detailed error messages to the the controller.
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(NoSuchUserException.class)
public ResponseEntity<Object> handleNoSuchPinCodeException(
NoSuchUserException ex) {
ApiError apiError = new ApiError(HttpStatus.NOT_FOUND);
apiError.setErrorMessage(ex.getMessage());
return buildResponseEntity(apiError);
}
private ResponseEntity<Object> buildResponseEntity(ApiError apiError) {
return new ResponseEntity<>(apiError, apiError.getStatus());
}
}
public class NoSuchUserException extends Exception{
public NoSuchUserException (String message) {
super(message);
}
}
public class ApiError {
private HttpStatus status;
private String errorMessage;
private ApiError() {
}
public ApiError(HttpStatus status) {
this();
this.status = status;
}
public ApiError(HttpStatus status, String errorMessage, Throwable ex) {
this();
this.status = status;
this.errorMessage = errorMessage;
}
public HttpStatus getStatus() {
return status;
}
public void setStatus(HttpStatus status) {
this.status = status;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
I have created a new exception class in my Dropwizard service that extends BadRequestException.
public class CustomBadRequestException extends BadRequestException {
private static final long serialVersionUID = 1L;
private List<ValidationFailureDto> validationFailures;
public CustomBadRequestException() {
super();
}
public CustomBadRequestException(final List<ValidationFailureDto> validationFailures) {
super();
this.validationFailures = validationFailures;
}
#ApiModelProperty(value = "List of validationFailures")
public List<ValidationFailureDto> getValidationFailures() {
return validationFailures;
}
}
When I throw that exception at first I was only getting back the deserialised BadRequestException, minus the additional property (validationFailures)
{
code: "400",
message: "Bad request"
}
This is because Dropwizard's internals have a default exception mapper that allows Jetty/Jackson to understand domain exceptions and how to send the appropriate HTTP response.
To overcome this you can implement your own ExceptionMapper class and register it with Dropwizard.
public class CustomBadRequestExceptionMapper implements ExceptionMapper<SamplePackOrderBadRequestException> {
/**
* Allows jackson to deserialise custom exceptions and its properties to JSON response
*
* #param exception exception
* #return response object
*/
#Override
public Response toResponse(final SamplePackOrderBadRequestException exception) {
if (exception instanceof SamplePackOrderBadRequestException) {
SamplePackOrderBadRequestException samplePackOrderBadRequestException
= (SamplePackOrderBadRequestException) exception;
return Response
.status(400)
.entity(samplePackOrderBadRequestException)
.build();
}
return Response.status(400).build();
}
}
However this issue with this is that it deserializes super (Throwable), so you get every single inherited property added in the response which I do not want.
To combat this I tried adding Jackson annotations like so:
#JsonIgnoreProperties(value = "stackTrace")
This is not an optimal solution as there are several properties other than stackTrace that I will need to ignore.
So to summarise, how can I get Dropwizard to properly deserialize my CustomException class without all the additional clutter that I do not need?
I think the easier option is to transform exception to a Error bean and return it as shown below.
public class CustomBadRequestExceptionMapper implements ExceptionMapper<SamplePackOrderBadRequestException> {
#Override
public Response toResponse(final SamplePackOrderBadRequestException exception) {
if (exception instanceof SamplePackOrderBadRequestException) {
SamplePackOrderBadRequestException ex
= (SamplePackOrderBadRequestException) exception;
return Response
.status(400)
.entity(new ErrorBean(400,ex.getMessage,ex.getgetValidationFailures()))
.build();
}
return Response.status(400).build();
}
}
And ErrorBean.java
public static class ErrorBean{
private int code;
private String message;
private List<ValidationFailureDto> failures;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public List<ValidationFailureDto> getFailures() {
return failures;
}
public void setFailures(List<ValidationFailureDto> failures) {
this.failures = failures;
}
}
I have a Spring controller that takes posts and it works. The only problem is that our SMS providers will be sending us headers that contain keys with a capitalized first letter, for example:
{
"FromPhoneNumber":"15177754077",
"ToPhoneNumber":"17572046106",
"ResponseReceiveDate":"7/29/2014 5:25:10 AM",
"Message":"PIN 1234"
}
Spring will throw an error like:
Could not read JSON: Unrecognized field "FromPhoneNumber" (class com.talksoft.spring.rest.domain.CDynePost), not marked as ignorable (4 known properties: "responseReceiveDate", "toPhoneNumber", "fromPhoneNumber", "message"])
So, there must be a way for me to override this behavior. Here is the controller method that handles the CDyne posts:
#RequestMapping(method = RequestMethod.POST, value="/celltrust")
public ResponseEntity<String> cellTrustPost(#RequestBody CDynePost cDynePost) {
String message = "FAILED";
UserInteraction userInteraction = getUserInteraction(cDynePost);
boolean success = someSpringService.logMessage(userInteraction);
if (success) {
message = "OK";
return new ResponseEntity<String>(message, HttpStatus.ACCEPTED);
} else {
return new ResponseEntity<String>(message, HttpStatus.FAILED_DEPENDENCY);
}
}
and here is the CDynePost class:
public class CDynePost {
private String FromPhoneNumber;
private String ToPhoneNumber;
private String ResponseReceiveDate;
private String Message;
public String getFromPhoneNumber() {
return FromPhoneNumber;
}
public void setFromPhoneNumber(String FromPhoneNumber) {
this.FromPhoneNumber = FromPhoneNumber;
}
public String getToPhoneNumber() {
return ToPhoneNumber;
}
public void setToPhoneNumber(String ToPhoneNumber) {
this.ToPhoneNumber = ToPhoneNumber;
}
public String getResponseReceiveDate() {
return ResponseReceiveDate;
}
public void setResponseReceiveDate(String ResponseReceiveDate) {
this.ResponseReceiveDate = ResponseReceiveDate;
}
public String getMessage() {
return Message;
}
public void setMessage(String Message) {
this.Message = Message;
}
}
I've looked at ObjectMapper but I am not sure how to work this into my controller, and truth be told I'd prefer not to have to write a bunch of extra classes if Spring will do it for free.
Simply annotate your field, getter, or setter with #JsonProperty, specifying the exact name that will appear in the JSON. For example
#JsonProperty("FromPhoneNumber")
private String FromPhoneNumber;
I have a controller like below
#Controller("myController")
#RequestMapping("api")
public class MyController {
#RequestMapping(method = RequestMethod.GET, value = "/get/info/{id}", headers = "Accept=application/json")
public #ResponseBody
Student getInfo(#PathVariable String info) {
.................
}
#ExceptionHandler(Throwable.class)
#ResponseStatus( HttpStatus.EXPECTATION_FAILED)
#ResponseBody
public String handleIOException(Throwable ex) {
ErrorResponse errorResponse = errorHandler.handelErrorResponse(ex);
return errorResponse.toString();
}
}
The controller has an error handling mechanism, in the error handling option it always return expectation fail status code 417. But I need to set a dynamic error Http status code like 500, 403 etc depending on type of error. How do I do this?
You need to change the type of the output value ResponseEntity. Answer here:
How to respond with HTTP 400 error in a Spring MVC #ResponseBody method returning String?
I get a solution and going to share this and also like to know any good suggestions.
#Controller("myController")
#RequestMapping("api")
public class MyController {
#RequestMapping(method = RequestMethod.GET, value = "/get/info/{id}", headers = "Accept=application/json")
public #ResponseBody
Student getInfo(#PathVariable String info) {
// ...
}
}
// ...
#ExceptionHandler(Throwable.class)
//#ResponseStatus( HttpStatus.EXPECTATION_FAILED)<<remove this line
#ResponseBody
public String handleIOException(HttpServletResponse httpRes,Throwable ex){ // <<Change this
if (some condition) {
httpRes.setStatus(HttpStatus.BAD_GATEWAY.value());
} else {
httpRes.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
ErrorResponse errorResponse = errorHandler.handleErrorResponse(ex);
return errorResponse.toString();
}
Expected out in rest client :
502 Bad Gateway
{
"status":"BAD_GATEWAY",
"error":"java.lang.UnsupportedOperationException",
"message":"Some error message"
}
Thanks for your replies. I still need pointers for good practices.
Going by the code above, you need to be more careful about which exceptions you are throwing and handling. Setting up an exception handler for Throwable seems overly broad.
The way I do this is to create an ErrorMessage class with my XML/JSON marshalling annotations.
#XmlRootElement(name = "error")
public class ErrorMessage {
private Throwable exception;
private String message;
public ErrorMessage() {
this.message = "";
}
public ErrorMessage(String message) {
this.message = message;
}
public ErrorMessage(Throwable exception) {
this.exception = exception;
this.message = exception.getLocalizedMessage();
}
#XmlTransient
#JsonIgnore
public Throwable getException() {
return exception;
}
public void setException(Throwable exception) {
this.exception = exception;
}
#XmlElement(name = "message")
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
With that in place, I tend to create my own application exceptions and then create my exception handler methods such as:
#ExceptionHandler(ResourceNotFoundException.class)
#ResponseBody
#ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorMessage handleResourceNotFoundException(ResourceNotFoundException e, HttpServletRequest req) {
return new ErrorMessage(e);
}
#ExceptionHandler(InternalServerErrorException.class)
#ResponseBody
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorMessage handleInternalServerErrorException(InternalServerErrorException e, HttpServletRequest req) {
return new ErrorMessage(e);
}
With those in place, I just need to throw appropriate exceptions from my controller methods. For instance, if I throw a ResourceNotFoundException, then Spring will redirect that to my handleResourceNotFoundException method, which returns a 404, and that will also return JSON or XML representing the error.
You can use an Aspect for your API. If you define an #Around interceptor for your service, you can change the response content.
In Jersey, how can we 'replace' the status string associated with a known status code?
e.g.
return Response.status(401).build();
generates a HTTP response that contains:
HTTP/1.1 401 Unauthorized
I (not me, but the client application) would like to see the response as:
HTTP/1.1 401 Authorization Required
I tried the following approaches but in vain:
1) This just adds the String in the body of the HTTP response
return Response.status(401).entity("Authorization Required").build();
2) Same result with this too:
ResponseBuilder rb = Response.status(401);
rb = rb.tag("Authorization Required");
return rb.build();
Appreciate your help!
-spd
To do this in Jersey you have the concept of WebApplicationException class. One method is to simply extend this class and all one of the methods to set the error text that is returned. In your case this would be:
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.*;
public class UnauthorizedException extends WebApplicationException {
/**
* Create a HTTP 401 (Unauthorized) exception.
*/
public UnauthorizedException() {
super(Response.status(Status.UNAUTHORIZED).build());
}
/**
* Create a HTTP 404 (Not Found) exception.
* #param message the String that is the entity of the 404 response.
*/
public UnauthorizedException(String message) {
super(Response.status(Status.UNAUTHORIZED).entity(message).type("text/plain").build());
}
}
Now in your code that implements the rest service you would simply throw a new exception of this type, passing in the text value in the constructor e.g.
throw new UnauthorizedException("Authorization Required");
That can create a class like this for each of your web exceptions and throw in a similar fashion.
This is also explained in the Jersey user guide - although the code is actually slightly incorrect:
https://jersey.github.io/nonav/documentation/latest/user-guide.html/#d4e435
I'm not sure JSR 339: JAX-RS 2.0: The Java API for RESTful Web Services already covered this or not.
You might have to extend the Response.StatusType for this.
public abstract class AbstractStatusType implements StatusType {
public AbstractStatusType(final Family family, final int statusCode,
final String reasonPhrase) {
super();
this.family = family;
this.statusCode = statusCode;
this.reasonPhrase = reasonPhrase;
}
protected AbstractStatusType(final Status status,
final String reasonPhrase) {
this(status.getFamily(), status.getStatusCode(), reasonPhrase);
}
#Override
public Family getFamily() { return family; }
#Override
public String getReasonPhrase() { return reasonPhrase; }
#Override
public int getStatusCode() { return statusCode; }
public ResponseBuilder responseBuilder() { return Response.status(this); }
public Response build() { return responseBuilder().build(); }
public WebApplicationException except() {
return new WebApplicationException(build());
}
private final Family family;
private final int statusCode;
private final String reasonPhrase;
}
And here are some extended statust types.
public class BadRequest400 extends AbstractStatusType {
public BadRequest400(final String reasonPhrase) {
super(Status.BAD_REQUEST, reasonPhrase);
}
}
public class NotFound404 extends AbstractStatusType {
public NotFound404(final String reasonPhrase) {
super(Status.NOT_FOUND, reasonPhrase);
}
}
This is how I do.
#POST
public Response create(final MyEntity entity) {
throw new BadRequest400("bad ass").except();
}
#GET
public MyEntity read(#QueryParam("id") final long id) {
throw new NotFound404("ass ignorant").except();
}
// Disclaimer
// I'm not a native English speaker.
// I don't know what 'bad ass' or 'ass ignorant' means.