What's the cleanest way to differientiate identical serialized objects? - java

I've got two root exception types my service is throwing
class ServiceException extends RuntimeException {
private Status status;
private String someString;
// Boilerplate omitted
}
class RetryableServiceException extends ServiceException {
// This class has no new fields
}
Because there's a common retry framework our clients will use which determines whether to retry or not based on the exception class.
But the problem, obviously, is that when the client gets the response and calls Response.readEntity(Class <T> entityType) they will just get an instance of whatever class they're trying to read, since they have the same fields.
Clearly I need to add some other field which distinguishes these two objects, but how can I add that to the builders and constructors in a way that:
Doesn't require a ton of client logic,
doesn't needlessly complicate the exception objects, and
can be understood by Jackson?

To answer your main issue, You don't want to couple the clients and the server so tightly by having the clients use the same exact Exception classes used on the server, create a generic error bean and map exceptions to that bean then serialise/de-serialise it. You can do that in a transparent way using javax.ws.rs.ext.ExceptionMapper, this error bean can have canRetry or shouldRetry fields. An example implementation
public class RetryableServiceExceptionMapper implements ExceptionMapper<RetryableServiceException> {
#Context
Request request;
public Response toResponse(RetryableServiceException exception) {
ApiError error = ApiError.builder().canRetry(true).message(exception.getMessage()).build();
return Response.status(status).cacheControl(cacheControl).tag(eTag).entity(error).type(APPLICATION_XML);;
}
}

Related

Spring Boot: Custom exception or Validation for file uploading?

For my file (csv) upload endpoint, I check the file type using a method in my CsvHelper class:
private static String[] TYPES = {"text/csv", "application/vnd.ms-excel"};
public static boolean hasCsvFormat(MultipartFile file) {
return Arrays.stream(TYPES).anyMatch(file.getContentType()::equals);
}
And call it from service as shown below:
public void create(MultipartFile file) throws Exception {
if (!CsvHelper.hasCsvFormat(file)) {
throw new NotValidFormatException(...);
}
// ...
}
I created a custom exception for this called NotValidFormatException using #ControllerAdvice, but I am not sure if it is the most proper way for this.
My questions:
1. Should I create custom exception or custom validator as mentioned on this? But as I have not a model field, I need to use #Valid on the request and not sure if I can use that approach to verify file type (by calling my hasCsvFormat() method.
2. I used this approach for creating custom exception handling. If I wanted to use that approach for this scenario, should I create a separate class (e.g. NotValidFormatException) like NoSuchElementFoundException on that example? Or should I include a new exception method to the GlobalExceptionHandler class as a common exception type?
Based on your requirement, option number 2 is more suitable from my perspective.
You can create separate class for NotValidFormatException , annotate with ResponseStatus of your need & extend it with RuntimeException.
Now, your GlobalExceptionHandler is general purpose handler which perform actions once specific exception is thrown.
So you have to create method which should annotate with #ExceptionHandler(NotValidFormatException.class) in GlobalExceptionHandler .
Benefits of having separate class for NotValidFormatException is you can customize error message, can perform more operation within methods of that class.
If your requirement is minimal(like logging & returning response etc.) , then I would suggest to have only single ExceptionHandler method in GlobalExceptionHandler which will handle most of exceptions i.e. logging & returning response.

Mass Assignment: Insecure Binder Configuration with Fortify Java 1.8 EJB

I am trying to solve some vulnerabilities issues, and I have one that I couldn't solve it, I tried to add #Valid annotation in sync method but same error, this is the description from fortify:
The framework binder used for binding the HTTP request parameters to
the model class has not been explicitly configured to allow, or
disallow certain attributes.
To ease development and increase productivity, most modern frameworks
allow an object to be automatically instantiated and populated with
the HTTP request parameters whose names match an attribute of the
class to be bound. Automatic instantiation and population of objects
speeds up development, but can lead to serious problems if implemented
without caution. Any attribute in the bound classes, or nested
classes, will be automatically bound to the HTTP request parameters.
Therefore, malicious users will be able to assign a value to any
attribute in bound or nested classes, even if they are not exposed to
the client through web forms or API contracts.
The error I am getting in this line:
public ResponseClass sync(#BeanParam MyClassRequest request) throws
Exception {
MyClassResource.java
#Api(tags = "Relay")
#Stateless
public class MyClassResource extends AbstractService<MyClassRequest, ResponseClass> {
#EJB
private MyClassService myClassService;
#POST
#Path("/api/v1/service")
#Produces({"application/json"})
#ApiOperation(value = "Processes Conn",
response = ResponseClass.class, responseContainer = "ResponseClass", hidden = true)
#Override
public ResponseClass sync(#BeanParam MyClassRequest request) throws Exception {
myClassService.processFeed(request);
return new RelayResponse(HttpStatuses.ACCEPTED.getStatus());
}
MyClassRequest.java
In this file I have tried #FormParam("ccc") but same
public class MyClassRequest extends RelayRequest {
public MyClassRequest() {
super.setMessageType("not required");
}
private String myData;
private String conneRid;
private String connectionCreatedDate;
If someone could give some hint that how I can solve it, I will really appreciate it.
Do you expect all fields to be present in request? You are using #Valid annotation but there are no validation annotations in MyClassRequest model. Try to add some validation annotations like #JsonIgnore for non mandatory fields. Or #JsonInclude on class. If this does not help, may be also try explicitly adding #JsonProperty on each field.

Multiple Java Objects to single endpoint

Instead of building a case statement for my Spring Boot Rest Controller, I want to have Spring use the correct endpoint. I am not even sure this is possible but I am hoping the universe could save me.
#PostMapping("/endpoint")
public String one(Greeting greet) {
return "Greeting Posted";
}
#PostMapping("/endpoint")
public String two(Address addr) {
return "Address Posted";
}
Current Error
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'RController' method
public java.lang.String com.example.controller.RController.two(com.example.model.Address)
to {[/endpoint],methods=[POST]}: There is already 'RController' bean method
public java.lang.String com.example.controller.RController.one(com.example.model.Greeting) mapped.
This is not possible. It's ambiguous.
As a good practice, if 2 resources will handle the data differently, you must create a different endpoint for each one.
Or possible workaround for you, it's create an ViewModel object and handle it in just one method.
public class GreetingAddressVM {
private Address address;
private Greeting greeting;
}
I'd prefer creating different mapping for each action.

Specify Bounded Type Parameter containing an annotation during compile time?

Can I check if a class contains a specific annotation during compile time if using generics?
I'm creating a wrapper class that will be the response to various HTTP calls.
What I want is for this wrapper class to allow the user to pass in an object of any type that is annotated with my own annotation.
For example, my wrapper class can be as follows:
public class HTTPResponse<T> {
private HttpStatus status;
private int statusCode;
private T data;
public HTTPResponse(HttpStatus status, T data) {
this.status = status;
this.statusCode = status.value();
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
//other getters/setters
}
When using this class, I can create a new HTTPResponse object and declare the type of the 'data' field:
HTTPResponse<SomePOJO> response = new HTTPResponse<>(HttpStatus.OK, new SomePOJO());
The idea is that anybody trying to use this class can pass in their own custom POJO during construction. However, I would like to be able to check that the object being passed in during construction is annotated with a custom interface created by me.
For example, my SomePOJO class needs to look like below:
#MyCustomInterface
public class SomePOJO() {
//code stuff here
}
Is there a way to check that the SomePOJO class is annotated with #MyCustomInterface during compile time?
I know that I can mark my annotation so that it will be available at runtime and then perform the check in the constructor of HTTPResponse class. But I would really like for the user of my class to know as soon as possible (during compile time) that they haven't yet marked their POJO with the correct interface and thus needs to do that before creating an HTTPResponse object.
Is an annotation even the right way to go in this case? Is it 100% impossible to check for class annotation during compile time somehow and I should force users to implement my custom interface then specify
HTTPResponse with public class HttpResponse<T implements MyCustomInterface> {
//fields, getters, setters
}
Thanks for reading if you managed to make it this far down!
The purpose of my custom annotation above was to just mark a class as an item that can be used in the HTTPResponse class. The difficult part was validating this during compile time.
The simple answer to this issue is to use Marker Interfaces!
In Joshua Bloch's Effective Java, he writes:
Marker interfaces have two advantages over marker annotations. First
and foremost, marker interfaces define a type that is implemented by
instances of the marked class; marker annotations do not. The
existence of this type allows you to catch errors at compile time that
you couldn't catch until runtime if you used a marker annotation.
Thus, the question I was asking above is invalid. You shouldn't be using marker annotations if you did want to catch errors at compile time. After changing my custom annotation to an interface, I specified my bounded generics class as follows:
HTTPResponse with public class HttpResponse<T implements MyCustomInterface> {
//fields, getters, setters
}

JAX-RS : Suppress Error Message

I have a class which takes enum values like Male,Female #POST . when I sent a wrong value like 'male' instead of 'Male' it shows me 400 Bad Request with this message in rest client : Can not construct instance of constants.Constants$GenderEnum from String value 'male': value not one of declared Enum instance names
at [Source: org.apache.catalina.connector.CoyoteInputStream#718a453d; line: 7, column: 23] (through reference chain: valueobjects.ConsumerValueObject["gender"])
My Rest End Point Looks like below :
#Consumes("application/json")
#Produces("application/json")
#POST
public Response addConsumer(ConsumerValueObject consumerVO)
Here ConsumerValueObject holds the enum.
How to suppress that error message in Rest client? I tried with ExceptionMapper but it did not help!I need to suppress the message due to security issues!
This is the Jackson response from either JsonParseExceptionMapper or JsonMappingExceptionMapper. These classes come with the dependency
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${2.x.version}</version>
</dependency>
Whether you have this explicit dependency or you have the resteasy-jackson2-provider (which uses the above under the hood), most likely the mappers are registered implicitly through classpath scanning. For instance you have an empty Application class.
#ApplicationPath("/")
public class ResteasyApplication extends Application {}
This will cause disovery/registration through classpath scanning. If you don't have either of those dependencies, and if you are in Wildfly, I am not exactly sure how they are registered, but that is what's happening.
You could write/register your own ExceptionMappers for the JsonParseException and JsonMappingException
#Provider
public class JsonMappingExceptionMapper
implements ExceptionMapper<JsonMappingException> {
#Override
public Response toResponse(JsonMappingException e) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
}
but from what I have tested, it's a tossup as to which one will be registered, yours or Jackson's. The mappers are put into a Set (so unordered), then pushed into a Map, so only one get's pushed in. The order in which they are pushed in like I said is a tossup.
I guess this is really only a partial answer, as I have not been able to find a solution that is guaranteed to use your mapper, aside from registering all your classes explicitly (ultimately disabling the classpath scanning), but that is a hassle.
But now the fight has been narrowed down. I will try again some more if I get a chance later
UPDATE
So this is not a solution, just a semi-proof-of-concept to show how we can get it to use our ExceptionMapper.
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.my.pkg.JsonMappingExceptionMapper;
#Path("/init")
public class InitResource {
#GET
public Response init() {
ResteasyProviderFactory factory = ResteasyProviderFactory.getInstance();
factory.getExceptionMappers().put(JsonMappingException.class,
new JsonMappingExceptionMapper());
return Response.ok("Done!").build();
}
}
Once we hit the init endpoint for first time, our JsonMappingExcpetionMapper will register, and override the existing one, whether it is Jackson's or ours.
Of course we would not want to do this for real, it's just showing how to override the mapper. The thing I can't figure out is where to put this code. I've tried a ServletContextListener, in the Application constructor, in a Feature with a low priority. I can't figure it out. None of the above occur before RESTeasy does its final registration.
Do you really want to supress the error message or do you want to fix the actual probelm?
You can actually catch all thrown exception with a custom exception mapper like
#Provider
public class CustomExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable t) {
return Response.ok().build();
}
}
though, this will handle all caught exceptions and return a 200 OK which tricks clients to think that the request actually succeeded - which was not the case! Instead of Throwable you should be able to catch the concrete exception (even if it is a RuntimeException) as well - maybe you have not declared it as provider or did not specify the correct exception class?
Though, as already mentioned returning a different status code for an exception is generally bad practice and should be avoided. Fixing the actual problem is probably more suitable in that case.
JAX-RS provides MessageBodyReader and MessageBodyWriter interfaces which you can declare to un/marshall an inputstream to an object or an object to return to an output-stream. The official documentation on MessageBodyReader has more detailed information on that regard.
One implementation therefore could be the following steps:
Read the input-stream to f.e. string
Replace all "male" or "female" tokens with their upper-case version
Parse the string to a json-representation (using org.json.JSONObject f.e)
Use ObjectMapper to convert the JSON representation to a Java object
return the mapped object
This works if the input failure is just a simple upper/lower case issue. If there are typos or semantically alternative available, which are not yet in your enum, you need to put in a bit more effort.
If you, however, fail to create a proper object representation, you should return a user-failure (something in the 400 range) to the client to inform the client that something went wrong.

Categories