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/09/17 00:05:28 UTC

[cxf] branch master updated: CXF-7543: JAX-RS Features not used in proxies or WebClients (#849)

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 bf25a93  CXF-7543: JAX-RS Features not used in proxies or WebClients (#849)
bf25a93 is described below

commit bf25a931a96a6eb6cac47651bef1f2ff4fb442bc
Author: Andriy Redko <dr...@gmail.com>
AuthorDate: Thu Sep 16 20:05:21 2021 -0400

    CXF-7543: JAX-RS Features not used in proxies or WebClients (#849)
---
 .../cxf/jaxrs/client/JAXRSClientFactoryBean.java   | 41 ++++++++++
 .../JAXRSClientFactoryBeanDefinitionParser.java    | 59 ++++++++++++++
 .../cxf/jaxrs/client/cache/ClientCacheTest.java    | 23 ++++++
 .../client/spring/JAXRSClientFactoryBeanTest.java  | 34 ++++++++
 .../client/spring/SpringParameterHandler.java      | 58 +++++++++++++
 .../org/apache/cxf/jaxrs/client/spring/clients.xml |  6 ++
 .../client/MicroProfileClientFactoryBean.java      | 14 +++-
 .../client/CxfTypeSafeClientBuilderTest.java       | 42 ++++++++++
 .../client/MicroProfileClientFactoryBeanTest.java  | 94 ++++++++++++++++++++++
 9 files changed, 368 insertions(+), 3 deletions(-)

diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java
index 13d3c28..ccf547c 100644
--- a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java
@@ -24,12 +24,19 @@ import java.lang.reflect.Type;
 import java.net.URI;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.logging.Logger;
 
 import javax.ws.rs.CookieParam;
 import javax.ws.rs.HeaderParam;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configurable;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.ext.ParamConverter;
 import javax.ws.rs.ext.ParamConverterProvider;
@@ -45,6 +52,8 @@ import org.apache.cxf.endpoint.UpfrontConduitSelector;
 import org.apache.cxf.jaxrs.AbstractJAXRSFactoryBean;
 import org.apache.cxf.jaxrs.JAXRSServiceFactoryBean;
 import org.apache.cxf.jaxrs.JAXRSServiceImpl;
+import org.apache.cxf.jaxrs.impl.ConfigurableImpl;
+import org.apache.cxf.jaxrs.impl.FeatureContextImpl;
 import org.apache.cxf.jaxrs.impl.MetadataMap;
 import org.apache.cxf.jaxrs.model.ClassResourceInfo;
 import org.apache.cxf.jaxrs.utils.AnnotationUtils;
@@ -403,6 +412,10 @@ public class JAXRSClientFactoryBean extends AbstractJAXRSFactoryBean {
             });
         }
     }
+    
+    protected <C extends Configurable<C>> Configurable<?> getConfigurableFor(C context) {
+        return new ConfigurableImpl<>(context, RuntimeType.CLIENT);
+    }
 
     protected void applyFeatures(AbstractClient client) {
         if (getFeatures() != null) {
@@ -410,6 +423,34 @@ public class JAXRSClientFactoryBean extends AbstractJAXRSFactoryBean {
                 feature.initialize(client.getConfiguration(), getBus());
             });
         }
+        
+        // Process JAX-RS features which are passed through as providers 
+        final Set<Object> providers = new HashSet<>();
+        for (final Object provider: getProviders()) {
+            if (provider instanceof Feature) {
+                final Feature feature = (Feature)provider;
+                final FeatureContextImpl context = new FeatureContextImpl();
+                final Configurable<?> configurable = getConfigurableFor(context);
+                final Configuration configuration = configurable.getConfiguration();
+                final Set<Object> registered = configuration.getInstances();
+                
+                if (!configuration.isRegistered(feature)) {
+                    configurable.register(feature);
+                    
+                    // Disregarding if the feature is enabled or disabled, register only newly added providers,
+                    // excluding pre-existing ones and the feature instance itself.
+                    final Set<Object> added = new HashSet<Object>(configuration.getInstances());
+                    added.remove(feature);
+                    added.removeAll(registered);
+                    
+                    providers.addAll(added);
+                }
+            }
+        }
+        
+        if (!providers.isEmpty()) {
+            setProviders(Arrays.asList(providers));
+        }
     }
 
     /**
diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanDefinitionParser.java b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanDefinitionParser.java
index 6543ddc..5c6989d 100644
--- a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanDefinitionParser.java
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanDefinitionParser.java
@@ -20,12 +20,14 @@ package org.apache.cxf.jaxrs.client.spring;
 
 import java.io.IOException;
 import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
 import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
 import javax.ws.rs.Path;
+import javax.ws.rs.core.Configurable;
 import javax.ws.rs.ext.Provider;
 import javax.xml.namespace.QName;
 
@@ -35,6 +37,7 @@ import org.apache.cxf.bus.spring.BusWiringBeanFactoryPostProcessor;
 import org.apache.cxf.common.util.ClasspathScanner;
 import org.apache.cxf.configuration.spring.AbstractFactoryBeanDefinitionParser;
 import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
+import org.apache.cxf.jaxrs.impl.ConfigurableImpl;
 import org.apache.cxf.jaxrs.model.UserResource;
 import org.apache.cxf.jaxrs.utils.ResourceUtils;
 import org.springframework.beans.BeansException;
@@ -109,11 +112,50 @@ public class JAXRSClientFactoryBeanDefinitionParser extends AbstractFactoryBeanD
             setFirstChildAsProperty(el, ctx, bean, name);
         }
     }
+    
+    /**
+     * Instantiate newly registered provider instances using {@link AutowireCapableBeanFactory} 
+     */
+    private static class SpringInstantiator implements ConfigurableImpl.Instantiator {
+        private final AutowireCapableBeanFactory beanFactory;
+        
+        SpringInstantiator(final AutowireCapableBeanFactory beanFactory) {
+            this.beanFactory = beanFactory;
+        }
+
+        @Override
+        public <T> Object create(Class<T> cls) {
+            return beanFactory.createBean(cls, resolveAutowireMode(cls), true);
+        }
+        
+        @Override
+        public void release(Object instance) {
+            beanFactory.destroyBean(instance);
+        }
+        
+        /**
+         * Return the resolved autowire mode (AUTOWIRE_CONSTRUCTOR or AUTOWIRE_BY_TYPE).
+         */
+        private <T> int resolveAutowireMode(Class<T> cls) {
+            final Constructor<?>[] constructors = cls.getConstructors();
+            
+            for (Constructor<?> constructor: constructors) {
+                // If there is a default constructor, we are good to go with
+                // autowiring by type, otherwise we need construction injection.
+                if (constructor.getParameterCount() == 0) {
+                    return AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
+                }
+            }
+
+            return AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;
+        }
+    }
 
     public static class JAXRSSpringClientFactoryBean extends JAXRSClientFactoryBean
         implements ApplicationContextAware {
 
         private List<String> basePackages;
+        private SpringInstantiator instantiator;
 
         public JAXRSSpringClientFactoryBean() {
             super();
@@ -125,6 +167,10 @@ public class JAXRSClientFactoryBeanDefinitionParser extends AbstractFactoryBeanD
 
         public void setApplicationContext(ApplicationContext ctx) throws BeansException {
             try {
+                if (instantiator == null) {
+                    instantiator = new SpringInstantiator(ctx.getAutowireCapableBeanFactory());
+                }
+                
                 if (basePackages != null) {
                     final Map< Class< ? extends Annotation >, Collection< Class< ? > > > classes =
                         ClasspathScanner.findClasses(basePackages, Path.class, Provider.class);
@@ -159,6 +205,17 @@ public class JAXRSClientFactoryBeanDefinitionParser extends AbstractFactoryBeanD
                 setBus(BusWiringBeanFactoryPostProcessor.addDefaultBus(ctx));
             }
         }
+        
+        @Override
+        protected <C extends Configurable<C>> Configurable<?> getConfigurableFor(C context) {
+            final Configurable<?> configurable = super.getConfigurableFor(context);
+            
+            if (instantiator != null) {
+                configurable.register(instantiator);
+            }
+            
+            return configurable;
+        }
     }
 
     static Class<?> getServiceClass(Collection<Class<?>> rootClasses) {
@@ -184,4 +241,6 @@ public class JAXRSClientFactoryBeanDefinitionParser extends AbstractFactoryBeanD
         }
         return providers;
     }
+    
+    
 }
diff --git a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/cache/ClientCacheTest.java b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/cache/ClientCacheTest.java
index f784f5d..5fc4be6 100644
--- a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/cache/ClientCacheTest.java
+++ b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/cache/ClientCacheTest.java
@@ -89,6 +89,29 @@ public class ClientCacheTest {
     }
 
     @Test
+    public void testGetTimeStringUsingClientFactory() {
+        CacheControlFeature feature = new CacheControlFeature();
+        try {
+            JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean();
+            bean.setAddress(ADDRESS);
+            bean.setProviders(Collections.singletonList(feature));
+            bean.setTransportId(LocalTransportFactory.TRANSPORT_ID);
+            
+            final WebClient cached = bean.createWebClient()
+                .accept("text/plain")
+                .header(HttpHeaders.CACHE_CONTROL, "public");
+            
+            final Response r = cached.get();
+            assertEquals(Response.Status.OK.getStatusCode(), r.getStatus());
+            final String r1 = r.readEntity(String.class);
+            waitABit();
+            assertEquals(r1, cached.get().readEntity(String.class));
+        } finally {
+            feature.close();
+        }
+    }
+
+    @Test
     public void testGetTimeStringAsInputStream() throws Exception {
         CacheControlFeature feature = new CacheControlFeature();
         try {
diff --git a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanTest.java b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanTest.java
index ecfff4c..f34d037 100644
--- a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanTest.java
+++ b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanTest.java
@@ -18,22 +18,30 @@
  */
 package org.apache.cxf.jaxrs.client.spring;
 
+import java.util.List;
+
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
 import javax.xml.namespace.QName;
 
 import org.apache.cxf.BusFactory;
 import org.apache.cxf.jaxrs.client.Client;
 import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
+import org.apache.cxf.jaxrs.client.cache.CacheControlClientReaderInterceptor;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 
 import org.junit.After;
 import org.junit.Test;
 
 import static org.hamcrest.CoreMatchers.endsWith;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 
 
+
 public class JAXRSClientFactoryBeanTest {
 
     @After
@@ -110,4 +118,30 @@ public class JAXRSClientFactoryBeanTest {
                 endsWith("?list=1&list=2"));
         }
     }
+    
+    @Test
+    public void testClientWithFeatures() throws Exception {
+        try (ClassPathXmlApplicationContext ctx =
+                new ClassPathXmlApplicationContext(new String[] {"/org/apache/cxf/jaxrs/client/spring/clients.xml"})) {
+            final Client bean = (Client) ctx.getBean("client4");
+            assertNotNull(bean);
+            
+            final JAXRSClientFactoryBean cfb = (JAXRSClientFactoryBean)ctx.getBean("client4.proxyFactory");
+            assertNotNull(bean);
+            
+            assertThat((List<Object>)cfb.getProviders(), 
+                hasItem(instanceOf(CacheControlClientReaderInterceptor.class)));
+            
+            assertThat((List<Object>)cfb.getProviders(), 
+                hasItem(instanceOf(SpringParameterHandler.class)));
+        }
+    }
+    
+    public static class SomeFeature implements Feature {
+        @Override
+        public boolean configure(FeatureContext context) {
+            context.register(SpringParameterHandler.class);
+            return true;
+        }
+    }
 }
\ No newline at end of file
diff --git a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/SpringParameterHandler.java b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/SpringParameterHandler.java
new file mode 100644
index 0000000..10745a5
--- /dev/null
+++ b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/SpringParameterHandler.java
@@ -0,0 +1,58 @@
+/**
+ * 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.spring;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Objects;
+
+import javax.ws.rs.ext.ParamConverter;
+import javax.ws.rs.ext.ParamConverterProvider;
+
+import org.apache.cxf.jaxrs.client.Customer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+
+public class SpringParameterHandler implements ParamConverterProvider, ParamConverter<Customer> {
+    @Autowired
+    public SpringParameterHandler(ApplicationContext context) {
+        Objects.requireNonNull(context);
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> ParamConverter<T> getConverter(Class<T> cls, Type arg1, Annotation[] arg2) {
+        if (Customer.class == cls) {
+            return (ParamConverter<T>)this;
+        }
+        return null;
+    }
+
+    public Customer fromString(String s) throws IllegalArgumentException {
+        Customer c = new Customer();
+        c.setName(s);
+        return c;
+    }
+
+    @Override
+    public String toString(Customer arg0) throws IllegalArgumentException {
+        return null;
+    }
+}
diff --git a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/clients.xml b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/clients.xml
index 320de2e..7f33450 100644
--- a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/clients.xml
+++ b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/clients.xml
@@ -59,4 +59,10 @@
             <entry key="expand.query.value.as.collection" value="true" />
         </jaxrs:properties>
     </jaxrs:client>
+    <jaxrs:client id="client4" serviceClass="org.apache.cxf.jaxrs.resources.BookStore" address="http://localhost:9000/foo" threadSafe="true">
+        <jaxrs:providers>
+            <bean class="org.apache.cxf.jaxrs.client.cache.CacheControlFeature"/>
+            <bean class="org.apache.cxf.jaxrs.client.spring.JAXRSClientFactoryBeanTest.SomeFeature"/>
+        </jaxrs:providers>
+    </jaxrs:client>
 </beans>
\ No newline at end of file
diff --git a/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/MicroProfileClientFactoryBean.java b/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/MicroProfileClientFactoryBean.java
index 389c4fa..0f10811 100644
--- a/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/MicroProfileClientFactoryBean.java
+++ b/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/MicroProfileClientFactoryBean.java
@@ -28,6 +28,7 @@ import java.util.concurrent.ExecutorService;
 
 import javax.ws.rs.client.ClientRequestFilter;
 import javax.ws.rs.client.ClientResponseFilter;
+import javax.ws.rs.core.Configurable;
 import javax.ws.rs.core.Configuration;
 
 import org.apache.cxf.common.util.ClassHelper;
@@ -51,17 +52,19 @@ import org.eclipse.microprofile.rest.client.RestClientBuilder;
 public class MicroProfileClientFactoryBean extends JAXRSClientFactoryBean {
     private final Comparator<ProviderInfo<?>> comparator;
     private final List<Object> registeredProviders;
+    private final Configurable<RestClientBuilder> configurable;
     private final Configuration configuration;
     private ClassLoader proxyLoader;
     private boolean inheritHeaders;
     private ExecutorService executorService;
     private TLSConfiguration secConfig;
 
-    public MicroProfileClientFactoryBean(MicroProfileClientConfigurableImpl<RestClientBuilder> configuration,
+    public MicroProfileClientFactoryBean(MicroProfileClientConfigurableImpl<RestClientBuilder> configurable,
                                          String baseUri, Class<?> aClass, ExecutorService executorService,
                                          TLSConfiguration secConfig) {
         super(new MicroProfileServiceFactoryBean());
-        this.configuration = configuration.getConfiguration();
+        this.configurable = configurable;
+        this.configuration = configurable.getConfiguration();
         this.comparator = MicroProfileClientProviderFactory.createComparator(this);
         this.executorService = (executorService == null) ? Utils.defaultExecutorService() : executorService; 
         this.secConfig = secConfig;
@@ -71,7 +74,7 @@ public class MicroProfileClientFactoryBean extends JAXRSClientFactoryBean {
         super.setProperties(this.configuration.getProperties());
         registeredProviders = new ArrayList<>();
         registeredProviders.addAll(processProviders());
-        if (!configuration.isDefaultExceptionMapperDisabled()) {
+        if (!configurable.isDefaultExceptionMapperDisabled()) {
             registeredProviders.add(new ProviderInfo<>(new DefaultResponseExceptionMapper(), getBus(), false));
         }
         registeredProviders.add(new ProviderInfo<>(new JsrJsonpProvider(), getBus(), false));
@@ -134,6 +137,11 @@ public class MicroProfileClientFactoryBean extends JAXRSClientFactoryBean {
                     inheritHeaders, executorService, configuration, interceptorWrapper, secConfig, varValues);
         }
     }
+    
+    @Override
+    protected <C extends Configurable<C>> Configurable<?> getConfigurableFor(C context) {
+        return configurable;
+    }
 
     Configuration getConfiguration() {
         return configuration;
diff --git a/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/CxfTypeSafeClientBuilderTest.java b/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/CxfTypeSafeClientBuilderTest.java
index c9d09a6..8ca5f4b 100644
--- a/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/CxfTypeSafeClientBuilderTest.java
+++ b/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/CxfTypeSafeClientBuilderTest.java
@@ -24,6 +24,8 @@ import java.net.URL;
 
 import javax.ws.rs.PathParam;
 import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
 import javax.ws.rs.core.Response;
 
 import org.apache.cxf.jaxrs.client.WebClientUtil;
@@ -116,6 +118,30 @@ public class CxfTypeSafeClientBuilderTest {
         // TODO: add a test for writer interceptors - possibly in systests
         //assertEquals(TestWriterInterceptor.getAndResetValue(), 1);
     }
+    
+    @Test
+    public void testInvokesPostOperationWithRegisteredFeature() throws Exception {
+        String inputBody = "input body will be removed";
+        String expectedResponseBody = TestMessageBodyReader.REPLACED_BODY;
+
+        InterfaceWithoutProvidersDefined api = new CxfTypeSafeClientBuilder()
+                .register(SomeFeature.class)
+                .property("microprofile.rest.client.disable.default.mapper", true)
+                .baseUrl(new URL("http://localhost/null"))
+                .build(InterfaceWithoutProvidersDefined.class);
+
+        Response response = api.executePost(inputBody);
+
+        String body = response.readEntity(String.class);
+
+        response.close();
+
+        assertEquals(expectedResponseBody, body);
+
+        assertEquals(TestClientResponseFilter.getAndResetValue(), 1);
+        assertEquals(TestClientRequestFilter.getAndResetValue(), 1);
+        assertEquals(TestReaderInterceptor.getAndResetValue(), 1);
+    }
 
     @Test(expected = NoSuchEntityException.class)
     public void testResponseExceptionMapper() throws Exception {
@@ -223,4 +249,20 @@ public class CxfTypeSafeClientBuilderTest {
     public void testProxyAddressNullHost() {
         RestClientBuilder.newBuilder().proxyAddress(null, 8080);
     }
+    
+    public static class SomeFeature implements Feature {
+        @Override
+        public boolean configure(FeatureContext context) {
+            context
+                .register(TestClientRequestFilter.class)
+                .register(TestClientResponseFilter.class)
+                .register(TestMessageBodyReader.class, 4999)
+                .register(TestMessageBodyWriter.class)
+                .register(TestParamConverterProvider.class)
+                .register(TestReaderInterceptor.class)
+                .register(TestWriterInterceptor.class)
+                .register(EchoClientReqFilter.class);
+            return true;
+        }
+    }
 }
diff --git a/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/MicroProfileClientFactoryBeanTest.java b/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/MicroProfileClientFactoryBeanTest.java
new file mode 100644
index 0000000..9951f82
--- /dev/null
+++ b/rt/rs/microprofile-client/src/test/java/org/apache/cxf/microprofile/client/MicroProfileClientFactoryBeanTest.java
@@ -0,0 +1,94 @@
+/**
+ * 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.microprofile.client;
+
+import java.util.List;
+
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.apache.cxf.jaxrs.client.spec.TLSConfiguration;
+import org.apache.cxf.microprofile.client.mock.MyClient;
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.eclipse.microprofile.rest.client.tck.providers.TestClientRequestFilter;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class MicroProfileClientFactoryBeanTest {
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testCreateClientEnabledFeature() throws Exception {
+        final MicroProfileClientConfigurableImpl<RestClientBuilder> configurable = 
+                new MicroProfileClientConfigurableImpl<>(RestClientBuilder.newBuilder());
+
+        final MicroProfileClientFactoryBean bean = new MicroProfileClientFactoryBean(configurable, 
+                "http://bar", MyClient.class, null, new TLSConfiguration());
+        
+        final SomeFeature feature = new SomeFeature(true);
+        bean.setProvider(feature);
+
+        assertTrue(bean.create() instanceof MyClient);
+        assertTrue(configurable.getConfiguration().isRegistered(SomeFeature.class));
+        assertTrue(configurable.getConfiguration().isRegistered(TestClientRequestFilter.class));
+        assertTrue(configurable.getConfiguration().isEnabled(SomeFeature.class));
+        
+        assertThat((List<Object>)bean.getProviders(), hasItem(instanceOf(TestClientRequestFilter.class)));
+    }
+    
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testCreateClientDisabledFeature() throws Exception {
+        final MicroProfileClientConfigurableImpl<RestClientBuilder> configurable = 
+                new MicroProfileClientConfigurableImpl<>(RestClientBuilder.newBuilder());
+
+        final MicroProfileClientFactoryBean bean = new MicroProfileClientFactoryBean(configurable, 
+                "http://bar", MyClient.class, null, new TLSConfiguration());
+        
+        final SomeFeature feature = new SomeFeature(false);
+        bean.setProvider(feature);
+
+        assertTrue(bean.create() instanceof MyClient);
+        assertTrue(configurable.getConfiguration().isRegistered(SomeFeature.class));
+        assertTrue(configurable.getConfiguration().isRegistered(TestClientRequestFilter.class));
+        assertFalse(configurable.getConfiguration().isEnabled(SomeFeature.class));
+        
+        assertThat((List<Object>)bean.getProviders(), hasItem(instanceOf(TestClientRequestFilter.class)));
+    }
+    
+    public static class SomeFeature implements Feature {
+        private final boolean enabled;
+        
+        public SomeFeature(boolean enabled) {
+            this.enabled = enabled;
+        }
+        
+        @Override
+        public boolean configure(FeatureContext context) {
+            context.register(TestClientRequestFilter.class);
+            return enabled;
+        }
+    }
+}