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.
Related
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);;
}
}
In a flow I've designed, I have a Validation component with a custom validator that references a class, DataValidator, implementing the mule Validator interface. In the DataValidator, I validate several properties and would like to throw different kinds of exceptions (possibly custom exceptions, created by me) for each one. Is this possible?
My understanding is that, by specifying the exceptionClass, the Validation component will only throw exceptions of that class.
There's the option to use an ExceptionFactory instead of an exceptionClass.
Does using it allows throwing several types of exception? If so, how can I use it? I checked this blog
post, but didn't understand it well enough.
In the case that none of this is possible, is there any way I can get the ValidationResult message in the custom validator component, so that I can use it in the message?
Yes you can throw several types of exceptions. As you mentioned, you'll have to implement the DataValidator and ExceptionFactory interface and configure your component to use them.
With Studio, choose "Use Exception Factory Config" and specify the full class name you want to use. With XML, specify the exception-factory you implemented in your validation-config. (You can also configure a Spring Bean and reference it):
<validation:config name="Validation_Configuration" doc:name="Validation Configuration">
<validation:exception-factory class="com.mycomp.validation.MyExceptionFactory"/>
</validation:config>
In your flow configure acustom-validator and reference your configuration with exception factory, your validator implementation and the type of exception you want to throw with exceptionClass. To be able to throw any kind of Exception, specify java.lang.Exception or a class from which the custom exceptions you'll want to use can inherit:
<flow name="validation-exceptionFlow">
...
<validation:custom-validator
config-ref="Validation_Configuration"
class="com.mycomp.validation.MyValidator"
exceptionClass="java.lang.Exception"
doc:name="Validation" />
...
</flow>
Depending on your need you may want to specify exceptionClass differently, the idea being that your actual exceptions should extend it.
Yout ExceptionFactory implementation is then up to you. You can return whatever exception you want... For example:
public class MyExceptionFactory implements ExceptionFactory{
#Override
public <T extends Exception> T createException(ValidationResult result, Class<T> exceptionClass, MuleEvent event) {
return (T) createException(result, exceptionClass.getCanonicalName(), event);
}
#Override
public Exception createException(ValidationResult result, String exceptionClassName, MuleEvent event) {
//...
//some logic to identify which kind of exception you want to throw
//from result and event
//...
if(something) {
return new SomeException("Something happened");
} else if (somethingElse) {
return new AnotherException("I am en error...");
} else {
return new BananaException("Ook");
}
}
}
It appears the interface has two methods, one returning a generic and the other a plain Exception. Not knowing the specific usage of your ExceptionFactory I won't be able to provide much guidance, but be aware that Mule may call any of these methods, and the doc provides some requirements:
The above interface receives the Event that was rejected by the
validation and the validator that raised the error. This method is
intended to return the exception to be thrown but not to throw it.
Implementations of this interface should never throw exceptions. They
should also be thread-safe and have a public default constructor. See
Also
I have a JAX-RS project that uses Jackson to handle JSON conversions.
When Jackson throws an exception, it automatically returns a string with the error description.
Since I want to return a custom JSON object, I created an ExceptionMapper.
The problem is, it only catches the exception when I specify exactly the type of the Exception being thrown.
For example, when the JSON sent to a method contains an unknown property, this works:
public class MyExceptionMapper implements ExceptionMapper<UnrecognizedPropertyException>
But if I change UnrecognizedPropertyException to PropertyBindingException (which the first extends), it won't work.
In short:
How can I create a generic exception mapper that catches any exception thrown by Jackson (or any other component of my app, for that matter)?
try with
public class MyExceptionMapper implements ExceptionMapper<Exception>
This should be the fallback for all Exceptions.
Jackson is looking for the the hierarchie from the exception up if it finds a suitable ExceptionMapper.
It looks as long as there is something in the type hierarchy.
So UnrecognizedPropertyException will be handled by PropertyBinding-Exception mapper but not the other way round because UnrecognizedPropertyException Mapper is more specific there could be many subclasses and then there cannot be determined which Mapper to take. So it only works upwards.
An because Exception is the base Exception everything ends up there.
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.
I am centralizing the exception handling in my app. But I want the messages to be internationalized and should be kept in a properties file. For this purpose I am planning to keep the keys with fully qualified name of the controller appended with .message. So my #ControllerAdvice marked class will handle the exception and fetch the message from the properties file based on the "fully qualified name of exception.ControllerName" and the problem is I am not getting the controller name and the method signatures from where the exception occurred. Is there a way to do so??
AFAIK there is no direct way to achieve this.
So you need to have an Around advice for your #ExceptionHandler annotated methods such as below
#Aspect
public class ExceptionLogging {
#Around(execution("#annotation(ExceptionHandler)"))
public Object logException(ProceddingJoinPoint pjp){
String name = pjp.getSignature().getName(); // tweak this as per your requirement
logger.info("Controller where Exception was raised - " + name);
Object obj = pjp.proceed();
return obj;
}
}