You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2020/04/28 00:13:10 UTC

[cxf] branch master updated: CXF-8261: Exceptions being thrown in a ClientResponseFilter are ignored (#662)

This is an automated email from the ASF dual-hosted git repository.

reta pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/master by this push:
     new 9584a56  CXF-8261: Exceptions being thrown in a ClientResponseFilter are ignored (#662)
9584a56 is described below

commit 9584a56a6289d8e1899a33cb6c604073d7851bbc
Author: Andriy Redko <dr...@gmail.com>
AuthorDate: Mon Apr 27 20:13:01 2020 -0400

    CXF-8261: Exceptions being thrown in a ClientResponseFilter are ignored (#662)
---
 .../apache/cxf/jaxrs/client/AbstractClient.java    |   5 +-
 .../spec/ClientResponseFilterInterceptor.java      |  27 ++--
 .../client/spec/ClientResponseFilterTest.java      | 168 +++++++++++++++++++++
 3 files changed, 190 insertions(+), 10 deletions(-)

diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
index 80eb2cc..c65945c 100644
--- a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
@@ -48,6 +48,7 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.ProcessingException;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.client.ResponseProcessingException;
 import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.EntityTag;
 import javax.ws.rs.core.Form;
@@ -615,7 +616,9 @@ public abstract class AbstractClient implements Client {
 
         Exchange exchange = outMessage.getExchange();
         Integer responseCode = getResponseCode(exchange);
-        if (responseCode == null
+        if (actualEx instanceof ResponseProcessingException) {
+            throw (ResponseProcessingException)actualEx;
+        } else if (responseCode == null
             || responseCode < 300 && !(actualEx instanceof IOException)
             || actualEx instanceof IOException && exchange.get("client.redirect.exception") != null) {
             if (actualEx instanceof ProcessingException) {
diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spec/ClientResponseFilterInterceptor.java b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spec/ClientResponseFilterInterceptor.java
index 0db80e3..901525c 100644
--- a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spec/ClientResponseFilterInterceptor.java
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spec/ClientResponseFilterInterceptor.java
@@ -23,10 +23,10 @@ import java.io.InputStream;
 import java.util.List;
 import java.util.Map;
 
-import javax.ws.rs.ProcessingException;
 import javax.ws.rs.client.ClientRequestContext;
 import javax.ws.rs.client.ClientResponseContext;
 import javax.ws.rs.client.ClientResponseFilter;
+import javax.ws.rs.client.ResponseProcessingException;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
 
@@ -54,18 +54,27 @@ public class ClientResponseFilterInterceptor extends AbstractInDatabindingInterc
 
         List<ProviderInfo<ClientResponseFilter>> filters = pf.getClientResponseFilters();
         if (!filters.isEmpty()) {
-            ClientRequestContext reqContext = new ClientRequestContextImpl(inMessage.getExchange().getOutMessage(),
-                                                                        true);
-
-            ClientResponseContext respContext =
-                new ClientResponseContextImpl((ResponseImpl)getResponse(inMessage),
-                                              inMessage);
+            final ClientRequestContext reqContext = new ClientRequestContextImpl(
+                inMessage.getExchange().getOutMessage(), true);
+            final ResponseImpl response = (ResponseImpl)getResponse(inMessage);
+            final ClientResponseContext respContext = new ClientResponseContextImpl(response, inMessage);
             for (ProviderInfo<ClientResponseFilter> filter : filters) {
                 InjectionUtils.injectContexts(filter.getProvider(), filter, inMessage);
                 try {
                     filter.getProvider().filter(reqContext, respContext);
-                } catch (IOException ex) {
-                    throw new ProcessingException(ex);
+                } catch (RuntimeException | IOException ex) {
+                    // Complete the IN chain, if we won't set it, the AbstractClient::preProcessResult
+                    // would be stuck waiting for the IN chain completion.
+                    if (!inMessage.getExchange().isOneWay()) {
+                        synchronized (inMessage.getExchange()) {
+                            inMessage.getExchange().put("IN_CHAIN_COMPLETE", Boolean.TRUE);
+                        }
+                    }
+                    
+                    // When a provider method throws an exception, the JAX-RS client runtime will map 
+                    // it to an instance of ResponseProcessingException if thrown while processing 
+                    // a response (4.5.2 Client Runtime).
+                    throw new ResponseProcessingException(response, ex);
                 }
             }
         }
diff --git a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spec/ClientResponseFilterTest.java b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spec/ClientResponseFilterTest.java
new file mode 100644
index 0000000..9056212
--- /dev/null
+++ b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spec/ClientResponseFilterTest.java
@@ -0,0 +1,168 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.client.spec;
+
+import java.io.IOException;
+
+import javax.annotation.Priority;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
+import javax.ws.rs.client.ResponseProcessingException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.cxf.endpoint.Server;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.cxf.transport.local.LocalTransportFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class ClientResponseFilterTest {
+    private static final String ADDRESS = "local://client";
+    private Server server;
+
+    @Path("/")
+    public static class TestEndpoint {
+        @GET
+        public String get() {
+            return "hello rabbit";
+        }
+        
+        @PUT
+        public Response update() {
+            return Response.status(Status.NOT_FOUND).build();
+        }
+        
+        @DELETE
+        public void delete() {
+        }
+    }
+
+    @Priority(2)
+    public static class AddHeaderClientResponseFilter implements ClientResponseFilter {
+        @Override
+        public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) 
+                throws IOException {
+            responseContext.getHeaders().add("X-Done", "true");
+        }
+    }
+
+    @Priority(1)
+    public static class FaultyClientResponseFilter implements ClientResponseFilter {
+        @Override
+        public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) 
+                throws IOException {
+            throw new IOException("Exception from client response filter");
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        final JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
+        sf.setResourceClasses(TestEndpoint.class);
+        sf.setResourceProvider(TestEndpoint.class, new SingletonResourceProvider(new TestEndpoint(), false));
+        sf.setTransportId(LocalTransportFactory.TRANSPORT_ID);
+        sf.setAddress(ADDRESS);
+        server = sf.create();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        server.stop();
+        server.destroy();
+    }
+
+    @Test(expected = ResponseProcessingException.class)
+    public void testExceptionInClientResponseFilter() {
+        try (Response response = ClientBuilder.newClient()
+             .register(FaultyClientResponseFilter.class)
+             .target(ADDRESS)
+             .request()
+             .get()) {
+            assertEquals(200, response.getStatus());
+            assertEquals("hello rabbit", response.readEntity(String.class));
+        }
+    }
+    
+    @Test(expected = ResponseProcessingException.class)
+    public void testExceptionInClientResponseFilterWhenNotFound() {
+        try (Response response = ClientBuilder.newClient()
+             .register(FaultyClientResponseFilter.class)
+             .target(ADDRESS)
+             .request()
+             .put(null)) {
+            fail("Should not be invoked");
+        }
+    }
+    
+    @Test
+    public void testClientResponseFilterWhenNotFound() {
+        try (Response response = ClientBuilder.newClient()
+             .register(AddHeaderClientResponseFilter.class)
+             .target(ADDRESS)
+             .request()
+             .put(null)) {
+            assertEquals(404, response.getStatus());
+            assertEquals("true", response.getHeaderString("X-Done"));
+        }
+    }
+    
+    @Test
+    public void testClientResponseFilter() {
+        try (Response response = ClientBuilder.newClient()
+             .register(AddHeaderClientResponseFilter.class)
+             .target(ADDRESS)
+             .request()
+             .get()) {
+            assertEquals(200, response.getStatus());
+            assertEquals("true", response.getHeaderString("X-Done"));
+        }
+    }
+    
+    @Test
+    public void testExceptionWhenMultipleClientResponseFilters() {
+        try (Response response = ClientBuilder.newClient()
+             .register(AddHeaderClientResponseFilter.class)
+             .register(FaultyClientResponseFilter.class)
+             .target(ADDRESS)
+             .request()
+             .put(null)) {
+            fail("Should not be invoked");
+        } catch (ResponseProcessingException ex) {
+            // Seems to be an issue here, CXF creates the response context only once
+            // for all client response filters, the changes performed upstream the chain
+            // are not visible to the downstream filters. 
+            assertEquals(null, ex.getResponse().getHeaderString("X-Done"));
+        } catch (Throwable ex) {
+            fail("Should be handled by ResponseProcessingException block");
+        }
+    }
+}
\ No newline at end of file