In my REST application I have a #POST method which consumes x-www-form-urlencoded and produces application/json and is designed to create new appointments for a beauty center:
#POST
#Produces("application/json")
#Consumes("application/x-www-form-urlencoded")
public Response bookAppointment(#FormParam("date") Date appDate,
#FormParam("time") Time appTime, #FormParam("type") String appType,
#FormParam("clientName") String clientName, #FormParam("email") String clientEmail,
#DefaultValue("") #FormParam("message") String clientMsg) {
//externalize the validation of all fields to concentrate on "positive"
//scenario only
validator(appDate, appType, clientName, clientEmail);
Appointment appointment = build(appDate, appTime, appType,clientName,
clientEmail, clientMsg);
try {
repository.add(appointment);
return Response.ok(appointment).build();
} catch (Exception e) {
throw new InternalServerErrorException("Something happened in the application "
+ "and this apointment could not get saved. Please contact us "
+ "to inform us of this issue.");
}
}
As you may see for two Endpoint method attributes are Java objects (sql.Time and sql.Date). To convert them from strings coming from the client I am using ParamConverterProviders as taught in B. Burke's Restful Java with JAX-RS (p. 70-71) and this Stack Overflow question.
I am using Postman chrome add-on to send requests and everything works fine and dandy if for time and date I am sending the full String required attributes for valueOf() builder methods of sql.Date and sql.Time(i.e. yyyy-MM-dd and hh:MM:ss, respectively). However when I am sending time without seconds I receive the 404 Exception with a generic message.
This is what I am sending from Postman:
If you will take a look at one of the ParamConverterProviders you will see that I am accounting for partial user input and in either way am throwing a custom message for BadRequestException (which works for other validation constraints):
#Provider
public class SqlTimeConverterProvider implements ParamConverterProvider {
#Override
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
System.out.println("SqlTimeConverterProvider");
if (rawType.equals(Time.class)) {
return new ParamConverter<T>() {
#Override
public T fromString(String value) {
//in case the ParamConverter does not do URI deconding
value = value.replaceAll("%3A", ":");
if (value.length() < 6) {
value = value.concat(":00");
}
if (!value.matches("([01]?[0-9]|2[0-3]):[0-5][0-9]"
+ "(:[0-5][0-9])*")) {
throw new BadRequestException(value + " is not an accepted Time format "
+ "please use this pattern: hh:mm:SS");
}
return rawType.cast(Time.valueOf(value));
}
#Override
public String toString(T value) {
Time timeRepr = (Time) value;
if (timeRepr.toLocalTime().getMinute() < 10) {
String reply = timeRepr.toLocalTime().getHour()+":"
+timeRepr.toLocalTime().getMinute();
return reply.concat("0");
}
return timeRepr.toLocalTime().toString();
}
};
}
return null;
}
}
Interesting enough using NetBeans debugger I have discovered that after this line in the code above:
System.out.println("SqlTimeConverterProvider");
if (rawType.equals(Time.class)) {
the 'Step over' moves the break-point to return null and the same thing repeats for the Date converter provider. As I am following meticulously the example from the book I am unsure as to why is the rawType being evaluated to false
Related
I need to set different HTTP Status code for my REST webservice request.
Basically user will send ISBN number , I need to validate it
if user send empty request body , give error message ISBN cannot be empty
and set http status code
if user gives Alphabets , Given error message Alphabets not allowed and set http status code appropriate
if user gives wrong format, Give error message wrong format and set different HTTP status code.
if isbn is not valid, Give error message Not a Valid ISBN number and set appropriate HTTP status code.
If Valid ISBN number then return book name with http status as 200.
I tried setting http status code but its not reflecting.
#RequestMapping(value = "/person", method = RequestMethod.POST,
consumes = "application/json", produces = "application/json")
public ResponseEntity<StatusBean> findBook(#RequestBody String json) {
StatusBean sb = new StatusBean();
if(json==null) {
sb.setMessage("Request Cannot be Null");
return new ResponseEntity<StatusBean>(sb,HttpStatus.BAD_REQUEST);
}
if(!isNumeric(json)) {
sb.setMessage("Request Cannot have Alphabets Characters");
//here i need to set different status
return new ResponseEntity<StatusBean>(sb,HttpStatus.BAD_REQUEST);
}
if(!isValidFormat(json)) {
sb.setMessage("Request Cannot have Alphabets Characters");
//here i need to set different status
return new ResponseEntity<StatusBean>(sb,HttpStatus.BAD_REQUEST);
}
if(!isValidISBN(json)) {
sb.setMessage("Request Cannot have Alphabets Characters");
//here i need to set different status
return new ResponseEntity<StatusBean>(sb,HttpStatus.BAD_REQUEST);
}
Map<String,String> map = new HashMap<>();
map.put("book", "Effective Java");
sb.setResponseJSONMap(map);
return new ResponseEntity<StatusBean>(sb,HttpStatus.OK);
}
public class StatusBean {
private String message;
private Map<String,String> responseJSONMap;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Map<String, String> getResponseJSONMap() {
return responseJSONMap;
}
public void setResponseJSONMap(Map<String, String> responseJSONMap) {
this.responseJSONMap = responseJSONMap;
}
}
One of the most elegant solutions is the following:
You can throw a custom exception in case of a validation error, like this:
#RequestMapping(...)
public ResponseEntity<StatusBean> findBook(#RequestBody String json) throws Exception {
...
if(json==null) {
throw new NullRequestException();
}
if(!isNumeric(json)) {
throw new RequestContainsAlphabetsException();
}
if(!isValidFormat(json)) {
throw new InvalidFormatException();
}
...
}
And then you need to define an own, global exception handler at application level. In this custom exception handler, you will catch the thrown exceptions and send back a proper response to clients with a custom error message, an HTTP response code, a timestamp, etc.
For more details see this page.
#RequestMapping(value = "/person", method = RequestMethod.POST,
consumes = "application/json", produces = "application/json")
public ResponseEntity<StatusBean> findBook(#RequestBody(required=false) String json) {
// rest of the code
}
try using (required=false) with request body. Spring requires resuest body by default.
The original question is here:
How to resolve URI encoding problem in spring-boot?. And following one of the suggestions, I am trying to come up with a solution with an Interceptor, but still has some issue.
I need to be able to handle some special characters in URL, for instance "%" and my spring controller is below:
#Controller
#EnableAutoConfiguration
public class QueryController {
private static final Logger LOGGER = LoggerFactory.getLogger(QueryController.class);
#Autowired
QueryService jnService;
#RequestMapping(value="/extract", method = RequestMethod.GET)
#SuppressWarnings("unchecked")
#ResponseBody
public ExtractionResponse extract(#RequestParam(value = "extractionInput") String input) {
// LOGGER.info("input: " + input);
JSONObject inputObject = JSON.parseObject(input);
InputInfo inputInfo = new InputInfo();
JSONObject object = (JSONObject) inputObject.get(InputInfo.INPUT_INFO);
String inputText = object.getString(InputInfo.INPUT_TEXT);
inputInfo.setInputText(inputText);
return jnService.getExtraction(inputInfo);
}
}
Following suggestions I want to write an interceptor to encode the URL before the request is sent to the controller, and my interceptor looks like (not complete yet):
public class ParameterInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(ParameterInterceptor.class);
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse reponse,
Object handler) throws Exception {
Enumeration<?> e = request.getParameterNames();
LOGGER.info("Request URL::" + request.getRequestURL().toString());
StringBuffer sb = new StringBuffer();
if (e != null) {
sb.append("?");
}
while (e.hasMoreElements()) {
String curr = (String) e.nextElement();
sb.append(curr + "=");
sb.append(request.getParameter(curr));
}
LOGGER.info("Parameter: " + sb.toString());
return true;
}
}
I tested a URL in my browser:
http://localhost:8090/extract?extractionInput={"inputInfo":{"inputText":"5.00%"}}
Due to the % sign, I received the error:
[log] - Character decoding failed. Parameter [extractionInput] with value [{"inputInfo":{"inputText":"5.0022:%225.00%%22}}] has been ignored. Note that the name and value quoted here may be corrupted due to the failed decoding.
When I test the interceptor, "request.getRequestURL()" gives the expected result:
http://localhost:8090/extract
However, "request.getParameterNames()" always get an empty Elumentation object. Why doesn't it get the parameters? What I hope is to first encode the parameter value:
"inputText":"5.00%"
'inputText' is a field of the object InputInfo in the json format. So how to get the request parameters to solve the problem?
I am using Jackson to interpret JSON responses from an API that I am writing. I would like, as a standard throughout my API, to throw errors from the API to the program with something like:
{"errorMessage":"No such username."}
So I want my response processor to first check if the response is just a single errorMessage key, and if so to handle the error, and if not, then to interpret it as whatever response it was expecting from that command.
So here's my code:
public class ProcessingException extends Exception {
private String errorMessage;
public ProcessingException(){}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
and then, in my response handler:
#Override
public void useResponse(InputStream in) throws IOException, ProcessingException {
// turn response into a string
java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\\A");
String response = s.hasNext() ? s.next() : "";
ProcessingException exception;
try {
// Attempt to interpret as an exception
exception = mapper.readValue(response, ProcessingException.class);
}
catch(IOException e) {
// Otherwise interpret it as expected. responseType() is an abstract TypeReference
// which is filled in by subclasses. useResponse() is also abstract. Each subclass
// represents a different kind of request.
Object responseObj = mapper.readValue(response, responseType());
useResponse(responseObj);
return;
}
// I needed this out of the try/catch clause because listener.errorResponse might
// actually choose to throw the passed exception to be dealt with by a higher
// authority.
if (listener!=null) listener.errorResponse(exception);
}
This works beautifully, except under one circumstance - there are some requests that actually don't need to respond with anything, so they return {}. For some reason, this response completely runs through the exception = mapper.readValue(response, ProcessingException.class); line without triggering an IOException, so the program things that there is an error. But then when it tries to read what the error was, it throws a NullPointerException when trying to read exception.getErrorMessage(), because of course there is no error.
Why is it treating {} as a valid ProcessingException object?
Jackson doesn't have bean validation. But what you can do is to declare constructor as a JsonCreator that will be used to instantiate the new object and check/throw an exception in case if that field is null:
class ProcessingException {
private String errorMessage;
#JsonCreator
public ProcessingException(#JsonProperty("errorMessage") String errorMessage) {
if (errorMessage == null) {
throw new IllegalArgumentException("'errorMessage' can't be null");
}
this.errorMessage = errorMessage;
}
// getters, setters and other methods
}
I am starting to use the new client API library in JAX-RS and really loving it so far. I have found one thing I cannot figure out however. The API I am using has a custom error message format that looks like this for example:
{
"code": 400,
"message": "This is a message which describes why there was a code 400."
}
It returns 400 as the status code but also includes a descriptive error message to tell you what you did wrong.
However the JAX-RS 2.0 client is re-mapping the 400 status into something generic and I lose the good error message. It correctly maps it to a BadRequestException, but with a generic "HTTP 400 Bad Request" message.
javax.ws.rs.BadRequestException: HTTP 400 Bad Request
at org.glassfish.jersey.client.JerseyInvocation.convertToException(JerseyInvocation.java:908)
at org.glassfish.jersey.client.JerseyInvocation.translate(JerseyInvocation.java:770)
at org.glassfish.jersey.client.JerseyInvocation.access$500(JerseyInvocation.java:90)
at org.glassfish.jersey.client.JerseyInvocation$2.call(JerseyInvocation.java:671)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:228)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:424)
at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:667)
at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:396)
at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:296)
Is there some sort of interceptor or custom error handler that can be injected so that I get access to the real error message. I've been looking through documentation but can't see any way of doing it.
I am using Jersey right now, but I tried this using CXF and got the same result. Here is what the code looks like.
Client client = ClientBuilder.newClient().register(JacksonFeature.class).register(GzipInterceptor.class);
WebTarget target = client.target("https://somesite.com").path("/api/test");
Invocation.Builder builder = target.request()
.header("some_header", value)
.accept(MediaType.APPLICATION_JSON_TYPE)
.acceptEncoding("gzip");
MyEntity entity = builder.get(MyEntity.class);
UPDATE:
I implemented the solution listed in the comment below. It is slightly different since the classes have changed a bit in the JAX-RS 2.0 client API. I still think it is wrong that the default behavior is to give a generic error message and discard the real one. I understand why it wouldn't parse my error object, but the un-parsed version should have been returned. I end up having the replicate exception mapping that the library already does.
Thanks for the help.
Here is my filter class:
#Provider
public class ErrorResponseFilter implements ClientResponseFilter {
private static ObjectMapper _MAPPER = new ObjectMapper();
#Override
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
// for non-200 response, deal with the custom error messages
if (responseContext.getStatus() != Response.Status.OK.getStatusCode()) {
if (responseContext.hasEntity()) {
// get the "real" error message
ErrorResponse error = _MAPPER.readValue(responseContext.getEntityStream(), ErrorResponse.class);
String message = error.getMessage();
Response.Status status = Response.Status.fromStatusCode(responseContext.getStatus());
WebApplicationException webAppException;
switch (status) {
case BAD_REQUEST:
webAppException = new BadRequestException(message);
break;
case UNAUTHORIZED:
webAppException = new NotAuthorizedException(message);
break;
case FORBIDDEN:
webAppException = new ForbiddenException(message);
break;
case NOT_FOUND:
webAppException = new NotFoundException(message);
break;
case METHOD_NOT_ALLOWED:
webAppException = new NotAllowedException(message);
break;
case NOT_ACCEPTABLE:
webAppException = new NotAcceptableException(message);
break;
case UNSUPPORTED_MEDIA_TYPE:
webAppException = new NotSupportedException(message);
break;
case INTERNAL_SERVER_ERROR:
webAppException = new InternalServerErrorException(message);
break;
case SERVICE_UNAVAILABLE:
webAppException = new ServiceUnavailableException(message);
break;
default:
webAppException = new WebApplicationException(message);
}
throw webAppException;
}
}
}
}
I believe you want to do something like this:
Response response = builder.get( Response.class );
if ( response.getStatusCode() != Response.Status.OK.getStatusCode() ) {
System.out.println( response.getStatusType() );
return null;
}
return response.readEntity( MyEntity.class );
Another thing you can try (since I don't know where this API puts stuff -- i.e. in the header or entity or what) is:
Response response = builder.get( Response.class );
if ( response.getStatusCode() != Response.Status.OK.getStatusCode() ) {
// if they put the custom error stuff in the entity
System.out.println( response.readEntity( String.class ) );
return null;
}
return response.readEntity( MyEntity.class );
If you would like to generally map REST response codes to Java exception you can add a client filter to do that:
class ClientResponseLoggingFilter implements ClientResponseFilter {
#Override
public void filter(final ClientRequestContext reqCtx,
final ClientResponseContext resCtx) throws IOException {
if ( resCtx.getStatus() == Response.Status.BAD_REQUEST.getStatusCode() ) {
throw new MyClientException( resCtx.getStatusInfo() );
}
...
In the above filter you can create specific exceptions for each code or create one generic exception type that wraps the Response code and entity.
There are other ways to getting a custom error message to the Jersey client besides writing a custom filter. (although the filter is an excellent solution)
1) Pass error message in an HTTP header field.
The detail error message could be in the JSON response and in an additional header field, such as "x-error-message".
The Server adds the HTTP error header.
ResponseBuilder rb = Response.status(respCode.getCode()).entity(resp);
if (!StringUtils.isEmpty(errMsg)){
rb.header("x-error-message", errMsg);
}
return rb.build();
The Client catches the exception, NotFoundException in my case, and reads the response header.
try {
Integer accountId = 2222;
Client client = ClientBuilder.newClient();
WebTarget webTarget = client.target("http://localhost:8080/rest-jersey/rest");
webTarget = webTarget.path("/accounts/"+ accountId);
Invocation.Builder ib = webTarget.request(MediaType.APPLICATION_JSON);
Account resp = ib.get(new GenericType<Account>() {
});
} catch (NotFoundException e) {
String errorMsg = e.getResponse().getHeaderString("x-error-message");
// do whatever ...
return;
}
2) Another solution is to catch the exception and read the response content.
try {
// same as above ...
} catch (NotFoundException e) {
String respString = e.getResponse().readEntity(String.class);
// you can convert to JSON or search for error message in String ...
return;
}
The class WebApplicationException was designed for that but for some reason it ignores and overwrites what you specify as parameter for the message.
For that reason I created my own extension WebAppException that honors the parameters. It is a single class and it doesn't require any response filter or a mapper.
I prefer exceptions than creating a Response as it can be thrown from anywhere while processing.
Simple usage:
throw new WebAppException(Status.BAD_REQUEST, "Field 'name' is missing.");
The class:
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.Response.Status.Family;
import javax.ws.rs.core.Response.StatusType;
public class WebAppException extends WebApplicationException {
private static final long serialVersionUID = -9079411854450419091L;
public static class MyStatus implements StatusType {
final int statusCode;
final String reasonPhrase;
public MyStatus(int statusCode, String reasonPhrase) {
this.statusCode = statusCode;
this.reasonPhrase = reasonPhrase;
}
#Override
public int getStatusCode() {
return statusCode;
}
#Override
public Family getFamily() {
return Family.familyOf(statusCode);
}
#Override
public String getReasonPhrase() {
return reasonPhrase;
}
}
public WebAppException() {
}
public WebAppException(int status) {
super(status);
}
public WebAppException(Response response) {
super(response);
}
public WebAppException(Status status) {
super(status);
}
public WebAppException(String message, Response response) {
super(message, response);
}
public WebAppException(int status, String message) {
super(message, Response.status(new MyStatus(status, message)). build());
}
public WebAppException(Status status, String message) {
this(status.getStatusCode(), message);
}
public WebAppException(String message) {
this(500, message);
}
}
A much more concise solution for anyone stumbling on this:
Calling .get(Class<T> responseType) or any of the other methods that take the result type as an argument Invocation.Builder will return a value of the desired type instead of a Response. As a side effect, these methods will check if the received status code is in the 2xx range and throw an appropriate WebApplicationException otherwise.
From the documentation:
Throws: WebApplicationException in case the response status code of
the response returned by the server is not successful and the
specified response type is not Response.
This allows to catch the WebApplicationException, retrieve the actual Response, process the contained entity as exception details (ApiExceptionInfo) and throw an appropriate exception (ApiException).
public <Result> Result get(String path, Class<Result> resultType) {
return perform("GET", path, null, resultType);
}
public <Result> Result post(String path, Object content, Class<Result> resultType) {
return perform("POST", path, content, resultType);
}
private <Result> Result perform(String method, String path, Object content, Class<Result> resultType) {
try {
Entity<Object> entity = null == content ? null : Entity.entity(content, MediaType.APPLICATION_JSON);
return client.target(uri).path(path).request(MediaType.APPLICATION_JSON).method(method, entity, resultType);
} catch (WebApplicationException webApplicationException) {
Response response = webApplicationException.getResponse();
if (response.getMediaType().equals(MediaType.APPLICATION_JSON_TYPE)) {
throw new ApiException(response.readEntity(ApiExceptionInfo.class), webApplicationException);
} else {
throw webApplicationException;
}
}
}
ApiExceptionInfo is custom data type in my application:
import lombok.Data;
#Data
public class ApiExceptionInfo {
private int code;
private String message;
}
ApiException is custom exception type in my application:
import lombok.Getter;
public class ApiException extends RuntimeException {
#Getter
private final ApiExceptionInfo info;
public ApiException(ApiExceptionInfo info, Exception cause) {
super(info.toString(), cause);
this.info = info;
}
}
[At least with Resteasy] there is one big disadvantage with the solution offered by #Chuck M and based on ClientResponseFilter.
When you use it based on ClientResponseFilter, your BadRequestException, NotAuthorizedException, ... exceptions are wrapped by javax.ws.rs.ProcessingException.
Clients of your proxy must not be forced to catch this javax.ws.rs.ResponseProcessingException exception.
Without filter, we get an original rest exception. If we catch and handle by default, it does not give us much:
catch (WebApplicationException e) {
//does not return response body:
e.toString();
// returns null:
e.getCause();
}
The problem can be solved on another level, when you extract a description from the error. WebApplicationException exception, which is a parent for all rest exceptions, contains javax.ws.rs.core.Response. Just write a helper method, that in case the exception is of WebApplicationException type, it will also check the response body. Here is a code in Scala, but the idea should be clear. The methord returns a clear description of the rest exception:
private def descriptiveWebException2String(t: WebApplicationException): String = {
if (t.getResponse.hasEntity)
s"${t.toString}. Response: ${t.getResponse.readEntity(classOf[String])}"
else t.toString
}
Now we move a responsibility to show exact error, on the client. Just use a shared exception handler to minimize effort for clients.
The following works for me
Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
I'm using RESTful application in my java project. Normally in my unit test classes, I use methods like this:
public Employee getEmployeeByEmail(String email) {
ClientResponse clientResponse = webResource.path(beginPath + "getByEmail/" + email).get(
ClientResponse.class);
Employee employee = null;
if (200 == clientResponse.getStatus()) {
employee = clientResponse.getEntity(Employee.class);
}
return employee;
}
... but I have to use similar methods in almost 12 different classes. This is what I decided to do:
public class TestManager<T> {
private WebResource webResource;
private String beginPath;
private Class<T> clazz;
public TestManager(WebResource webResource, String beginPath, Class<T> clazz) {
this.webResource = webResource;
this.beginPath = beginPath;
this.clazz = clazz;
}
public boolean objectExists(String methodPath, String uniqueFieldName, String uniqueField) {
boolean check = false;
ClientResponse clientResponse = webResource.path(beginPath + methodPath + "/" + uniqueField).get(
ClientResponse.class);
JSONObject jsonObject = clientResponse.getEntity(JSONObject.class);
if (200 == clientResponse.getStatus() && !jsonObject.isNull(uniqueFieldName)) {
check = true;
}
return check;
}
public T getObjectById(String methodPath, long id) {
ClientResponse clientResponse = webResource.path(beginPath + methodPath + "/" + id).get(
ClientResponse.class);
T object = null;
if (200 == clientResponse.getStatus() && !clientResponse.getEntity(JSONObject.class).isNull("id")) {
object = clientResponse.getEntity(clazz);
}
return object;
}
}
The method objectExists() works fine, but getObjectById() method generate stack trace:
javax.ws.rs.WebApplicationException: javax.xml.bind.UnmarshalException: Error creating JSON-based XMLStreamReader - with linked exception:[javax.xml.stream.XMLStreamException: java.io.IOException: stream is closed]
It seems that I can't do this:
object = clientResponse.getEntity(clazz);
But I have no idea how to fix it. Sorry for my english :P
Edit:
Im using jersey
Edit2:
Solution:
The problem was I used getEntity() method twice ... If I use it only once ... it works ... damn it
It is a GET call. try calling it from a browser.
If that gives the same error, then there is nothing wrong with your client.
If you're having issues just with unit testing, then you might want to take a look at the Jersey Test Framework. Here's a link to the jersey docs on it, google can point you to further tutorials.