Hi Jean,

 
Yes, I believe that call to:
 
      SodexoApi clientProxy = 
JAXRSClientFactory.fromClient(WebClient.client(getClientProxy()), 
SodexoApi.class);

is not needed (since as you rightly pointed out, SodexoApi is already thread 
safe proxy). Folks, correct us
here if that is not the case. 

Thank you.

Best Regards,
    Andriy Redko
 

> Hi Andriy,
> Ok, it is clear for the 1st part, which I restructured to:
>         private static SodexoApi getThreadsafeProxy(String baseAddress) 
> throws GeneralSecurityException {
>                 JacksonJsonProvider provider = new JacksonJsonProvider(new 
> CustomObjectMapper());
>                 List<JacksonJsonProvider> providers = new 
> ArrayList<JacksonJsonProvider>();
>                 providers.add(provider);
>                
>                 final JAXRSClientFactoryBean factory = new 
> JAXRSClientFactoryBean();
>                 factory.setAddress(baseAddress);
>                 factory.setServiceClass(SodexoApi.class);
>                 factory.setProviders(providers);
>                 factory.getOutInterceptors().add(new LoggingOutInterceptor());
>                 factory.getInInterceptors().add(new LoggingInInterceptor());
>                 factory.setThreadSafe(true);
>                 SodexoApi api = factory.create(SodexoApi.class);
>                 ClientConfiguration config = WebClient.getConfig(api);
>                 addTLSClientParameters(config.getHttpConduit());
>                 return api;
>         }
> You’re less clear on the 2nd part. If I look at the example 
> http://svn.apache.org/repos/asf/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultithreadedClientTest.java
>  (especially the runproxies() method),
> then in my service method (which I am trying to make threadsafe) I wouldn’t 
> need to call:
>                 //Get a threadsafe API client
>                 SodexoApi clientProxy = 
> JAXRSClientFactory.fromClient(WebClient.client(getClientProxy()), 
> SodexoApi.class);
> The getClientProxy() which returns a singleton instance from the 
> getThreadsafeProxy(…) method above is already threadsafe even though it is 
> the same proxy instance for all service invocations as state is kept in a 
> ThreadLocalClientState object.
> So my service method could just look like (still need a casting to WebClient 
> to be able to set the authorization HTTP header):
>         public synchronized Response closeAccount(UUID operationId, Long 
> authorisationId, AccountKey accountKey) {
>                 try {
>                         //Set the Bearer authorization header
>                         String issuer = 
> Configuration.getInstance().getItem(EmittentProperties.emit_security_bearer_issuer);
>                         String audience = 
> Configuration.getInstance().getItem(EmittentProperties.emit_security_bearer_audience);
>                         String jws = 
> BearerUtils.createJwtSignedJose(BearerUtils.SDX_HEADER_TYPE,issuer,audience, 
> operationId.toString(),(PrivateKey) 
> BearerUtils.getKey(KeyName.kmopSignPrivKey));
>                         WebClient.client(getClientProxy())
>                                         .reset()
>                                         .header(HttpHeaders.AUTHORIZATION, 
> "Bearer " + jws);
>                         //Send service request
>                         return 
> getClientProxy().closeAccount(operationId.toString(), authorisationId, 
> accountKey);
>                 } catch (GeneralSecurityException e) {
>                         StringBuilder bldr = new StringBuilder("Internal 
> error processing customerFund request (")
>                                         
> .append("operationId=").append(operationId.toString())
>                                         
> .append(",authorisationId=").append(authorisationId)
>                                         .append("): ").append(e.getMessage());
>                         LOG.error(bldr.toString(), e);
>                         return 
> Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
>                 }
>         }
> Regards,
> J.P. 

> -----Original Message-----
> From: Andriy Redko <[email protected]>
> Sent: woensdag 28 juni 2023 23:55
> To: Jean Pierre URKENS <[email protected]>; CXF Dev List 
> <[email protected]>
> Subject: Re: Apache CXF JAX-RS threadsafe clients
> Hi Jean,
> So the 1st part is 100% correct way to create a thread safe client proxy, but 
> with the 2nd one we have an issue well documented here [1]. Since you only 
> modify headers, the thread safe proxy should work, but you probably could 
> avoid using the WebClient part (just use JAXRSClientFactoryBean)
> directly:
>    final JAXRSClientFactoryBean factory = new JAXRSClientFactoryBean();
>    factory.setServiceClass(SodexoApi.class);
>    factory.setProviders(providers);
>    factory.getOutInterceptors().add(new LoggingOutInterceptor());
>    factory.getInInterceptors().add(new LoggingInInterceptor());
>    factory.setThreadSafe(true)
>    SodexoApi api = factory.create(SodexoApi.class);
>    ClientConfiguration config = WebClient.getConfig(api);
>    addTLSClientParameters(config.getHttpConduit()); 

> Hope it helps!
> [1] 
> https://cxf.apache.org/docs/jax-rs-client-api.html#JAXRSClientAPI-ThreadSafety
>  
> Best Regards,
>     Andriy Redko 

JPU>>  Apache CXF JAX-RS threadsafe clients
JPU>> Hi Andriy,
JPU>> I am struggling to understand threadsafety when creating/using
JPU>> JAX-RS clients.
JPU>> My intention is to:
JPU>> 1.      Create a client once as I think it is heavy loaded:
JPU>> o       We need to add providers and interceptors
JPU>> o       We need to set TLS-client parameters
JPU>> 2.      Then (re-)use this client in a threadsafe way for multiple
JPU>> (possibly concurrent) invocations of service methods
JPU>> So step ‘1.’ I have done by creating a static  threadsafe proxy
JPU>> using the JAXRSClientFactory class.
JPU>> Now the problem is step ‘2.’ How do I invoke this (static) client
JPU>> to make sure that:
JPU>> ·       Stuff under’1.’ is reused
JPU>> ·       Reset and add headers specific for this invocation
JPU>> ·       all is threadsafe
JPU>> Currently, in my service method I create a client as follows:
JPU>> SodexoApi* clientProxy* =
JPU>> JAXRSClientFactory.*fromClient*(WebClient.*client*
JPU>> (*getClientProxy*()), SodexoApi.*class*);
JPU>> Here getClientProxy() is the static proxy I created, covering for step 
‘1.’
JPU>> Stuff, that I want to re-use to create a proxy for the SodexoApi 
interface.
JPU>> But I am doubting whether this is the correct way.
JPU>> For the moment I don’t think I’ve a problem due to the fact that
JPU>> the service methods are defined as ‘synchronized’ but that comes
JPU>> with a performance penalty.
JPU>> Below, a stripped version of the code I am using, to help you
JPU>> understanding what I am trying to do:
JPU>> public class SodexoApiClientImpl {
JPU>>         private static final Logger LOG =
JPU>> Logger.getLogger(SodexoApiClientImpl.class); 


JPU>>         /** custom HEADER field name for X-KMO-OPERATION-ID */
JPU>>         public static final String HEADER_OPERATION_ID =
JPU>> "X-KMO-OPERATION-ID";
JPU>>         /** A threadsafe proxy to serve as {@link WebClient} for
JPU>> the {@link SodexoApi} interface*/
JPU>>         private static SodexoApi clientProxy;
JPU>>         protected static synchronized SodexoApi getClientProxy()
JPU>> throws GeneralSecurityException {
JPU>>                 if (clientProxy == null) {
JPU>>                         //Get the base address of the service
JPU>> endpoint
JPU>>                         String baseAddress =
JPU>> Configuration.getInstance().getItem(EmittentProperties.emmitent_ser
JPU>> vice_endpoint);
JPU>>                         clientProxy =
JPU>> getThreadsafeProxy(baseAddress);
JPU>>                 }
JPU>>                 return clientProxy;
JPU>>         }
JPU>>         /**
JPU>>          * Create a proxy for the {@link SodexoApi} service
JPU>> endpoint
JPU>>          *
JPU>>          * @param baseAddress - The base URI of the SDX REST API endpoint.
JPU>>          * @return The {@link SodexoApi} service endpoint proxy
JPU>>          *
JPU>>          * @throws GeneralSecurityException - when retrieving
JPU>> certificate info fails
JPU>>          */
JPU>>         private static SodexoApi getThreadsafeProxy(String
JPU>> baseAddress) throws GeneralSecurityException {
JPU>>                 JacksonJsonProvider provider = new
JPU>> JacksonJsonProvider(new CustomObjectMapper());
JPU>>                 List<JacksonJsonProvider> providers = new
JPU>> ArrayList<JacksonJsonProvider>();
JPU>>                 providers.add(provider);
JPU>>                 SodexoApi api =
JPU>> JAXRSClientFactory.create(baseAddress,
JPU>> SodexoApi.class, providers,true);
JPU>>                 Client client = WebClient.client(api);
JPU>>                 ClientConfiguration config =
JPU>> WebClient.getConfig(client);
JPU>>                 config.getOutInterceptors().add(new
JPU>> LoggingOutInterceptor());
JPU>>                 config.getInInterceptors().add(new
JPU>> LoggingInInterceptor());
JPU>>                 addTLSClientParameters(config.getHttpConduit());
JPU>>                 return api;
JPU>>         }
JPU>>         /**
JPU>>          * Add {@link TLSClientParameters} to the {@link HTTPConduit}.
JPU>>          * <p>In the Devoteam test environement the SSL
JPU>> certificates of both KMOP and KmopEmittent
JPU>>          * are self-signed with a CN not referring to their hostname.
JPU>> Therefore in these environments
JPU>>          * the check to verifiy whether the hostname matches the CN
JPU>> must be disabled.</p>
JPU>>          *
JPU>>          * @param conduit The {@link HTTPConduit} handling the
JPU>> https transport protocol.
JPU>>          *
JPU>>          * @throws GeneralSecurityException - when retrieving
JPU>> certificate info fails
JPU>>          */
JPU>>         private static void addTLSClientParameters(HTTPConduit
JPU>> conduit) throws GeneralSecurityException {
JPU>>                 TLSClientParameters params =
JPU>> conduit.getTlsClientParameters();
JPU>>                 if (params == null) {
JPU>>                         params = new TLSClientParameters();
JPU>>                         conduit.setTlsClientParameters(params);
JPU>>                 }
JPU>>                 //1.0 set the trust Manager (the server certificate
JPU>> should be included in the default ca-certs trust keystore
JPU>>                 X509TrustManager tm =
JPU>> TrustManagerUtils.getValidateServerCertificateTrustManager();
JPU>>                 params.setTrustManagers(new TrustManager[] { tm }); 


JPU>>                 //2.0 in case of m-TLS set the keyManager if
JPU>> required
JPU>>                 try {
JPU>>                         final String keystoreType =
JPU>> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopK
JPU>> eystoreType);
JPU>>                         final String keystore =
JPU>> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopE
JPU>> ncKeystore);
JPU>>                         final String keystorePass =
JPU>> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopE
JPU>> ncKeystorePass);
JPU>>                         final String keyAlias =
JPU>> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopE
JPU>> ncKeyAlias);
JPU>>                         final String keyPass =
JPU>> Configuration.getInstance().getItem(KmoPortefeuilleProperties.kmopE
JPU>> ncKeyPass);
JPU>>                         KeyStore ks =
JPU>> KeystoreUtils.loadKS(keystoreType,
JPU>> keystore, keystorePass);
JPU>>                         KeyManager km =
JPU>> KeyManagerUtils.createClientKeyManager(ks, keyAlias, keyPass);
JPU>>                         params.setKeyManagers(new KeyManager[]
JPU>> {km});
JPU>>                 } catch (PropertyNotFoundException pe) {
JPU>>                         //if any of the properties do not exist
JPU>> then either m-TLS does not apply or there is a property
JPU>> misconfiguration
JPU>>                         LOG.warn("Couldn't configure KeyManagers
JPU>> for the client! This either indicates that m-TLS doesnt' apply or a
JPU>> property misconifguration.");
JPU>>                         LOG.warn(pe.getMessage(),pe);
JPU>>                 } catch ( GeneralSecurityException e) {
JPU>>                         LOG.error("Couldn't configure KeyManagers
JPU>> on the HTTPConduit of the JAX-RS webclient: "+e.getMessage(),e);
JPU>>                         throw e;
JPU>>                 }
JPU>>         }
JPU>>         /**
JPU>>          * Sends a CloseAccount service request to the peer
JPU>> endpoint service.
JPU>>          *
JPU>>          * @param operationId - The unique {@link UUID identifier}
JPU>> for the request message
JPU>>          * @param authorisationId Long - The unique identifier of
JPU>> the account at the peer side
JPU>>          * @param accountKey The {@link AccountKey} request entity
JPU>>          * @return An {@link Response} response object.
JPU>>          */
JPU>>         public synchronized Response closeAccount(UUID operationId,
JPU>> Long authorisationId, AccountKey accountKey) {
JPU>>                 try {
JPU>>                         //Get a threadsafe SodexoAPI client proxy
JPU>> instance
JPU>>                        * //UJ: Is this the correct way?*
JPU>>                         SodexoApi clientProxy =
JPU>> JAXRSClientFactory.fromClient(WebClient.client(getClientProxy()),
JPU>> SodexoApi.class); 


JPU>>                         //Set the Bearer authorization header
JPU>>                         String issuer =
JPU>> Configuration.getInstance().getItem(EmittentProperties.emit_securit
JPU>> y_bearer_issuer);
JPU>>                         String audience =
JPU>> Configuration.getInstance().getItem(EmittentProperties.emit_securit
JPU>> y_bearer_audience);
JPU>>                         String jws =
JPU>> BearerUtils.createJwtSignedJose(BearerUtils.SDX_HEADER_TYPE,issuer,
JPU>> audience,
JPU>> operationId.toString(),(PrivateKey)
JPU>> BearerUtils.getKey(KeyName.kmopSignPrivKey));
JPU>>                         WebClient.client(clientProxy)
JPU>>                                         .reset()
JPU>>                                        
JPU>> .header(HttpHeaders.AUTHORIZATION,
JPU>> "Bearer " + jws);
JPU>>                         return
JPU>> clientProxy.closeAccount(operationId.toString(), authorisationId,
JPU>> accountKey);
JPU>>                 } catch (GeneralSecurityException e) {
JPU>>                         StringBuilder bldr = new
JPU>> StringBuilder("Internal error processing customerFund request (") 

JPU>> .append("operationId=").append(operationId.toString()) 

JPU>> .append(",authorisationId=").append(authorisationId)
JPU>>                                         .append("):
JPU>> ").append(e.getMessage());
JPU>>                         LOG.error(bldr.toString(), e);
JPU>>                         return
JPU>> Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()
JPU>> ).build();
JPU>>                 }
JPU>>         }
JPU>> }

Reply via email to