Compare commits

..

18 Commits

Author SHA1 Message Date
e074296de7 Merge branch 'develop' into develop-jersey 2025-03-03 09:07:40 -05:00
3bcec23ddd updated jersey comments/logging 2025-03-03 09:06:52 -05:00
2c15ce03a2 jersey v3.1.10 2025-02-28 11:25:34 -05:00
093936aabb Merge branch 'develop' into develop-jersey 2025-02-28 11:22:17 -05:00
3f34ddb59d v3.x; jersey v3.x 2024-08-15 11:51:22 -04:00
255d64728e Merge branch 'develop' into develop-jersey 2024-03-05 18:47:23 -05:00
c61f2ca107 removing spring annotations to support multiple clients 2024-03-05 18:45:46 -05:00
f1852afe4e Merge branch 'develop' into develop-jersey 2024-01-23 10:43:58 -05:00
d386f35258 Merge branch 'develop' into develop-jersey 2023-06-29 10:31:57 -04:00
bb20a78224 Merge branch 'develop' into develop-jersey 2023-06-12 17:16:14 -04:00
053b7a36ff removing javax.annotation conflict 2023-06-12 16:08:32 -04:00
fa96a8cfb8 Merge branch 'develop' into develop-jersey 2023-06-12 16:07:35 -04:00
2cbb08e9cc Merge branch 'develop' into develop-jersey 2023-05-30 17:58:54 -04:00
6844044395 Merge branch 'develop' into develop-jersey 2023-05-30 17:54:44 -04:00
efcfab1795 Merge branch 'develop' into develop-jersey 2023-05-30 12:44:04 -04:00
eb843c3cb6 upgraded jersey version 2023-05-29 10:07:46 -04:00
56aca63307 Merge branch 'develop' into develop-jersey 2023-05-29 09:58:48 -04:00
df402790b4 added Jersey impl 2022-10-02 17:50:48 -04:00
7 changed files with 146 additions and 325 deletions

View File

@@ -1,7 +1,7 @@
# Common ReST Client Library # Common ReST Client Library
This project provides a library for Spring and POJO-based REST client instantiation. It includes special classes with classifiers for two popular JAXRS-based client frameworks: Apache CXF and Jersey. This project provides a library for Spring and POJO-based REST client instantiation. It includes special classes for the Jersey JAXRS-based client frameworks.
## Usage ## Usage
@@ -15,8 +15,7 @@ First, you will need to include the library in your project.
<dependency> <dependency>
<groupId>com.inteligr8</groupId> <groupId>com.inteligr8</groupId>
<artifactId>common-rest-client</artifactId> <artifactId>common-rest-client</artifactId>
<classifier>...</classifier> <version>...-jersey</version>
<version>...</version>
</dependency> </dependency>
... ...
</dependencies> </dependencies>
@@ -24,8 +23,6 @@ First, you will need to include the library in your project.
</project> </project>
``` ```
Valid `classifier` values are `cxf` or `jersey`.
### Spring Framework ### Spring Framework
#### Single Client #### Single Client
@@ -36,29 +33,27 @@ If you will only be declaring a single client in your Spring context, this is ea
@Component @Component
public class ... { public class ... {
@Autowired @Autowired
@Qualifier("client.cxf") // may be optional @Qualifier("client.jersey") // may be optional
private Client client; private Client client;
} }
``` ```
Next, you need to configure that client. You can do that by providing a single implementation of the `ClientConfiguration` (or `ClientCxfConfiguration`) interface. Next, you need to configure that client. You can do that by providing a single implementation of the `ClientConfiguration` (or `ClientJerseyConfiguration`) interface.
```java ```java
@Configuration @Configuration
public class ... implements ClientCxfConfiguration { public class ... implements ClientJerseyConfiguration {
... ...
} }
``` ```
For Jersey implementations, just use `client.jersey` and `ClientJerseyConfiguration`. If you want to provide one of each, then follow the instructions for multiple clients below.
#### Multiple Clients #### Multiple Clients
If you will or may have multiple clients in your Spring context, there is an extra step. You will still need to define a `ClientConfiguration` for each. On top of that, you will need to create specialized implementations of each client. That special implementation will reference the configuration directly. An example is below. If you will or may have multiple clients in your Spring context, there is an extra step. You will still need to define a `ClientConfiguration` for each. On top of that, you will need to create specialized implementations of each client. That special implementation will reference the configuration directly. An example is below.
```java ```java
@Component("my.client") @Component("my.client")
public class MyClient extends ClientCxfImpl { public class MyClient extends ClientJerseyImpl {
@Autowired @Autowired
public MyClient(MyClientConfiguration config) { public MyClient(MyClientConfiguration config) {
super(config); super(config);
@@ -87,6 +82,6 @@ You do not have to use the Spring framework to use these classes. You can insta
```java ```java
MyClientConfiguration config = new MyClientConfiguration(); MyClientConfiguration config = new MyClientConfiguration();
... ...
ClientCxfImpl client = new ClientCxfImpl(config); ClientJerseyImpl client = new ClientJerseyImpl(config);
MyJaxRsApi api = client.getApi(MyJaxRsApi.class); MyJaxRsApi api = client.getApi(MyJaxRsApi.class);
``` ```

40
pom.xml
View File

@@ -6,11 +6,11 @@
<groupId>com.inteligr8</groupId> <groupId>com.inteligr8</groupId>
<artifactId>common-rest-client</artifactId> <artifactId>common-rest-client</artifactId>
<version>3.0.3-cxf</version> <version>3.0-SNAPSHOT-jersey</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>ReST API Client for Java</name> <name>ReST API Client for Java</name>
<description>A common library for building CXF REST API clients</description> <description>A common library for building Jersey REST API clients</description>
<url>https://bitbucket.org/inteligr8/common-rest-client</url> <url>https://bitbucket.org/inteligr8/common-rest-client</url>
<licenses> <licenses>
@@ -46,7 +46,7 @@
<junit.version>5.12.0</junit.version> <junit.version>5.12.0</junit.version>
<spring.version>6.0.23</spring.version> <spring.version>6.0.23</spring.version>
<jackson.version>2.17.3</jackson.version> <jackson.version>2.17.3</jackson.version>
<cxf.version>4.0.6</cxf.version> <jersey.version>3.1.10</jersey.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -109,11 +109,33 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- Apache CXF libraries --> <!-- Jersey libraries -->
<dependency> <dependency>
<groupId>org.apache.cxf</groupId> <groupId>org.glassfish.jersey.ext</groupId>
<artifactId>cxf-rt-rs-client</artifactId> <artifactId>jersey-proxy-client</artifactId>
<version>${cxf.version}</version> <version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -129,7 +151,7 @@
<goals><goal>add-source</goal></goals> <goals><goal>add-source</goal></goals>
<configuration> <configuration>
<sources> <sources>
<source>src/main/cxf</source> <source>src/main/jersey</source>
</sources> </sources>
</configuration> </configuration>
</execution> </execution>
@@ -138,7 +160,7 @@
<goals><goal>add-test-source</goal></goals> <goals><goal>add-test-source</goal></goals>
<configuration> <configuration>
<sources> <sources>
<source>src/test/cxf</source> <source>src/test/jersey</source>
</sources> </sources>
</configuration> </configuration>
</execution> </execution>

View File

@@ -1,181 +0,0 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.rs;
import java.util.LinkedList;
import java.util.List;
import jakarta.annotation.PostConstruct;
import jakarta.ws.rs.ext.RuntimeDelegate;
import org.apache.cxf.BusFactory;
import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.jaxrs.impl.RuntimeDelegateImpl;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
/**
* A class that provides pre-configured JAX-RS Client &amp; WebTarget &amp;
* CXF WebClient objects.
*
* @author brian@inteligr8.com
*/
public class ClientCxfImpl extends Client {
private final Logger logger = LoggerFactory.getLogger(ClientCxfImpl.class);
private final Object sync = new Object();
private ClientCxfConfiguration config;
private WebClient client;
/**
* This constructor is for Spring or POJO use.
* @param config The client configuration.
*/
public ClientCxfImpl(ClientCxfConfiguration config) {
this.config = config;
}
/**
* This method registers the Apache CXF library as the default provider for
* the JAX-RS specification.
*/
@PostConstruct
public void register() {
if (RuntimeDelegate.getInstance() == null) {
this.logger.info("Setting JAX-RS runtime delegate to the CXF library");
RuntimeDelegate.setInstance(new RuntimeDelegateImpl());
} else if (RuntimeDelegate.getInstance() instanceof RuntimeDelegateImpl) {
this.logger.info("JAX-RS runtime delegate already the CXF library");
} else {
this.logger.warn("Setting JAX-RS runtime delegate to the CXF library; was: " + RuntimeDelegate.getInstance().getClass().getName());
RuntimeDelegate.setInstance(new RuntimeDelegateImpl());
}
if (this.logger.isInfoEnabled())
this.logger.info("API Base URL: " + this.getConfig().getBaseUrl());
}
/**
* @return A CXF client (not JAX-RS).
*/
public WebClient getCxfClient() {
synchronized (this.sync) {
if (this.client == null)
this.client = this.buildCxfClient(null);
}
return this.client;
}
/**
* @param authFilter A dynamic authorization filter.
* @return A pre-configured CXF client (no URL) with the specified authorization.
*/
public WebClient getCxfClient(AuthorizationFilter authFilter) {
if (authFilter == null) {
return this.getCxfClient();
} else {
return this.buildCxfClient(authFilter);
}
}
/**
* @param authFilter A post-configuration authorization filter.
* @return A CXF client (not JAX-RS).
*/
public WebClient buildCxfClient(AuthorizationFilter authFilter) {
ObjectMapper om = new ObjectMapper();
om.registerModules(new JavaTimeModule());
this.getConfig().configureJacksonMapper(om);
JacksonJsonProvider jacksonProvider = new JacksonJsonProvider(om, JacksonJsonProvider.BASIC_ANNOTATIONS);
this.getConfig().configureJacksonProvider(jacksonProvider);
List<Object> providersAndFilters = new LinkedList<Object>();
providersAndFilters.add(jacksonProvider);
providersAndFilters.add(new CxfLoggingFilter());
providersAndFilters.add(new CxfMultipartProvider());
if (authFilter == null)
authFilter = this.getConfig().createAuthorizationFilter();
if (authFilter != null)
providersAndFilters.add(authFilter);
this.addProvidersAndFilters(providersAndFilters);
// we can't use JAXRSClientFactory with a JAXRS client (duh!)
// so we need to create a CXF client
WebClient client = WebClient.create(this.getConfig().getBaseUrl(), providersAndFilters);
if (this.getConfig().getConnectTimeoutInMillis() != null || this.getConfig().getResponseTimeoutInMillis() != null) {
HTTPConduit conduit = client.getConfiguration().getHttpConduit();
HTTPClientPolicy policy = conduit.getClient();
if (policy == null)
conduit.setClient(policy = new HTTPClientPolicy());
if (this.getConfig().getConnectTimeoutInMillis() != null)
policy.setConnectionTimeout(this.getConfig().getConnectTimeoutInMillis());
if (this.getConfig().getResponseTimeoutInMillis() != null)
policy.setReceiveTimeout(this.getConfig().getResponseTimeoutInMillis());
}
if (!this.getConfig().isDefaultBusEnabled()) {
// Some applications (like ACS) add interceptors to the default bus
// those interceptors may treat all messages as SOAP messages (like ACS), resulting in ClassCastExceptions
// we need to ignore the default bus
org.apache.cxf.jaxrs.client.ClientConfiguration config = WebClient.getConfig(client);
config.setBus(BusFactory.newInstance().createBus());
}
this.config.configureClient(client);
return client;
}
/**
* @param providersAndFilters A list of JAX-RS and CXF providers.
*/
public void addProvidersAndFilters(List<Object> providersAndFilters) {
// for extension purposes
}
/**
* @return The client configuration.
*/
public ClientCxfConfiguration getConfig() {
return this.config;
}
/**
* This method retrieves a JAX-RS implementation of the specified API with
* the specified authorization.
*
* @param authFilter A dynamic authorization filter.
* @param apiClass A JAX-RS annotation API class.
* @return An instance of the API class.
*/
@Override
public <T> T getApi(AuthorizationFilter authFilter, Class<T> apiClass) {
return JAXRSClientFactory.fromClient(this.getCxfClient(authFilter), apiClass);
}
}

View File

@@ -1,54 +0,0 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.rs;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.core.MediaType;
import org.apache.cxf.jaxrs.ext.multipart.Attachment;
import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
import org.slf4j.Logger;
/**
* This is a CXF specific handling of the logging of multipart requests, which
* would otherwise be ignored by the base LoggingFilter. It is meant to be
* used for debugging purposes. When used, it will write to 'jaxrs.request' and
* 'jaxrs.response' loggers at the 'trace' level.
*
* @author brian@inteligr8.com
*/
public class CxfLoggingFilter extends LoggingFilter {
@Override
protected void logUnhandledRequest(ClientRequestContext requestContext, Logger logger) throws IOException {
if (MediaType.MULTIPART_FORM_DATA_TYPE.equals(requestContext.getMediaType())) {
if (requestContext.getEntity() instanceof MultipartBody) {
List<String> attIds = new LinkedList<>();
for (Attachment att : ((MultipartBody)requestContext.getEntity()).getAllAttachments())
attIds.add(att.getContentId());
logger.trace("request: {} {}: {}", requestContext.getMethod(), requestContext.getUri(), attIds);
} else {
logger.trace("request: {} {}: failed to output form", requestContext.getMethod(), requestContext.getUri());
}
} else {
super.logUnhandledRequest(requestContext, logger);
}
}
}

View File

@@ -1,49 +0,0 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.rs;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.ext.Provider;
import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
import org.apache.cxf.jaxrs.provider.MultipartProvider;
/**
* This implements a JAX-RS provider that adds support for the handling of CXF
* MultipartBody.
*
* @author brian@inteligr8.com
*/
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.MULTIPART_FORM_DATA)
@Provider
public class CxfMultipartProvider extends MultipartProvider {
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return MultipartBody.class.isAssignableFrom(type) || super.isReadable(type, genericType, annotations, mediaType);
}
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return MultipartBody.class.isAssignableFrom(type) || super.isWriteable(type, genericType, annotations, mediaType);
}
}

View File

@@ -14,35 +14,22 @@
*/ */
package com.inteligr8.rs; package com.inteligr8.rs;
import org.apache.cxf.jaxrs.client.WebClient;
/** /**
* This interface defines additional configurations specific to the Apache CXF * This interface defines additional configurations specific to the Jersey
* JAX-RS library and its nuances. * Jakarta RS library and its nuances.
* *
* @author brian@inteligr8.com * @author brian@inteligr8.com
*/ */
public interface ClientCxfConfiguration extends ClientConfiguration { public interface ClientJerseyConfiguration extends ClientConfiguration {
/** /**
* Apache CXF uses a global bus configuration where interceptors could * Jersey is automatically strict in its adherence to the ReST API
* wreck havoc on your implementation. This method allows you to * specifications. It requires a body to PUT calls by default.
* explicitly by-pass the default bus.
* *
* See https://cxf.apache.org/docs/bus-configuration.html. * @return true to require body in PUT calls; false to make it optional
*
* @return true to use the default bus; false otherwise.
*/ */
default boolean isDefaultBusEnabled() { default boolean isPutBodyRequired() {
return true; return true;
} }
/**
* A Jackson provider, logging filter, and authentication filter are already registered.
*
* @param client A CXF client to configure.
*/
default void configureClient(WebClient client) {
}
} }

View File

@@ -0,0 +1,101 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.rs;
import jakarta.annotation.PostConstruct;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.ext.RuntimeDelegate;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.proxy.WebResourceFactory;
import org.glassfish.jersey.internal.RuntimeDelegateImpl;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class that provides pre-configured JAX-RS Client &amp; WebTarget objects
* for Jersey.
*
* @author brian@inteligr8.com
*/
public class ClientJerseyImpl extends Client {
private final Logger logger = LoggerFactory.getLogger(ClientJerseyImpl.class);
private ClientJerseyConfiguration config;
/**
* This constructor is for Spring or POJO use.
* @param config The client configuration.
*/
public ClientJerseyImpl(ClientJerseyConfiguration config) {
this.config = config;
}
/**
* This method registers the Jersey library as the default provider for the
* Jakarta RS specification.
*/
@PostConstruct
public void register() {
if (RuntimeDelegate.getInstance() == null) {
this.logger.info("Setting Jakarta RS runtime delegate to the Jersey library");
RuntimeDelegate.setInstance(new RuntimeDelegateImpl());
} else if (RuntimeDelegate.getInstance() instanceof RuntimeDelegateImpl) {
this.logger.info("Jakarta RS runtime delegate already the Jersey library");
} else {
this.logger.warn("Setting Jakarta RS runtime delegate to the Jersey library; was: " + RuntimeDelegate.getInstance().getClass().getName());
RuntimeDelegate.setInstance(new RuntimeDelegateImpl());
}
if (this.logger.isInfoEnabled())
this.logger.info("API Base URL: " + this.getConfig().getBaseUrl());
}
/**
* @param clientBuilder A client builder.
*/
@Override
public void buildClient(ClientBuilder clientBuilder) {
clientBuilder.register(MultiPartFeature.class);
if (!this.getConfig().isPutBodyRequired()) {
// allow PUT operations without body data
clientBuilder.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
}
}
/**
* @return The client configuration.
*/
public ClientJerseyConfiguration getConfig() {
return this.config;
}
/**
* This method retrieves a Jakarta RS implementation of the specified API
* with the specified authorization.
*
* @param authFilter A dynamic authorization filter.
* @param apiClass A Jakarta RS annotation API class.
* @return An instance of the API class.
*/
@Override
public <T> T getApi(AuthorizationFilter authFilter, Class<T> apiClass) {
return WebResourceFactory.newResource(apiClass, this.getTarget(authFilter));
}
}