8 Replies Latest reply on Sep 6, 2016 10:08 AM by andriyv

    Exception propagation from RESTEasy reference binding

    ozkin

      I am trying to find ways how to get an exception propagated from RESTEasy reference binding to the Bean component invoking that REST service.

      So, I have a Bean. It invokes a remote REST service via RESTEasy composite reference. Java interface defines a method with an exception, say void foo() throws MyException. The idea is to transform HTTP status code (say 404) into MyException AND let it propagate to my Bean code. I am using MessageComposer and ExceptionMapper. Still cannot make it. I can easily translate HTTP status to the exception (in Composer) but MyException does not get propagated to the bean which invokes the remote service.

       

      I can explain this problem by using rest-binding quickstart. OrderServiceImpl.addItems() method invokes _warehouse.getItem(), which may throw ItemNotFoundException in some cases. Imagine, you would put try/catch block into this code, so that you can catch this exception and do some other logic. I tried. I was not able to get ItemNotFoundException eventhough it was generated by CustomComposer. Instead, I am getting following error:

       

      01:33:30,821 ERROR [org.switchyard.component.resteasy.OutboundHandler] (http-/127.0.0.1:8080-2) SWITCHYARD038019: Unexpected exception composing inbound REST response: org.switchyard.quickstarts.rest.binding.ItemNotFoundException: Item not found

          at org.switchyard.quickstarts.rest.binding.CustomComposer.compose(CustomComposer.java:49) [switchyard-rest-binding.jar:]

          at org.switchyard.quickstarts.rest.binding.CustomComposer.compose(CustomComposer.java:35) [switchyard-rest-binding.jar:]

          at org.switchyard.component.resteasy.OutboundHandler.handleMessage(OutboundHandler.java:146) [switchyard-component-resteasy-2.0.0.Final.jar:2.0.0.Final]

          at org.switchyard.handlers.ProviderHandler.handleMessage(ProviderHandler.java:77) [switchyard-runtime-2.0.0.Final.jar:2.0.0.Final]

       

      Any idea how to get an exception propagated in this scenario? Is it possible at all? Which other SwitchYard concept should be used to make this flow working?

        • 1. Re: Exception propagation from RESTEasy reference binding
          ozkin

          Here are my additional observations. One could throw application level exception from the custom composer (of a RESTEasy reference binding), like it is done in rest-binding quickstarts:

           

          if (clientResponse.getResponseStatus() == Response.Status.NOT_FOUND) {

                          throw new ItemNotFoundException("Item not found");

          }

           

          But ItemNotFoundException will not propagate to the bean (invoking this reference) as such (and that's what I want).

          RESTEasy OutboundHandler will not let any exception from your composer go further because of the following logic in handleMessage() method:

           

          try {

                      RESTEasyBindingData restResponse = methodInvoker.invoke(restRequest.getParameters(), restRequest.getHeaders());

                      restResponse.setOperationName(opName);

                      Message out = _messageComposer.compose(restResponse, exchange);

                      // Our transformer magic transforms the entity appropriately here

                      exchange.send(out);

          } catch (Exception e) {

                      final String m = RestEasyMessages.MESSAGES.unexpectedExceptionComposingRESTResponse();

                      LOGGER.error(m, e);

                      throw new HandlerException(m, e);

          }

           

          If your method's definition in Java interface of the composite reference contains exception declaration then your method will get HandlerException, otherwise you get java.lang.reflect.UndeclaredThrowableException.

           

          So, in my Bean I could put try/catch and then handle HandlerException, where its cause will contain the exception thrown from the Composer, but that is ugly, imho.

          There are ways to normalize native protocol messages to the SY message (which works for parameters) but why the same can't work for exceptions?

          How can one build a business logic code (my Bean in this case) fully transparent from a communication protocol / binding (used either by composite service or composite reference) be it REST, plain HTTP, SOAP, sockets, whatever?

          The only way I can think of right now is to develop an additional component between my bean and the composite reference, which will act as kind of a proxy handling HandlerException and translating it to the application exception. Though I don't like this solution....

          • 2. Re: Exception propagation from RESTEasy reference binding
            igarashitm

            It looks like a bug in the rest OutboundHandler, it should invoke Exchange.sendFault() if the checked exception (i.e. declared on interface) is thrown. Can you file a JIRA against it?

            • 3. Re: Exception propagation from RESTEasy reference binding
              ozkin

              Created JIRA: https://issues.jboss.org/browse/SWITCHYARD-2810

              Please, check if all is right in there..

               

              If it is a bug then I guess it is quite simple one.. do you have already an idea how it should be fixed (where sendFault() should be placed)? I am eager to patch my SY and try it again...

              • 4. Re: Exception propagation from RESTEasy reference binding
                ozkin

                Should the decision (to send() or sendFault()) be based on the response status code? something like this? (stolen from HTTP component):

                 

                try {

                            RESTEasyBindingData restResponse = methodInvoker.invoke(restRequest.getParameters(), restRequest.getHeaders());

                            restResponse.setOperationName(opName);

                            Message out = _messageComposer.compose(restResponse, exchange);

                            // Our transformer magic transforms the entity appropriately here

                            if (restResponse.getStatusCode() < 400) {

                                exchange.send(out);

                            } else {

                                exchange.sendFault(out);

                            }

                } catch (Exception e) {

                            final String m = RestEasyMessages.MESSAGES.unexpectedExceptionComposingRESTResponse();

                            LOGGER.error(m, e);

                            throw new HandlerException(m, e);

                }

                 

                Hmm... but then it has nothing to do with the checked exceptions... I guess we need somehow to check the exception thrown from the composer..... (?)

                • 5. Re: Exception propagation from RESTEasy reference binding
                  igarashitm

                  I don't have an answer right now, would need to look into RESTEasy invoker. but you can check what is the declared fault on that reference. Here is an example in camel OutboundHandler

                  switchyard/OutboundHandler.java at master · jboss-switchyard/switchyard · GitHub

                  switchyard/OutboundHandler.java at master · jboss-switchyard/switchyard · GitHub

                  • 6. Re: Exception propagation from RESTEasy reference binding
                    igarashitm

                    Thanks for the report - contributions are always very welcome!

                    • 7. Re: Exception propagation from RESTEasy reference binding
                      ozkin

                      I checked all available OutboundHandlers and one thing I can conclude is that any exception thrown from your Composer will result in HandlerException (and not in sendFault()). And thus it seems wrong to throw application exception from the composer because it won't be propagated as such. This is why I still question the use of ItemNotFoundException in rest-binding quickstarts.

                      If this thinking is right then in order to do sendFault() in the Handler one needs to play either with msg (as a result from the composer) or with the original response itself.. if so then it would mean that in the composer instead of throwing application exception you need to change/deal with the message (for example, when response status code is 404 and you want to indicate it as a fault to the business logic)..

                      Where is the trick? Anyone to clarify this issue?

                       

                      Another general question: does sendFault() result in a thrown exception (declared on interface)?

                      Update: good reading on the internals regarding my last question: Re: Transaction behavior with faults/errors

                      • 8. Re: Exception propagation from RESTEasy reference binding
                        andriyv

                        since REST has HTTP semantic we should have same handling of errors in http/OutboundHandler and rest/OutboundHandler.

                         

                        In http/OutboundHandler, if status code < 400 then exchange.send(message) otherwise exchange.sendFault(message), all other exceptions (IOExceptions etc.) will be considered as HandlerException

                         

                        In rest/OutboundHandler handling of status code delegated to org.switchyard.component.resteasy.util.ClientInvoker or rather to resteasy layer itself. Which throws exception once status code of response is in range [400;600)

                         

                        One possible solution here will be following:

                         

                        - adopt method invoke of ClientInvoker and catch exception from resteasy layer. If status code is in range [400;600) then exception should be set as parameter in RESTEasyBindingData. otherwise rethrow it back to OutboundHandler.

                        - adopt method handleMessage of rest/OutboundHandler and check RESTEasyBindingData.getStatus() < 400 to send(message) or sendFault(message)