This is an automated email from the ASF dual-hosted git repository. reta pushed a commit to branch 3.4.x-fixes in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 69e31b9bbde9b6662989868cb600b545ba379b97 Author: Andriy Redko <[email protected]> AuthorDate: Tue Aug 17 17:19:25 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 | 184 +++++++++++++++++++++ .../src/test/resources/application-client.yml | 10 ++ 5 files changed, 218 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..7f7a585 --- /dev/null +++ b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringClientOnlyJaxrsTest.java @@ -0,0 +1,184 @@ +/** + * 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.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.servlet.function.RequestPredicates; +import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.RouterFunctions; +import org.springframework.web.servlet.function.ServerResponse; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.search.RequiredSearch; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +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; + +@ExtendWith(SpringExtension.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; + + @EnableAutoConfiguration + static class TestConfig { + @Bean + public Feature metricsFeature(MetricsProvider metricsProvider) { + return new MetricsFeature(metricsProvider); + } + + @Bean + public JacksonJsonProvider jacksonJsonProvider() { + return new JacksonJsonProvider(); + } + + @Bean + RouterFunction<ServerResponse> get() { + return RouterFunctions.route(RequestPredicates.GET("/api/library"), + req -> ServerResponse + .ok() + .build()); + } + + @Bean + RouterFunction<ServerResponse> delete() { + return RouterFunctions.route(RequestPredicates.DELETE("/api/library"), + req -> ServerResponse + .badRequest() + .build()); + } + } + + @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); + } + + @AfterEach + public void clear() { + registry.clear(); + } + + @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
