Repository: incubator-juneau
Updated Branches:
  refs/heads/master ca31b2388 -> b1a30b033


Add support for exception passing in proxy interfaces.

Project: http://git-wip-us.apache.org/repos/asf/incubator-juneau/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-juneau/commit/b1a30b03
Tree: http://git-wip-us.apache.org/repos/asf/incubator-juneau/tree/b1a30b03
Diff: http://git-wip-us.apache.org/repos/asf/incubator-juneau/diff/b1a30b03

Branch: refs/heads/master
Commit: b1a30b03340213cbd50a58a7eb06a0d5a3be8663
Parents: ca31b23
Author: JamesBognar <[email protected]>
Authored: Sun Mar 26 18:19:20 2017 -0700
Committer: JamesBognar <[email protected]>
Committed: Sun Mar 26 18:19:20 2017 -0700

----------------------------------------------------------------------
 juneau-core/src/main/javadoc/overview.html      | 108 +++++++++++++------
 .../org/apache/juneau/rest/client/RestCall.java |   4 +-
 .../juneau/rest/client/RestCallException.java   |  59 ++++++++++
 .../apache/juneau/rest/client/RestClient.java   |   6 +-
 .../apache/juneau/rest/test/InterfaceProxy.java |  14 +++
 .../rest/test/InterfaceProxyResource.java       |  80 ++++++++++----
 .../juneau/rest/test/InterfaceProxyTest.java    |  27 ++++-
 .../org/apache/juneau/rest/RestContext.java     |  14 ++-
 8 files changed, 252 insertions(+), 60 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/b1a30b03/juneau-core/src/main/javadoc/overview.html
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/javadoc/overview.html 
b/juneau-core/src/main/javadoc/overview.html
index b9af064..5c80d9a 100644
--- a/juneau-core/src/main/javadoc/overview.html
+++ b/juneau-core/src/main/javadoc/overview.html
@@ -2250,56 +2250,95 @@
                        use and allowing much more flexibility.
        </p>
        <p>
-               Remoteable Services are implemented through a combination of 
the Server and Client libraries.
+               The remote proxy interface API allows you to invoke server-side 
POJO methods on the client side using REST as the communications protocol:
        </p>
-       <ul class='spaced-list'>
-               <li>Proxy interfaces are retrieved using the {@link 
org.apache.juneau.rest.client.RestClient#getRemoteableProxy(Class)} method.
-               <li>The {@link 
org.apache.juneau.rest.client.RestClientBuilder#remoteableServletUri(String)} 
method is used to specify the location
-                       of the remoteable services servlet running on the 
server.
-               <li>The {@link 
org.apache.juneau.rest.remoteable.RemoteableServlet} class is a specialized 
subclass of {@link org.apache.juneau.rest.RestServlet} that provides a 
full-blown
-                       REST interface for calling interfaces remotely. 
-       </ul>
-       <p>
-               In this example, you have the following interface defined that 
you want to call from the client side against
-                       a POJO on the server side (i.e. a Remoteable Service):
        <p class='bcode'>
-       <jk>public interface</jk> IAddressBook {
-               Person createPerson(CreatePerson cp) <jk>throws</jk> Exception;
-       }
-       </p>                    
+       <jc>// Get an interface proxy.</jc>
+       IAddressBook ab = 
restClient.getRemoteableProxy(IAddressBook.<jk>class</jk>);
+       
+       <jc>// Invoke a method on the server side and get the returned 
result.</jc>
+       Person p = ab.createPerson(
+               <jk>new</jk> Person(
+                       <js>"John Smith"</js>, 
+                       <js>"Aug 1, 1999"</js>,
+                       <jk>new</jk> Address(<js>"My street"</js>, <js>"My 
city"</js>, <js>"My state"</js>, 12345, <jk>true</jk>)
+               )
+       );
+       </p>
        <p>
-               The client side code for invoking this method is shown below:
+               There are two ways to expose remoteable proxies on the server 
side:
+       </p>
+       <ol>
+               <li>Extending from <code>RemoteableServlet</code>.
+               <li>Using a 
<code><ja>@RestMethod</ja>(name=<js>"PROXY"</js>)</code> annotation on a Java 
method.
+       </ol>
+       <p>
+               The <code>RemoteableServlet</code> class is a simple 
specialized servlet with an abstract <code>getServiceMap()</code>
+               method to define the server-side POJOs:
        </p>
        <p class='bcode'>
-       <jc>// Create a RestClient using JSON for serialization, and point to 
the server-side remoteable servlet.</jc>
-       RestClient client = <jk>new</jk> RestClientBuilder()
-               
.remoteableServletUri(<js>"https://localhost:9080/juneau/sample/remoteable";</js>)
-               .build();
+       <ja>@RestResource</ja>(
+               path=<js>"/remote"</js>
+       )
+       <jk>public class</jk> SampleRemoteableServlet <jk>extends</jk> 
RemoteableServlet {
        
-       <jc>// Create a proxy interface.</jc>
-       IAddressBook ab = 
client.getRemoteableProxy(IAddressBook.<jk>class</jk>);
+               <jc>// Our server-side POJO.</jc>
+               AddressBook <jf>addressBook</jf> = <jk>new</jk> AddressBook();
        
-       <jc>// Invoke a method on the server side and get the returned 
result.</jc>
-       Person p = ab.createPerson(
-               <jk>new</jk> CreatePerson(<js>"Test Person"</js>,
-                       AddressBook.<jsm>toCalendar</jsm>(<js>"Aug 1, 
1999"</js>),
-                       <jk>new</jk> CreateAddress(<js>"Test street"</js>, 
<js>"Test city"</js>, <js>"Test state"</js>, 12345, <jk>true</jk>))
-       );
+               <ja>@Override</ja> <jc>/* RemoteableServlet */</jc>
+               <jk>protected</jk> Map&lt;Class&lt;?&gt;,Object&gt; 
getServiceMap() <jk>throws</jk> Exception {
+                       Map&lt;Class&lt;?&gt;,Object&gt; m = <jk>new</jk> 
LinkedHashMap&lt;Class&lt;?&gt;,Object&gt;();
+       
+                       <jc>// In this simplified example, we expose the same 
POJO service under two different interfaces.
+                       // One is IAddressBook which only exposes methods 
defined on that interface, and
+                       // the other is AddressBook itself which exposes all 
methods defined on the class itself (dangerous!).</jc>
+                       m.put(IAddressBook.<jk>class</jk>, 
<jf>addressBook</jf>);
+                       m.put(AddressBook.<jk>class</jk>, <jf>addressBook</jf>);
+                       <jk>return</jk> m;
+               }
+       }
        </p>
        <p>
-               The requirements for a method to be callable through a 
remoteable service are:
+               The <code><ja>@RestMethod</ja>(name=<js>"PROXY"</js>)</code> 
approach is easier if you only have a single interface you want to expose.  
+               You simply define a Java method whose return type is an 
interface, and return the implementation of that interface:
+       </p>
+       <p class='bcode'>
+       <jc>// Our exposed proxy object.</jc>
+       <ja>@RestMethod</ja>(name=<js>"PROXY"</js>, 
path=<js>"/addressbookproxy/*"</js>)
+       <jk>public</jk> IAddressBook getProxy() {
+               <jk>return</jk> addressBook;
+       }
        </p>
-       <ul class='spaced-list'>
-               <li>The method must be public.
-               <li>The parameter and return types must be <a 
href='#Core.PojoCategories'>serializable and parsable</a>.
-       </ul>
        <p>
-               One significant feature is that the remoteable services servlet 
is a full-blown REST interface.  
+               In either case, the proxy communications layer is pure REST.   
+               Parameters passed in on the client side are serialized as an 
HTTP POST, parsed on the
+               server side, and then passed to the invocation method.  The 
returned POJO is then marshalled back as an HTTP response.
                Therefore, in cases where the interface classes are not 
available on the client side,
                        the same method calls can be made through pure REST 
calls.  
                This can also aid significantly in debugging, since calls to 
the remoteable service
                        can be made directly from a browser with no coding 
involved.
        </p>
+       <p>
+               The parameters and return types of the Java methods can be any 
of the supported serializable and parsable types in <a class='doclink' 
href='#Core.PojoCategories'>POJO Categories</a>.
+               This ends up being WAY more flexible than other proxy 
interfaces since Juneau can handle so may POJO types out-of-the-box.
+               Most of the time you don't even need to modify your existing 
Java implementation code.
+       </p>
+       <p>
+               The RemoteableServlet class itself shows how sophisticated REST 
interfaces can be built on the Juneau RestServlet
+               API using very little code.  The RemoteableServlet class itself 
consists of only 53 lines of code, yet is
+               a sophisticated discoverable and self-documenting REST 
interface.  And since the remote proxy API is built on top 
+               of REST, it can be debugged using just a browser.
+       </p>
+       <p>
+               The requirements for a method to be callable through a 
remoteable service are:
+       </p>
+       <ul class='spaced-list'>
+               <li>The method must be public.
+               <li>The parameter and return types must be <a 
href='#Core.PojoCategories'>serializable and parsable</a>.
+                       Parameterized types are supported.
+               <li>Methods can throw <code>Throwables</code> with public 
no-arg or single-arg-string constructors which will be automatically 
+                       recreated on the client side.
+       </ul>
        <h6 class='topic'>Additional Information</h6>
        <ul class='javahierarchy'>
                <li class='p'><a class='doclink' 
href='org/apache/juneau/server/remoteable/package-summary.html#TOC'>org.apache.juneau.rest.remoteable</a>
 - Juneau Remoteable API Javadocs.
@@ -5788,6 +5827,7 @@
        IAddressBook ab = 
client.getRemoteableProxy(IAddressBook.<jk>class</jk>, 
<js>"/addressBook/myproxy"</js>);
                        </p>
                                See {@link 
org.apache.juneau.rest.annotation.RestMethod#name()} for more information.
+                       <li>Updated doc: <a class='doclink' 
href='#Remoteable'>6 - Remoteable Services</a>
                        <li>{@link 
org.apache.juneau.rest.RestRequest#toString()} can be called at any time to 
view the headers and content of the request
                                without affecting functionality.  Very useful 
for debugging.
                </ul>

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/b1a30b03/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java 
b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
index 88e892d..ab9e010 100644
--- 
a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
+++ 
b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
@@ -953,7 +953,9 @@ public final class RestCall {
                        String method = request.getMethod();
                        sc = sl.getStatusCode(); // Read it again in case it 
was changed by one of the interceptors.
                        if (sc >= 400 && ! ignoreErrors)
-                               throw new RestCallException(sc, 
sl.getReasonPhrase(), method, request.getURI(), 
getResponseAsString()).setHttpResponse(response);
+                               throw new RestCallException(sc, 
sl.getReasonPhrase(), method, request.getURI(), getResponseAsString())
+                                       
.setServerException(response.getFirstHeader("Exception-Name"), 
response.getFirstHeader("Exception-Message"), 
response.getFirstHeader("Exception-Trace"))
+                                       .setHttpResponse(response);
                        if ((sc == 307 || sc == 302) && allowRedirectsOnPosts 
&& method.equalsIgnoreCase("POST")) {
                                if (redirectOnPostsTries-- < 1)
                                        throw new RestCallException(sc, 
"Maximum number of redirects occurred.  Location header: " + 
response.getFirstHeader("Location"), method, request.getURI(), 
getResponseAsString());

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/b1a30b03/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCallException.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCallException.java
 
b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCallException.java
index d986b46..d4579eb 100644
--- 
a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCallException.java
+++ 
b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCallException.java
@@ -15,6 +15,7 @@ package org.apache.juneau.rest.client;
 import static java.lang.String.*;
 
 import java.io.*;
+import java.lang.reflect.*;
 import java.net.*;
 import java.util.regex.*;
 
@@ -35,6 +36,9 @@ public final class RestCallException extends IOException {
        HttpResponseException e;
        private HttpResponse httpResponse;
 
+       @SuppressWarnings("unused")
+       private String serverExceptionName, serverExceptionMessage, 
serverExceptionTrace;
+
 
        /**
         * Constructor.
@@ -92,6 +96,61 @@ public final class RestCallException extends IOException {
        }
 
        /**
+        * Sets the server-side exception details.
+        *
+        * @param exceptionName The <code>Exception-Name:</code> header 
specifying the full name of the exception.
+        * @param exceptionMessage The <code>Exception-Message:</code> header 
specifying the message returned by {@link Throwable#getMessage()}.
+        * @param exceptionTrace The stack trace of the exception returned by 
{@link Throwable#printStackTrace()}.
+        * @return This object (for method chaining).
+        */
+       protected RestCallException setServerException(Header exceptionName, 
Header exceptionMessage, Header exceptionTrace) {
+               if (exceptionName != null)
+                       serverExceptionName = exceptionName.getValue();
+               if (exceptionMessage != null)
+                       serverExceptionMessage = exceptionMessage.getValue();
+               if (exceptionTrace != null)
+                       serverExceptionTrace = exceptionTrace.getValue();
+               return this;
+       }
+
+       /**
+        * Tries to reconstruct and re-throw the server-side exception.
+        * <p>
+        * The exception is based on the following HTTP response headers:
+        * <ul>
+        *      <li><code>Exception-Name:</code> - The full class name of the 
exception.
+        *      <li><code>Exception-Message:</code> - The message returned by 
{@link Throwable#getMessage()}.
+        *      <li><code>Exception-Trace:</code> - The stack trace of the 
exception returned by {@link Throwable#printStackTrace()}.
+        * </ul>
+        * <p>
+        * Does nothing if the server-side exception could not be reconstructed.
+        * <p>
+        * Currently only supports <code>Throwables</code> with either a public 
no-arg constructor
+        * or a public constructor that takes in a simple string message.
+        *
+        * @param cl The classloader to use to resolve the throwable class name.
+        * @throws Throwable If the throwable could be reconstructed.
+        */
+       protected void throwServerException(ClassLoader cl) throws Throwable {
+               if (serverExceptionName != null) {
+                       Throwable t = null;
+                       try {
+                               Class<?> exceptionClass = 
cl.loadClass(serverExceptionName);
+                               Constructor<?> c = 
ClassUtils.findPublicConstructor(exceptionClass, String.class);
+                               if (c != null)
+                                       t = 
(Throwable)c.newInstance(serverExceptionMessage);
+                               if (t == null) {
+                                       c = 
ClassUtils.findPublicConstructor(exceptionClass);
+                                       if (c != null)
+                                               t = (Throwable)c.newInstance();
+                               }
+                       } catch (Exception e2) { /* Ignore */ }
+                       if (t != null)
+                               throw t;
+               }
+       }
+
+       /**
         * Sets the HTTP response object that caused this exception.
         *
         * @param httpResponse The HTTP respose object.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/b1a30b03/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
 
b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index 37e8f33..d926c8e 100644
--- 
a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ 
b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -455,7 +455,7 @@ public class RestClient extends CoreObject {
                                        final String uri = 
toURI(proxyUrl).toString();
 
                                        @Override /* InvocationHandler */
-                                       public Object invoke(Object proxy, 
Method method, Object[] args) {
+                                       public Object invoke(Object proxy, 
Method method, Object[] args) throws Throwable {
 
                                                // Constructing this string 
each time can be time consuming, so cache it.
                                                String u = uriCache.get(method);
@@ -468,6 +468,10 @@ public class RestClient extends CoreObject {
 
                                                try {
                                                        return doPost(u, 
args).serializer(serializer).parser(parser).getResponse(method.getGenericReturnType());
+                                               } catch (RestCallException e) {
+                                                       // Try to throw 
original exception if possible.
+                                                       
e.throwServerException(interfaceClass.getClassLoader());
+                                                       throw new 
RuntimeException(e);
                                                } catch (Exception e) {
                                                        throw new 
RuntimeException(e);
                                                }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/b1a30b03/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java
 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java
index 762886e..67e2f5f 100644
--- 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java
+++ 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java
@@ -38,6 +38,9 @@ public interface InterfaceProxy {
        Map<String,List<Bean>> returnBeanListMap();
        Map<Integer,List<Bean>> returnBeanListMapIntegerKeys();
 
+       void throwException1() throws InterfaceProxyException1;
+       void throwException2() throws InterfaceProxyException2;
+
        void setNothing();
        void setInt(int x);
        void setInteger(Integer x);
@@ -67,4 +70,15 @@ public interface InterfaceProxy {
                        return this;
                }
        }
+       
+       @SuppressWarnings("serial")
+       public static class InterfaceProxyException1 extends Throwable {
+               public InterfaceProxyException1(String msg) {
+                       super(msg);
+               }
+       }
+
+       @SuppressWarnings("serial")
+       public static class InterfaceProxyException2 extends Throwable {        
        
+       }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/b1a30b03/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java
 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java
index 57fa638..8e226a0 100644
--- 
a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java
+++ 
b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java
@@ -37,42 +37,84 @@ public class InterfaceProxyResource extends 
RestServletJenaDefault {
        public InterfaceProxy getProxy() {
                return new InterfaceProxy() {
                        @Override
-                       public void returnVoid() {}
+                       public void returnVoid() {
+                       }
                        @Override
-                       public Integer returnInteger() { return 1;}
+                       public Integer returnInteger() {
+                               return 1;
+                       }
                        @Override
-                       public int returnInt() { return 1; }
+                       public int returnInt() {
+                               return 1;
+                       }
                        @Override
-                       public boolean returnBoolean() { return true; }
+                       public boolean returnBoolean() {
+                               return true;
+                       }
                        @Override
-                       public float returnFloat() { return 1f; }
+                       public float returnFloat() {
+                               return 1f;
+                       }
                        @Override
-                       public Float returnFloatObject() { return 1f; }
+                       public Float returnFloatObject() {
+                               return 1f;
+                       }
                        @Override
-                       public String returnString() { return "foobar"; }
+                       public String returnString() {
+                               return "foobar";
+                       }
                        @Override
-                       public String returnNullString() { return null; }
+                       public String returnNullString() {
+                               return null;
+                       }
                        @Override
-                       public int[] returnIntArray() { return new int[]{1,2}; }
+                       public int[] returnIntArray() {
+                               return new int[]{1,2};
+                       }
                        @Override
-                       public String[] returnStringArray() { return new 
String[]{"foo","bar",null};}
+                       public String[] returnStringArray() {
+                               return new String[]{"foo","bar",null};
+                       }
                        @Override
-                       public List<Integer> returnIntegerList() { return 
Arrays.asList(new Integer[]{1,2}); }
+                       public List<Integer> returnIntegerList() {
+                               return Arrays.asList(new Integer[]{1,2});
+                       }
                        @Override
-                       public List<String> returnStringList() { return 
Arrays.asList(new String[]{"foo","bar",null}); }
+                       public List<String> returnStringList() {
+                               return Arrays.asList(new 
String[]{"foo","bar",null});
+                       }
                        @Override
-                       public Bean returnBean() { return new Bean().init(); }
+                       public Bean returnBean() {
+                               return new Bean().init();
+                       }
                        @Override
-                       public Bean[] returnBeanArray() { return new Bean[]{new 
Bean().init()}; }
+                       public Bean[] returnBeanArray() {
+                               return new Bean[]{new Bean().init()};
+                       }
                        @Override
-                       public List<Bean> returnBeanList() { return 
Arrays.asList(new Bean().init()); }
+                       public List<Bean> returnBeanList() {
+                               return Arrays.asList(new Bean().init());
+                       }
                        @Override
-                       public Map<String,Bean> returnBeanMap() { return new 
HashMap<String,Bean>(){{put("foo",new Bean().init());}}; }
+                       public Map<String,Bean> returnBeanMap() {
+                               return new 
HashMap<String,Bean>(){{put("foo",new Bean().init());}};
+                       }
                        @Override
-                       public Map<String,List<Bean>> returnBeanListMap() { 
return new HashMap<String,List<Bean>>(){{put("foo",Arrays.asList(new 
Bean().init()));}}; }
+                       public Map<String,List<Bean>> returnBeanListMap() {
+                               return new 
HashMap<String,List<Bean>>(){{put("foo",Arrays.asList(new Bean().init()));}};
+                       }
                        @Override
-                       public Map<Integer,List<Bean>> 
returnBeanListMapIntegerKeys() { return new 
HashMap<Integer,List<Bean>>(){{put(1,Arrays.asList(new Bean().init()));}}; }
-
+                       public Map<Integer,List<Bean>> 
returnBeanListMapIntegerKeys() {
+                               return new 
HashMap<Integer,List<Bean>>(){{put(1,Arrays.asList(new Bean().init()));}};
+                       }
+                       @Override
+                       public void throwException1() throws 
InterfaceProxy.InterfaceProxyException1 {
+                               throw new 
InterfaceProxy.InterfaceProxyException1("foo");
+                       }
+                       @Override
+                       public void throwException2() throws 
InterfaceProxy.InterfaceProxyException2 {
+                               throw new 
InterfaceProxy.InterfaceProxyException2();
+                       }
                        @Override
                        public void setNothing() {
                        }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/b1a30b03/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java
 
b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java
index 2a7fdfd..74b59c6 100644
--- 
a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java
+++ 
b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java
@@ -160,6 +160,25 @@ public class InterfaceProxyTest extends RestTestcase {
        }
 
        @Test
+       public void throwException1() {
+               try {
+                       getProxy().throwException1();
+                       fail("Exception expected");
+               } catch (InterfaceProxy.InterfaceProxyException1 e) {
+                       assertEquals("foo", e.getMessage());
+               }
+       }
+
+       @Test
+       public void throwException2() {
+               try {
+                       getProxy().throwException2();
+                       fail("Exception expected");
+               } catch (InterfaceProxy.InterfaceProxyException2 e) {
+               }
+       }
+
+       @Test
        public void setNothing() {
                getProxy().setNothing();
        }
@@ -174,8 +193,8 @@ public class InterfaceProxyTest extends RestTestcase {
                try {
                        getProxy().setInt(2);
                        fail("Exception expected");
-               } catch (Exception e) {
-                       // Good.
+               } catch (AssertionError e) { // AssertionError thrown on server 
side.
+                       assertEquals("expected:<1> but was:<2>", 
e.getMessage());
                }
        }
 
@@ -214,8 +233,8 @@ public class InterfaceProxyTest extends RestTestcase {
                try {
                        getProxy().setNullString("foo");
                        fail("Exception expected");
-               } catch (Exception e) {
-                       // Good.
+               } catch (AssertionError e) { // AssertionError thrown on server 
side.
+                       assertEquals("expected null, but was:<foo>", 
e.getLocalizedMessage());
                }
        }
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/b1a30b03/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java 
b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
index 89abdc4..f1e0228 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -432,7 +432,19 @@ public final class RestContext extends Context {
                                                                                
                // Parse the args and invoke the method.
                                                                                
                Parser p = req.getParser();
                                                                                
                Object input = p.isReaderParser() ? req.getReader() : 
req.getInputStream();
-                                                                               
                res.setOutput(m.invoke(o, p.parseArgs(input, 
m.getGenericParameterTypes())));
+                                                                               
                Object output = null;
+                                                                               
                try {
+                                                                               
                        output = m.invoke(o, p.parseArgs(input, 
m.getGenericParameterTypes()));
+                                                                               
                } catch (InvocationTargetException e) {
+                                                                               
                        res.setHeader("Exception-Name", 
e.getCause().getClass().getName());
+                                                                               
                        res.setHeader("Exception-Message", 
e.getCause().getMessage());
+                                                                               
                        throw e;
+                                                                               
                } catch (Exception e) {
+                                                                               
                        res.setHeader("Exception-Name", e.getClass().getName());
+                                                                               
                        res.setHeader("Exception-Message", 
e.getCause().getMessage());
+                                                                               
                        throw e;
+                                                                               
                }
+                                                                               
                res.setOutput(output);
                                                                                
                return SC_OK;
                                                                                
        } catch (Exception e) {
                                                                                
                throw new RestException(SC_INTERNAL_SERVER_ERROR, e);

Reply via email to