Cleaning the message of a custom exception - exception

In my grails app I have a custom InvalidTokenException as follows:
class InvalidTokenException extends Exception{
public InvalidTokenException() {}
public InvalidTokenException(String message)
{
super(message);
}
}
which I am throwing in my service as follows:
throw new InvalidTokenException("Invalid token : '${word}'")
which I am catching in my controller and rendering to the client as follows:
catch(e)
{
//send the exception to the client for rendering an error message.
render(status: 400, text: e)
return false //stops further execution
}
Though I want to be strip back the message that it only contains the text "Invalid token : word" rather than "Exception uk.co.litecollab.exceptions.InvalidTokenException Invalid token : word"
Any ideas how to do this?

Change it to render(status: 400, text: e.message) since you're currently taking advantage of the auto-conversion of e to a String, which calls the toString() method. Calling getMessage() directly makes more sense in general and does what you need here.

Related

Spring Boot Global Exception Handler does not Capture HttpMessageNotReadableException

I have a global exception handler, which works fine to capture exceptions thrown from my controller, service layer, or repository layer. However, it fails to capture exceptions that occur before entering my controller. Specifically, I have a POST controller that expects a valid json body, if the actual json body is malformed, an HttpMessageNotReadableException is thrown, and I have no idea where this exception got handled. The response code is indeed 400. So my question is, how to use my own logic to capture and handle message deserialization exception that happens before entering my controller.
My global exception handler (it works fine for exceptions thrown from my service layer)
#ControllerAdvice(basePackageClasses = TopologyApiController.class)
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final String UNKNOWN_SERVER_ERROR_MSG = "Unknown server error";
#ExceptionHandler(value = {ServiceException.class})
public ResponseEntity<ErrorResponse> handleServiceException(Exception ex, WebRequest request) {
// some handling
return generateExceptionResponseEntity(errorMessage, status);
}
#ExceptionHandler(value = {Exception.class})
public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex, WebRequest request) {
return generateExceptionResponseEntity(UNKNOWN_SERVER_ERROR_MSG, HttpStatus.INTERNAL_SERVER_ERROR);
}
private ResponseEntity<ErrorResponse> generateExceptionResponseEntity(String message, HttpStatus status) {
ErrorResponse response = new ErrorResponse();
response.setMessage(message);
return ResponseEntity.status(status).body(response);
}
}
My POST controller (expects a json body to deserialize into a CityInfo object)
#RequestMapping(value = API_BASE + "/topology/cities", method = RequestMethod.POST)
ResponseEntity<CityInfo> topologyCitiesPost(#Valid #RequestBody CityInfo body) {
CityInfo cityInfo = topologyService.addCity(body);
return ResponseEntity.status(HttpStatus.CREATED).body(cityInfo);
}
The controller expects a json body in the form of below, and the entire code works fine if the json is valid.
{
"description": "string",
"name": "string",
"tag": "string"
}
but if the actual content is something like below (e.g., with several commas at the end), an HttpMessageNotReadableException will be thrown and is not captured by my handler.
{
"description": "this is description",
"name": "city name",
"tag": "city tag",,,,
}
This: So my question is, how to use my own logic to capture and handle message deserialization exception that happens before entering my controller.
Annotate and write an exceptionHandler for that specific exception. Add this to your GlobalExceptionHandler class:
#ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleMessageNotReadableException(Exception ex, WebRequest request) {
// some handling
return generateExceptionResponseEntity(errorMessage, status);
}

Spring AMQP RPC consumer and throw exception

I have a consumer (RabbitListner) in RPC mode and I would like to know if it is possible to throw exception that can be treated by the publisher.
To make more clear my explication the case is as follow :
The publisher send a message in RPC mode
The consumer receive the message, check the validity of the message and if the message can not be take in count, because of missing parameters, then I would like to throw Exception. The exception can be a specific business exception or a particular AmqpException but I want that the publisher can handle this exception if it is not go in timeout.
I try with the AmqpRejectAndDontRequeueException, but my publisher do not receive the exception, but just a response which is empty.
Is it possible to be done or may be it is not a good practice to implement like that ?
EDIT 1 :
After the #GaryRussel response here is the resolution of my question:
For the RabbitListner I create an error handler :
#Configuration
public class RabbitErrorHandler implements RabbitListenerErrorHandler {
#Override public Object handleError(Message message, org.springframework.messaging.Message<?> message1, ListenerExecutionFailedException e) {
throw e;
}
}
Define the bean into a configuration file :
#Configuration
public class RabbitConfig extends RabbitConfiguration {
#Bean
public RabbitTemplate getRabbitTemplate() {
Message.addWhiteListPatterns(RabbitConstants.CLASSES_TO_SEND_OVER_RABBITMQ);
return new RabbitTemplate(this.connectionFactory());
}
/**
* Define the RabbitErrorHandle
* #return Initialize RabbitErrorHandle bean
*/
#Bean
public RabbitErrorHandler rabbitErrorHandler() {
return new RabbitErrorHandler();
}
}
Create the #RabbitListner with parameters where rabbitErrorHandler is the bean that I defined previously :
#Override
#RabbitListener(queues = "${rabbit.queue}"
, errorHandler = "rabbitErrorHandler"
, returnExceptions = "true")
public ReturnObject receiveMessage(Message message) {
For the RabbitTemplate I set this attribute :
rabbitTemplate.setMessageConverter(new RemoteInvocationAwareMessageConverterAdapter());
When the messsage threated by the consumer, but it sent an error, I obtain a RemoteInvocationResult which contains the original exception into e.getCause().getCause().
See the returnExceptions property on #RabbitListener (since 2.0). Docs here.
The returnExceptions attribute, when true will cause exceptions to be returned to the sender. The exception is wrapped in a RemoteInvocationResult object.
On the sender side, there is an available RemoteInvocationAwareMessageConverterAdapter which, if configured into the RabbitTemplate, will re-throw the server-side exception, wrapped in an AmqpRemoteException. The stack trace of the server exception will be synthesized by merging the server and client stack traces.
Important
This mechanism will generally only work with the default SimpleMessageConverter, which uses Java serialization; exceptions are generally not "Jackson-friendly" so can’t be serialized to JSON. If you are using JSON, consider using an errorHandler to return some other Jackson-friendly Error object when an exception is thrown.
What worked for me was :
On "serving" side :
Service
#RabbitListener(id = "test1", containerFactory ="BEAN CONTAINER FACTORY",
queues = "TEST QUEUE", returnExceptions = "true")
DataList getData() {
// this exception will be transformed by rabbit error handler to a RemoteInvocationResult
throw new IllegalStateException("mon expecion");
//return dataHelper.loadAllData();
}
On "requesting" side :
Service
public void fetchData() throws AmqpRemoteException {
var response = (DataList) amqpTemplate.convertSendAndReceive("TEST EXCHANGE", "ROUTING NAME", new Object());
Optional.ofNullable(response)
.ifPresentOrElse(this::setDataContent, this::handleNoData);
}
Config
#Bean
AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
var rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
#Bean
MessageConverter jsonMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.registerModule(new JavaTimeModule());
var jsonConverter = new Jackson2JsonMessageConverter(objectMapper);
DefaultClassMapper classMapper = new DefaultClassMapper();
Map<String, Class<?>> idClassMapping = Map.of(
DataList.class.getName(), DataList.class,
RemoteInvocationResult.class.getName(), RemoteInvocationResult.class
);
classMapper.setIdClassMapping(idClassMapping);
jsonConverter.setClassMapper(classMapper);
// json converter with returned exception awareness
// this will transform RemoteInvocationResult into a AmqpRemoteException
return new RemoteInvocationAwareMessageConverterAdapter(jsonConverter);
}
You have to return a message as an error, which the consuming application can choose to treat as an exception. However, I don't think normal exception handling flows apply with messaging. Your publishing application (the consumer of the RPC service) needs to know what can go wrong and be programmed to deal with those possibilities.

How to handle JSON Parse Error in Spring Rest Web Service

I have a rest web service developed with Spring Boot.I am able to handle all the exceptions that occur due to my code, but suppose the json object that the client posts is not compatible with the object that i want to desrialize it with, I get
"timestamp": 1498834369591,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "JSON parse error: Can not deserialize value
I wanted to know is there a way that for this exception, I can provide the client a custom exception message. I am not sure how to handle this error.
To customize this message per Controller, use a combination of #ExceptionHandler and #ResponseStatus within your Controllers:
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "CUSTOM MESSAGE HERE")
#ExceptionHandler(HttpMessageNotReadableException.class)
public void handleException(HttpMessageNotReadableException ex) {
//Handle Exception Here...
}
If you'd rather define this once and handle these Exceptions globally, then use a #ControllerAdvice class:
#ControllerAdvice
public class CustomControllerAdvice {
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "CUSTOM MESSAGE HERE")
#ExceptionHandler(HttpMessageNotReadableException.class)
public void handleException(HttpMessageNotReadableException ex) {
//Handle Exception Here...
}
}
You also can extend ResponseEntityExceptionHandler and override the method handleHttpMessageNotReadable (example in Kotlin, but very similar in Java):
override fun handleHttpMessageNotReadable(ex: HttpMessageNotReadableException, headers: HttpHeaders, status: HttpStatus, request: WebRequest): ResponseEntity<Any> {
val entity = ErrorResponse(status, ex.message ?: ex.localizedMessage, request)
return this.handleExceptionInternal(ex, entity as Any?, headers, status, request)
}

How to catch the play.api.libs.openid.Errors$AUTH_CANCEL$ exception?

Using Play Framework 2.1 with OpenID, if I cancel my authentication from the OpenID Provider, I get this exception :
[RuntimeException: play.api.libs.openid.Errors$AUTH_CANCEL$]
Here's my code :
Promise<UserInfo> userInfoPromise = OpenID.verifiedId();
UserInfo userInfo = userInfoPromise.get(); // Exception thrown here
But since it's a Runtime exception, I can't catch it with a try/catch so I'm stuck on how to avoid exception and returns something nicer than a server error to the client.
How can I do that?
A Promise is success biased, for all its operations, it assumes it actually contains a value and not an error.
You get the exception because you try to call get on a promise which contains an untransformed error.
What you want is to determine if the Promise is a success or an error, you can do that with pattern matching for instance.
try this code:
AsyncResult(
OpenID.verifiedId.extend1( _ match {
case Redeemed(info) => Ok(info.attributes.get("email").getOrElse("no email in valid response"))
case Thrown(throwable) => {
Logger.error("openid callback error",throwable)
Unauthorized
}
}
)
)
You may want to read more on future and promises, I recommend this excellent article :
http://danielwestheide.com/blog/2013/01/09/the-neophytes-guide-to-scala-part-8-welcome-to-the-future.html
edit :
checking the documentation (http://www.playframework.com/documentation/2.1.0/JavaOpenID) in java it seems you are supposed to catch and handle exceptions yourself.
In any case, you should catch exceptions and if one is thrown redirect
back the user to the login page with relevant information.
something like this should work :
public class Application extends Controller {
public static Result index() {
return ok("welcome");
}
public static Result auth() {
Map<String, String> attributes = new HashMap<String, String>();
attributes.put("email", "http://schema.openid.net/contact/email");
final Promise<String> stringPromise = OpenID.redirectURL("https://www.google.com/accounts/o8/id", "http://localhost:9000/auth/callback",attributes);
return redirect(stringPromise.get());
}
public static Result callback() {
try{
Promise<UserInfo> userInfoPromise = OpenID.verifiedId();
final UserInfo userInfo = userInfoPromise.get();
System.out.println("id:"+userInfo.id);
System.out.println("email:"+userInfo.attributes.get("email"));
return ok(userInfo.attributes.toString());
} catch (Throwable e) {
System.out.println(e.getMessage());
e.printStackTrace();
return unauthorized();
}
}
}

GWT RPC Call onFailure

I created a Remote Procedure Call. The Server-Side connects to Webservices, to get Information, which it hands over to the Client-Side. This is the Client-Side Code.
public void statusFor(GwtLaneServiceAsync laneProxy){
AsyncCallback<LaneInformation> callback = new AsyncCallback<LaneInformation>()
{
#Override
public void onFailure(Throwable caught)
{
}
#Override
public void onSuccess(LaneInformation information)
{
doStatusForSuccess(information);
}
};
for (Lane lane : this.mainmenu.getSlidePanel().getLaneMenu().getProperLanes().values())
{
if (lane.isChecked().booleanValue())
laneProxy.statusFor("admin", "password", true, lane.getId(), callback);
else
laneProxy.statusFor("admin", "password", false, lane.getId(), callback);
this.laneIndex++;
}
}
Now i wanna do the following...
When the Server can't reach the Webservice, a WebServiceException is thrown. If that happens, I wanna type "Offline" on one of my Buttons of the GUI. BUT I need to tell on which button. It can't be hard coded, cause it depends on which "lane" the Webservice failed.
I need to catch the Exceptions
I need to tell the "onFailure"-Part, on which lane, the Service failed.
Can I somehow deliver the statusFor()-Parameters to that part?
There is no of ways to handle such case. you can throw any custom exception from server side while server can't reach the webservice. then it will come onFailure block. or you can return any message string in response variable. Here response variable you are using LaneInformation bean. so take new variable there like result, and set message as per your requirement.
OnFailure it comes only when any exception occurred or any wrong thing happens in RPC call.
Why not wrap your LaneInformation in a generic response object and add the exception/an error code to that response, to signal that something went wrong on the server side, eg.:
public class RemoteResult<T>
{
T payload;
String errorCode;
}
and
public abstract class AbstractAsyncCallBack<T> implements AsyncCallback<RemoteResult<T>>
{
public void onSuccess( RemoteResult<T> rr )
{
if ( rr.getErrrorCode() != null ) { failure( rr.getErrorCode() ); }
else { success( rr.getPayload() ); }
}
public abstract void success( T payload );
public void failure( String errorCode ) { /* Ignore by default */ }
}
To conclude, you shouldn't throw an exception on the server side when the server can't connect to some other service, you should communicate that nicely to the client, and that's not by (re)throwing the exception :-)
The onFailure() method is mostly for when things go wrong in the RPC communication proper.
Cheers,