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 2021/08/18 00:52:51 UTC

[cxf] 01/02: CXF-8539: Allow client-only Spring Boot autoconfiguration (#834)

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

reta pushed a commit to branch 3.3.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit ccb295ecc5ad2f0626a7008827bccd9bd3095dc9
Author: Andriy Redko <dr...@gmail.com>
AuthorDate: Tue Aug 17 20:51:56 2021 -0400

    CXF-8539: Allow client-only Spring Boot autoconfiguration (#834)
    
    (cherry picked from commit 37fbc36cbefc372102a51206ecf4a3d01185b308)
---
 .../boot/autoconfigure/CxfAutoConfiguration.java   |   2 +
 .../spring/boot/autoconfigure/CxfProperties.java   |  12 ++
 .../autoconfigure/CxfAutoConfigurationTest.java    |  10 +
 .../spring/boot/SpringClientOnlyJaxrsTest.java     | 201 +++++++++++++++++++++
 .../src/test/resources/application-client.yml      |  10 +
 5 files changed, 235 insertions(+)

diff --git a/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/CxfAutoConfiguration.java b/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/CxfAutoConfiguration.java
index 92d862e..2554081 100644
--- a/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/CxfAutoConfiguration.java
+++ b/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/CxfAutoConfiguration.java
@@ -31,6 +31,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.servlet.ServletRegistrationBean;
@@ -59,6 +60,7 @@ public class CxfAutoConfiguration {
 
     @Bean
     @ConditionalOnMissingBean(name = "cxfServletRegistration")
+    @ConditionalOnProperty(prefix = "cxf", name = "servlet.enabled", matchIfMissing = true)
     public ServletRegistrationBean<CXFServlet> cxfServletRegistration() {
         String path = this.properties.getPath();
         String urlMapping = path.endsWith("/") ? path + "*" : path + "/*";
diff --git a/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/CxfProperties.java b/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/CxfProperties.java
index 7f18726..709ada5 100644
--- a/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/CxfProperties.java
+++ b/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/CxfProperties.java
@@ -74,6 +74,11 @@ public class CxfProperties {
          * Load on startup priority of the Apache CXF servlet.
          */
         private int loadOnStartup = -1;
+        
+        /**
+         * Enables or disables the servlet registration
+         */
+        private boolean enabled = true;
 
         public Map<String, String> getInit() {
             return this.init;
@@ -91,6 +96,13 @@ public class CxfProperties {
             this.loadOnStartup = loadOnStartup;
         }
 
+        public boolean isEnabled() {
+            return enabled;
+        }
+
+        public void setEnabled(boolean enabled) {
+            this.enabled = enabled;
+        }
     }
 
     public static class Metrics {
diff --git a/integration/spring-boot/autoconfigure/src/test/java/org/apache/cxf/spring/boot/autoconfigure/CxfAutoConfigurationTest.java b/integration/spring-boot/autoconfigure/src/test/java/org/apache/cxf/spring/boot/autoconfigure/CxfAutoConfigurationTest.java
index 1db2063..cc6b1b3 100644
--- a/integration/spring-boot/autoconfigure/src/test/java/org/apache/cxf/spring/boot/autoconfigure/CxfAutoConfigurationTest.java
+++ b/integration/spring-boot/autoconfigure/src/test/java/org/apache/cxf/spring/boot/autoconfigure/CxfAutoConfigurationTest.java
@@ -22,6 +22,7 @@ import java.util.Map;
 
 import org.apache.cxf.endpoint.Server;
 import org.apache.cxf.endpoint.ServerImpl;
+import org.apache.cxf.helpers.CastUtils;
 import org.apache.cxf.spring.boot.jaxrs.CustomJaxRSServer;
 import org.hamcrest.Matcher;
 import org.springframework.beans.factory.UnsatisfiedDependencyException;
@@ -47,6 +48,7 @@ import static org.hamcrest.Matchers.hasEntry;
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.hasProperty;
 import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.collection.IsEmptyCollection.empty;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -113,6 +115,14 @@ public class CxfAutoConfigurationTest {
         assertThat(ReflectionTestUtils.getField(registrationBean, "loadOnStartup"),
                 equalTo(1));
     }
+    
+    @Test
+    public void disableServlet() {
+        load(CxfAutoConfiguration.class, "cxf.servlet.enabled=false");
+        Map<String, ServletRegistrationBean<?>> registrationBeans = CastUtils.cast(this.context
+                .getBeansOfType(ServletRegistrationBean.class));
+        assertThat(registrationBeans.keySet(), empty());
+    }
 
     @Test
     public void customInitParameters() {
diff --git a/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringClientOnlyJaxrsTest.java b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringClientOnlyJaxrsTest.java
new file mode 100644
index 0000000..ca75f5a
--- /dev/null
+++ b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringClientOnlyJaxrsTest.java
@@ -0,0 +1,201 @@
+/**
+ * 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.systest.jaxrs.spring.boot;
+
+import java.util.Map;
+
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Invocation.Builder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.metrics.MetricsFeature;
+import org.apache.cxf.metrics.MetricsProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.search.RequiredSearch;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static java.util.stream.Collectors.toMap;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.entry;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = SpringClientOnlyJaxrsTest.TestConfig.class)
+@ActiveProfiles("client")
+public class SpringClientOnlyJaxrsTest {
+
+    @Autowired
+    private MeterRegistry registry;
+    
+    @Autowired
+    private MetricsProvider metricsProvider;
+    
+    @LocalServerPort
+    private int port;
+
+    @Configuration
+    @EnableAutoConfiguration
+    static class TestConfig {
+        @Bean
+        public Feature metricsFeature(MetricsProvider metricsProvider) {
+            return new MetricsFeature(metricsProvider);
+        }
+        
+        @Bean
+        public JacksonJsonProvider jacksonJsonProvider() {
+            return new JacksonJsonProvider();
+        }
+        
+        @Autowired
+        public void setHandlerMapping(RequestMappingHandlerMapping mapping) throws NoSuchMethodException {
+            class LibraryHandler {
+                public ResponseEntity<?> get() {
+                    return ResponseEntity.ok().build();
+                }
+                
+                public ResponseEntity<?> delete() {
+                    return ResponseEntity.badRequest().build();
+                }
+            }
+            
+            final LibraryHandler handler = new LibraryHandler();
+
+            mapping.registerMapping(
+                RequestMappingInfo
+                    .paths("/api/library")
+                    .methods(RequestMethod.GET)
+                    .build(), 
+                handler, 
+                LibraryHandler.class.getMethod("get")); 
+            
+            mapping.registerMapping(
+                RequestMappingInfo
+                    .paths("/api/library")
+                    .methods(RequestMethod.DELETE)
+                    .build(), 
+                handler, 
+                LibraryHandler.class.getMethod("delete")); 
+        }
+    }
+
+    @Autowired
+    public void setBus(Bus bus) {
+        // By default, the exception are propagated and out fault interceptors are not called 
+        bus.setProperty("org.apache.cxf.propagate.exception", Boolean.FALSE);
+    }
+
+    @Before
+    public void setUp() {
+        this.registry.getMeters().forEach(meter -> registry.remove(meter));
+    }
+
+    @Test
+    public void testJaxrsClientSuccessMetric() {
+        final WebTarget target = createWebTarget();
+        
+        final Builder builder = target.request().header(HttpHeaders.CONTENT_TYPE, "text/plain"); 
+        try (Response r = builder.get()) {
+            assertThat(r.getStatus()).isEqualTo(200);
+        }
+        
+        // no server meters
+        assertThat(registry.getMeters())
+            .noneMatch(m -> "cxf.server.requests".equals(m.getId().getName()));
+        
+        RequiredSearch clientRequestMetrics = registry.get("cxf.client.requests");
+
+        Map<Object, Object> clientTags = clientRequestMetrics.timer().getId().getTags().stream()
+                .collect(toMap(Tag::getKey, Tag::getValue));
+
+        assertThat(clientTags)
+            .containsOnly(
+                entry("exception", "None"),
+                entry("method", "GET"),
+                entry("operation", "UNKNOWN"),
+                entry("uri", "http://localhost:" + port + "/api/library"),
+                entry("outcome", "SUCCESS"),
+                entry("status", "200"));
+    }
+    
+    @Test
+    public void testJaxrsClientExceptionMetric() {
+        final WebTarget target = ClientBuilder
+            .newClient()
+            .register(new MetricsFeature(metricsProvider))
+            .target("http://localhost:" + port + "/api/library");
+        
+        final Builder builder = target.request().header(HttpHeaders.CONTENT_TYPE, "text/plain"); 
+        assertThatThrownBy(() -> builder.delete(String.class))
+            .isInstanceOf(BadRequestException.class)
+            .hasMessageContaining("Bad Request");
+
+        // no server meters
+        assertThat(registry.getMeters())
+            .noneMatch(m -> "cxf.server.requests".equals(m.getId().getName()));
+        
+        RequiredSearch clientRequestMetrics = registry.get("cxf.client.requests");
+
+        Map<Object, Object> clientTags = clientRequestMetrics.timer().getId().getTags().stream()
+                .collect(toMap(Tag::getKey, Tag::getValue));
+
+        assertThat(clientTags)
+            .containsOnly(
+                entry("exception", "None"),
+                entry("method", "DELETE"),
+                entry("operation", "UNKNOWN"),
+                entry("uri", "http://localhost:" + port + "/api/library"),
+                entry("outcome", "CLIENT_ERROR"),
+                entry("status", "400"));
+    }
+
+    private WebTarget createWebTarget() {
+        return ClientBuilder
+            .newClient()
+            .register(JacksonJsonProvider.class)
+            .register(new MetricsFeature(metricsProvider))
+            .target("http://localhost:" + port + "/api/library");
+    }
+
+}
diff --git a/systests/spring-boot/src/test/resources/application-client.yml b/systests/spring-boot/src/test/resources/application-client.yml
new file mode 100644
index 0000000..71a23f4
--- /dev/null
+++ b/systests/spring-boot/src/test/resources/application-client.yml
@@ -0,0 +1,10 @@
+cxf:
+  path: /api
+  servlet:
+    enabled: false
+  jaxrs:
+    component-scan: false
+    classes-scan: false
+  metrics:
+    jaxws:
+      enabled: false
\ No newline at end of file