Compare commits
33 Commits
v2.0.1-jer
...
v3.0.0
Author | SHA1 | Date | |
---|---|---|---|
eb7ac34f5e | |||
b7ac0d25c9 | |||
271016dcf9 | |||
00ec8c972d | |||
2cd9cc9484 | |||
d4cd2f248c | |||
ac6f82a7f4 | |||
eb654c5488 | |||
0258cfcb9a | |||
6fc43b85f7 | |||
d29ccbef79 | |||
512f118354 | |||
21255bfb3d | |||
f84279a148 | |||
f10fd39e97 | |||
eaed628f00 | |||
44e82b3a6b | |||
ab920aedd7 | |||
ab6b0d8da7 | |||
c25988fc8f | |||
cd278bcc81 | |||
b64c408029 | |||
9a9a958913 | |||
37e8b63179 | |||
255a9c8e85 | |||
ec6e7cc15d | |||
2451c634b7 | |||
bcb7d4a7ed | |||
ed00346e71 | |||
c001576b65 | |||
8b13f06667 | |||
964cec0fb5 | |||
fe2e04f3af |
67
README.md
67
README.md
@@ -1,7 +1,7 @@
|
||||
|
||||
# Common ReST Client Library
|
||||
|
||||
This project provides a library for Spring and POJO-based REST client instantiation. It includes special classes for the Jersey JAXRS-based client frameworks.
|
||||
This project provides a library for Spring and POJO-based REST client instantiation.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -15,7 +15,7 @@ First, you will need to include the library in your project.
|
||||
<dependency>
|
||||
<groupId>com.inteligr8</groupId>
|
||||
<artifactId>common-rest-client</artifactId>
|
||||
<version>...-jersey</version>
|
||||
<version>...</version>
|
||||
</dependency>
|
||||
...
|
||||
</dependencies>
|
||||
@@ -23,65 +23,4 @@ First, you will need to include the library in your project.
|
||||
</project>
|
||||
```
|
||||
|
||||
### Spring Framework
|
||||
|
||||
#### Single Client
|
||||
|
||||
If you will only be declaring a single client in your Spring context, this is easy. You will just need two things. First, inject the single client into any of your Spring beans. You may do inject it into more than one. An example is below:
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class ... {
|
||||
@Autowired
|
||||
@Qualifier("client.jersey") // may be optional
|
||||
private Client client;
|
||||
}
|
||||
```
|
||||
|
||||
Next, you need to configure that client. You can do that by providing a single implementation of the `ClientConfiguration` (or `ClientJerseyConfiguration`) interface.
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class ... implements ClientJerseyConfiguration {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
```java
|
||||
@Component("my.client")
|
||||
public class MyClient extends ClientJerseyImpl {
|
||||
@Autowired
|
||||
public MyClient(MyClientConfiguration config) {
|
||||
super(config);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can then inject your client(s) into your Spring beans. Like the example below:
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class ... {
|
||||
@Autowired
|
||||
private MyClient client;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
MyJaxRsApi api = this.client.getApi(MyJaxRsApi.class);
|
||||
}
|
||||
}
|
||||
|
||||
### POJO
|
||||
|
||||
You do not have to use the Spring framework to use these classes. You can instantiate them directly. But you wil still need to create a `ClientConfiguration` as mentioned above.
|
||||
|
||||
```java
|
||||
MyClientConfiguration config = new MyClientConfiguration();
|
||||
...
|
||||
ClientJerseyImpl client = new ClientJerseyImpl(config);
|
||||
MyJaxRsApi api = client.getApi(MyJaxRsApi.class);
|
||||
```
|
||||
See the `cxf` and `jersey` branches for examples and more documentation.
|
||||
|
144
pom.xml
144
pom.xml
@@ -6,11 +6,11 @@
|
||||
|
||||
<groupId>com.inteligr8</groupId>
|
||||
<artifactId>common-rest-client</artifactId>
|
||||
<version>2.0.1-jersey</version>
|
||||
<version>3.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ReST API Client for Java</name>
|
||||
<description>A common library for building Jersey REST API clients</description>
|
||||
<description>A common library for building REST API clients</description>
|
||||
<url>https://bitbucket.org/inteligr8/common-rest-client</url>
|
||||
|
||||
<licenses>
|
||||
@@ -40,15 +40,20 @@
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
|
||||
<junit.version>5.7.2</junit.version>
|
||||
<spring.version>5.2.14.RELEASE</spring.version>
|
||||
<jersey.version>2.35</jersey.version>
|
||||
<junit.version>5.10.0</junit.version>
|
||||
<spring.version>6.0.19</spring.version>
|
||||
<jackson.version>2.17.2</jackson.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>jakarta.annotation</groupId>
|
||||
<artifactId>jakarta.annotation-api</artifactId>
|
||||
<version>2.1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
@@ -57,27 +62,32 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.12.2</version>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.jaxrs</groupId>
|
||||
<artifactId>jackson-jaxrs-json-provider</artifactId>
|
||||
<version>2.12.2</version>
|
||||
<groupId>com.fasterxml.jackson.jakarta.rs</groupId>
|
||||
<artifactId>jackson-jakarta-rs-json-provider</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.12.2</version>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.14.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.29</version>
|
||||
<version>2.0.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.ws.rs</groupId>
|
||||
<artifactId>jakarta.ws.rs-api</artifactId>
|
||||
<version>2.1.6</version>
|
||||
<version>3.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
@@ -94,70 +104,16 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.9</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Jersey libraries -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.ext</groupId>
|
||||
<artifactId>jersey-proxy-client</artifactId>
|
||||
<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>
|
||||
<version>4.5.14</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>add-jaxrs-src</id>
|
||||
<goals><goal>add-source</goal></goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>src/main/jersey</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>add-test-src</id>
|
||||
<goals><goal>add-test-source</goal></goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>src/test/jersey</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<version>3.1.0</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
@@ -168,7 +124,7 @@
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<version>3.1.0</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
@@ -177,29 +133,6 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>source</id>
|
||||
<phase>package</phase>
|
||||
<goals><goal>jar-no-fork</goal></goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>javadoc</id>
|
||||
<phase>package</phase>
|
||||
<goals><goal>jar</goal></goals>
|
||||
<configuration>
|
||||
<show>public</show>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -211,6 +144,29 @@
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>source</id>
|
||||
<phase>package</phase>
|
||||
<goals><goal>jar-no-fork</goal></goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>javadoc</id>
|
||||
<phase>package</phase>
|
||||
<goals><goal>jar</goal></goals>
|
||||
<configuration>
|
||||
<show>public</show>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<executions>
|
||||
|
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
package com.inteligr8.rs;
|
||||
|
||||
import javax.ws.rs.client.ClientRequestFilter;
|
||||
import jakarta.ws.rs.client.ClientRequestFilter;
|
||||
|
||||
/**
|
||||
* This is a marker that allows the developer to segregate, restrict, or limit
|
||||
|
@@ -17,8 +17,10 @@ package com.inteligr8.rs;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.ws.rs.client.ClientRequestContext;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.client.ClientRequestContext;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* This class implements a simple 2-credential (username & password) based
|
||||
@@ -36,8 +38,8 @@ public class BasicAuthorizationFilter implements AuthorizationFilter {
|
||||
* @param password A password or secret key.
|
||||
*/
|
||||
public BasicAuthorizationFilter(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.username = StringUtils.trimToNull(username);
|
||||
this.password = StringUtils.trimToNull(password);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -14,8 +14,8 @@
|
||||
*/
|
||||
package com.inteligr8.rs;
|
||||
|
||||
import javax.ws.rs.client.ClientRequestContext;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.client.ClientRequestContext;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
|
||||
/**
|
||||
* This class implements a simple long living or proxied token-based
|
||||
|
@@ -14,13 +14,16 @@
|
||||
*/
|
||||
package com.inteligr8.rs;
|
||||
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
import javax.ws.rs.client.WebTarget;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jakarta.ws.rs.client.ClientBuilder;
|
||||
import jakarta.ws.rs.client.WebTarget;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
|
||||
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
|
||||
|
||||
/**
|
||||
* A class that provides pre-configured JAX-RS Client & WebTarget objects.
|
||||
@@ -30,7 +33,7 @@ import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
|
||||
public abstract class Client {
|
||||
|
||||
private final Object sync = new Object();
|
||||
private javax.ws.rs.client.Client client;
|
||||
private jakarta.ws.rs.client.Client client;
|
||||
|
||||
/**
|
||||
* @return The client configuration.
|
||||
@@ -40,7 +43,7 @@ public abstract class Client {
|
||||
/**
|
||||
* @return A pre-configured JAX-RS client (no URL) with configured authorization.
|
||||
*/
|
||||
public final javax.ws.rs.client.Client getClient() {
|
||||
public final jakarta.ws.rs.client.Client getClient() {
|
||||
synchronized (this.sync) {
|
||||
if (this.client == null)
|
||||
this.client = this.buildClient((AuthorizationFilter)null);
|
||||
@@ -53,7 +56,7 @@ public abstract class Client {
|
||||
* @param authFilter A dynamic authorization filter.
|
||||
* @return A pre-configured JAX-RS client (no URL) with the specified authorization.
|
||||
*/
|
||||
public javax.ws.rs.client.Client getClient(AuthorizationFilter authFilter) {
|
||||
public jakarta.ws.rs.client.Client getClient(AuthorizationFilter authFilter) {
|
||||
if (authFilter == null) {
|
||||
return this.getClient();
|
||||
} else {
|
||||
@@ -65,9 +68,14 @@ public abstract class Client {
|
||||
* @param authFilter A dynamic authorization filter.
|
||||
* @return A pre-configured JAX-RS client (no URL) with the specified authorization.
|
||||
*/
|
||||
public final javax.ws.rs.client.Client buildClient(AuthorizationFilter authFilter) {
|
||||
JacksonJsonProvider provider = new JacksonJaxbJsonProvider();
|
||||
|
||||
public final jakarta.ws.rs.client.Client buildClient(AuthorizationFilter authFilter) {
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
om.registerModules(new JavaTimeModule());
|
||||
this.getConfig().configureJacksonMapper(om);
|
||||
|
||||
JacksonJsonProvider provider = new JacksonJsonProvider(om, JacksonJsonProvider.BASIC_ANNOTATIONS);
|
||||
this.getConfig().configureJacksonProvider(provider);
|
||||
|
||||
if (this.getConfig().isWrapRootValueEnabled())
|
||||
provider.enable(SerializationFeature.WRAP_ROOT_VALUE);
|
||||
if (this.getConfig().isUnwrapRootValueEnabled())
|
||||
@@ -76,22 +84,28 @@ public abstract class Client {
|
||||
ClientBuilder clientBuilder = ClientBuilder.newBuilder()
|
||||
.register(provider)
|
||||
.register(new LoggingFilter());
|
||||
|
||||
if (this.getConfig().getConnectTimeoutInMillis() != null)
|
||||
clientBuilder.connectTimeout(this.getConfig().getConnectTimeoutInMillis(), TimeUnit.MILLISECONDS);
|
||||
if (this.getConfig().getResponseTimeoutInMillis() != null)
|
||||
clientBuilder.readTimeout(this.getConfig().getResponseTimeoutInMillis(), TimeUnit.MILLISECONDS);
|
||||
|
||||
if (authFilter == null)
|
||||
authFilter = this.getConfig().createAuthorizationFilter();
|
||||
if (authFilter != null)
|
||||
clientBuilder.register(authFilter);
|
||||
this.buildClient(clientBuilder);
|
||||
this.getConfig().configureClient(clientBuilder);
|
||||
|
||||
return clientBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientBuilder A client builder
|
||||
*/
|
||||
public void buildClient(ClientBuilder clientBuilder) {
|
||||
// for extension purposes
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientBuilder A client builder
|
||||
*/
|
||||
public void buildClient(ClientBuilder clientBuilder) {
|
||||
// for extension purposes
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A pre-configured JAX-RS target (client w/ base URL) with configured authorization.
|
||||
|
@@ -16,6 +16,11 @@ package com.inteligr8.rs;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
|
||||
|
||||
import jakarta.ws.rs.client.ClientBuilder;
|
||||
|
||||
/**
|
||||
* This interface defines the configurable parameters of the clients; primarily
|
||||
* their default authentication and authorization.
|
||||
@@ -114,6 +119,16 @@ public interface ClientConfiguration {
|
||||
}
|
||||
|
||||
|
||||
|
||||
default Integer getConnectTimeoutInMillis() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default Integer getResponseTimeoutInMillis() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @return true to enable Jackson UNWRAP_ROOT_VALUE feature; false otherwise.
|
||||
@@ -128,6 +143,18 @@ public interface ClientConfiguration {
|
||||
default boolean isWrapRootValueEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mapper A Jackson object mapper to configure.
|
||||
*/
|
||||
default void configureJacksonMapper(ObjectMapper mapper) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param provider A Jackson Jakarta RS provider to configure.
|
||||
*/
|
||||
default void configureJacksonProvider(JacksonJsonProvider provider) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -169,5 +196,13 @@ public interface ClientConfiguration {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Jackson provider, logging filter, and authentication filter are already registered.
|
||||
*
|
||||
* @param clientBuilder A JAX-RS client builder to configure.
|
||||
*/
|
||||
default void configureClient(ClientBuilder clientBuilder) {
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -14,7 +14,9 @@
|
||||
*/
|
||||
package com.inteligr8.rs;
|
||||
|
||||
import javax.ws.rs.client.ClientRequestContext;
|
||||
import jakarta.ws.rs.client.ClientRequestContext;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* This class is the base for implementations of client authorization similar
|
||||
@@ -45,8 +47,8 @@ public class ClientEnforcementAuthorizationFilter implements AuthorizationFilter
|
||||
* @param clientSecret A secret corresponding to the client ID.
|
||||
*/
|
||||
public ClientEnforcementAuthorizationFilter(String clientId, String clientSecret) {
|
||||
this.clientId = clientId;
|
||||
this.clientSecret = clientSecret;
|
||||
this.clientId = StringUtils.trimToNull(clientId);
|
||||
this.clientSecret = StringUtils.trimToNull(clientSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -14,18 +14,11 @@
|
||||
*/
|
||||
package com.inteligr8.rs;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
import javax.ws.rs.ext.RuntimeDelegate;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.ws.rs.client.ClientBuilder;
|
||||
|
||||
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;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A class that provides pre-configured JAX-RS Client & WebTarget objects
|
||||
@@ -33,19 +26,17 @@ import org.springframework.stereotype.Component;
|
||||
*
|
||||
* @author brian@inteligr8.com
|
||||
*/
|
||||
@Component("client.jersey")
|
||||
public class ClientJerseyImpl extends Client {
|
||||
public class ClientImpl extends Client {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ClientJerseyImpl.class);
|
||||
private final Logger logger = LoggerFactory.getLogger(ClientImpl.class);
|
||||
|
||||
private ClientJerseyConfiguration config;
|
||||
private ClientConfiguration config;
|
||||
|
||||
/**
|
||||
* This constructor is for Spring or POJO use.
|
||||
* @param config The client configuration.
|
||||
*/
|
||||
@Autowired
|
||||
public ClientJerseyImpl(ClientJerseyConfiguration config) {
|
||||
public ClientImpl(ClientConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@@ -55,18 +46,7 @@ public class ClientJerseyImpl extends Client {
|
||||
*/
|
||||
@PostConstruct
|
||||
public void register() {
|
||||
if (RuntimeDelegate.getInstance() == null) {
|
||||
this.logger.info("Setting JAX-RS runtime delegate to the Jersey library");
|
||||
RuntimeDelegate.setInstance(new RuntimeDelegateImpl());
|
||||
} else if (RuntimeDelegate.getInstance() instanceof RuntimeDelegateImpl) {
|
||||
this.logger.info("JAX-RS runtime delegate already the Jersey library");
|
||||
} else {
|
||||
this.logger.warn("Setting JAX-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());
|
||||
this.logger.info("API Base URL: {}", this.getConfig().getBaseUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,18 +54,12 @@ public class ClientJerseyImpl extends Client {
|
||||
*/
|
||||
@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() {
|
||||
public ClientConfiguration getConfig() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
@@ -99,7 +73,7 @@ public class ClientJerseyImpl extends Client {
|
||||
*/
|
||||
@Override
|
||||
public <T> T getApi(AuthorizationFilter authFilter, Class<T> apiClass) {
|
||||
return WebResourceFactory.newResource(apiClass, this.getTarget(authFilter));
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
@@ -14,8 +14,8 @@
|
||||
*/
|
||||
package com.inteligr8.rs;
|
||||
|
||||
import javax.ws.rs.client.ClientRequestContext;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.client.ClientRequestContext;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
|
||||
/**
|
||||
* This class implements a proxied or forwarded authorization header based
|
||||
|
@@ -16,12 +16,12 @@ package com.inteligr8.rs;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.ws.rs.client.ClientRequestContext;
|
||||
import javax.ws.rs.client.ClientRequestFilter;
|
||||
import javax.ws.rs.client.ClientResponseContext;
|
||||
import javax.ws.rs.client.ClientResponseFilter;
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.client.ClientRequestContext;
|
||||
import jakarta.ws.rs.client.ClientRequestFilter;
|
||||
import jakarta.ws.rs.client.ClientResponseContext;
|
||||
import jakarta.ws.rs.client.ClientResponseFilter;
|
||||
import jakarta.ws.rs.core.Form;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -58,7 +58,7 @@ public class LoggingFilter implements ClientRequestFilter, ClientResponseFilter
|
||||
logger.trace("request: {} {}: {}", requestContext.getMethod(), requestContext.getUri(),
|
||||
((Form)requestContext.getEntity()).asMap());
|
||||
} else {
|
||||
this.loggerRequest.trace("request: {} {}: failed to output form", requestContext.getMethod(), requestContext.getUri());
|
||||
logger.trace("request: {} {}: failed to output form", requestContext.getMethod(), requestContext.getUri());
|
||||
}
|
||||
} else {
|
||||
this.logUnhandledRequest(requestContext, logger);
|
||||
|
@@ -16,7 +16,11 @@ package com.inteligr8.rs;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import javax.ws.rs.core.Form;
|
||||
import jakarta.ws.rs.core.Form;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class implements the OAuth Authorization Code flow as an authorization
|
||||
@@ -25,6 +29,8 @@ import javax.ws.rs.core.Form;
|
||||
* @author brian@inteligr8.com
|
||||
*/
|
||||
public class OAuthAuthorizationCodeAuthorizationFilter extends OAuthAuthorizationFilter {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private final String code;
|
||||
private final URI redirectUri;
|
||||
@@ -68,17 +74,22 @@ public class OAuthAuthorizationCodeAuthorizationFilter extends OAuthAuthorizatio
|
||||
public OAuthAuthorizationCodeAuthorizationFilter(String tokenUrl, String clientId, String clientSecret, String code, URI redirectUri) {
|
||||
super(tokenUrl, clientId, clientSecret);
|
||||
|
||||
this.code = code;
|
||||
this.code = StringUtils.trimToNull(code);
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Form createForm() {
|
||||
Form form = new Form().param("grant_type", "authorization_code")
|
||||
.param("code", this.code);
|
||||
this.logger.debug("Using OAuth grant_type 'authorization_code'");
|
||||
Form form = new Form().param("grant_type", "authorization_code");
|
||||
if (this.redirectUri != null)
|
||||
form.param("redirect_uri", this.redirectUri.toString());
|
||||
return form;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extendFormSensitive(Form form) {
|
||||
form.param("code", this.code);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -16,15 +16,22 @@ package com.inteligr8.rs;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
import javax.ws.rs.client.ClientRequestContext;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.client.WebTarget;
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import jakarta.ws.rs.client.Client;
|
||||
import jakarta.ws.rs.client.ClientBuilder;
|
||||
import jakarta.ws.rs.client.ClientRequestContext;
|
||||
import jakarta.ws.rs.client.Entity;
|
||||
import jakarta.ws.rs.client.WebTarget;
|
||||
import jakarta.ws.rs.core.Form;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status.Family;
|
||||
|
||||
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
|
||||
|
||||
/**
|
||||
* This class is the base for implementations of OAuth authorization flows.
|
||||
@@ -33,6 +40,8 @@ import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
|
||||
*/
|
||||
public abstract class OAuthAuthorizationFilter implements AuthorizationFilter {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private final String tokenUrl;
|
||||
private final String clientId;
|
||||
private final String clientSecret;
|
||||
@@ -40,6 +49,7 @@ public abstract class OAuthAuthorizationFilter implements AuthorizationFilter {
|
||||
private String accessToken;
|
||||
private long expiration;
|
||||
private String refreshToken;
|
||||
private Long refreshTokenExpiration;
|
||||
|
||||
/**
|
||||
* This constructor creates an OAuth-based authorization filter using the
|
||||
@@ -79,9 +89,9 @@ public abstract class OAuthAuthorizationFilter implements AuthorizationFilter {
|
||||
*/
|
||||
public OAuthAuthorizationFilter(String tokenUrl, String clientId, String clientSecret, String scope) {
|
||||
this.tokenUrl = tokenUrl;
|
||||
this.clientId = clientId;
|
||||
this.clientSecret = clientSecret;
|
||||
this.scope = scope;
|
||||
this.clientId = StringUtils.trimToNull(clientId);
|
||||
this.clientSecret = StringUtils.trimToNull(clientSecret);
|
||||
this.scope = StringUtils.trimToNull(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,8 +101,29 @@ public abstract class OAuthAuthorizationFilter implements AuthorizationFilter {
|
||||
*/
|
||||
@Override
|
||||
public void filter(ClientRequestContext requestContext) {
|
||||
if (this.accessToken == null || System.currentTimeMillis() > this.expiration)
|
||||
this.requestToken();
|
||||
if (this.accessToken == null) {
|
||||
this.requestToken();
|
||||
} else if (System.currentTimeMillis() >= this.expiration) {
|
||||
this.logger.trace("Access token expired; retrieving new one with refresh token");
|
||||
|
||||
if (this.refreshTokenExpiration != null && System.currentTimeMillis() >= this.refreshTokenExpiration.longValue()) {
|
||||
this.logger.debug("Refresh token expired; performing full authentication");
|
||||
this.refreshToken = null;
|
||||
this.requestToken();
|
||||
} else {
|
||||
try {
|
||||
this.requestToken();
|
||||
} catch (WebApplicationException wae) {
|
||||
if (wae.getResponse().getStatusInfo().getFamily() == Family.CLIENT_ERROR) {
|
||||
this.logger.debug("Received OAuth response {} using refresh token; performing full authentication", wae.getResponse().getStatus());
|
||||
this.refreshToken = null;
|
||||
this.requestToken();
|
||||
} else {
|
||||
throw wae;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + this.accessToken);
|
||||
}
|
||||
@@ -111,29 +142,53 @@ public abstract class OAuthAuthorizationFilter implements AuthorizationFilter {
|
||||
}
|
||||
|
||||
form.param("client_id", this.clientId);
|
||||
if (this.clientSecret != null)
|
||||
form.param("client_secret", this.clientSecret);
|
||||
if (this.scope != null)
|
||||
form.param("scope", this.scope);
|
||||
this.extendRefreshTokenForm(form);
|
||||
|
||||
this.logger.trace("Sending OAuth request: {}", form);
|
||||
|
||||
if (this.refreshToken != null) {
|
||||
this.extendRefreshFormSensitive(form);
|
||||
} else {
|
||||
this.extendFormSensitive(form);
|
||||
}
|
||||
|
||||
if (this.clientSecret != null)
|
||||
form.param("client_secret", this.clientSecret);
|
||||
|
||||
Entity<Form> entity = Entity.form(form);
|
||||
|
||||
WebTarget target = ClientBuilder.newBuilder()
|
||||
.register(new JacksonJaxbJsonProvider())
|
||||
.build()
|
||||
.target(this.tokenUrl);
|
||||
Client client = ClientBuilder.newBuilder()
|
||||
.register(new JacksonJsonProvider())
|
||||
.build();
|
||||
WebTarget target = client.target(this.tokenUrl);
|
||||
|
||||
long requestSendTime = System.currentTimeMillis();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> response = target.request().post(entity, Map.class);
|
||||
Response response = target.request().post(entity);
|
||||
|
||||
if (response.containsKey("error"))
|
||||
throw new WebApplicationException((String)response.get("error"), 400);
|
||||
this.logger.debug("Received OAuth response: {}", response.getStatus());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> responseMap = response.readEntity(Map.class);
|
||||
|
||||
this.logger.trace("Received OAuth response: {}", responseMap);
|
||||
|
||||
this.accessToken = (String)response.get("access_token");
|
||||
this.expiration = System.currentTimeMillis() + ((Number)response.get("expires_in")).longValue() * 1000L;
|
||||
this.refreshToken = (String)response.get("refresh_token");
|
||||
this.extendRefreshTokenResponse(response);
|
||||
if (response.getStatusInfo().getFamily() != Family.SUCCESSFUL) {
|
||||
String code = (String) responseMap.get("error");
|
||||
if (code != null) {
|
||||
String description = (String) responseMap.get("error_description");
|
||||
throw new WebApplicationException(code + ": " + description, response.getStatus());
|
||||
} else {
|
||||
throw new WebApplicationException(response);
|
||||
}
|
||||
}
|
||||
|
||||
this.accessToken = (String)responseMap.get("access_token");
|
||||
this.expiration = requestSendTime + ((Number)responseMap.get("expires_in")).longValue() * 1000L;
|
||||
this.refreshToken = (String)responseMap.get("refresh_token");
|
||||
if (responseMap.containsKey("refresh_token_expires_in"))
|
||||
this.refreshTokenExpiration = requestSendTime + ((Number)responseMap.get("refresh_token_expires_in")).longValue() * 1000L;
|
||||
}
|
||||
|
||||
protected Form createRefreshForm() {
|
||||
@@ -143,10 +198,9 @@ public abstract class OAuthAuthorizationFilter implements AuthorizationFilter {
|
||||
|
||||
protected abstract Form createForm();
|
||||
|
||||
protected void extendRefreshTokenForm(Form form) {
|
||||
}
|
||||
|
||||
protected void extendRefreshTokenResponse(Map<String, Object> response) {
|
||||
protected void extendRefreshFormSensitive(Form form) {
|
||||
}
|
||||
|
||||
protected abstract void extendFormSensitive(Form form);
|
||||
|
||||
}
|
||||
|
@@ -14,7 +14,10 @@
|
||||
*/
|
||||
package com.inteligr8.rs;
|
||||
|
||||
import javax.ws.rs.core.Form;
|
||||
import jakarta.ws.rs.core.Form;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class implements the OAuth Client Credential flow as an authorization
|
||||
@@ -23,6 +26,8 @@ import javax.ws.rs.core.Form;
|
||||
* @author brian@inteligr8.com
|
||||
*/
|
||||
public class OAuthClientCredentialAuthorizationFilter extends OAuthAuthorizationFilter {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
/**
|
||||
* @param tokenUrl The URL to the OAuth IdP token service.
|
||||
@@ -35,7 +40,12 @@ public class OAuthClientCredentialAuthorizationFilter extends OAuthAuthorization
|
||||
|
||||
@Override
|
||||
protected Form createForm() {
|
||||
this.logger.debug("Using OAuth grant_type 'client_credentials'");
|
||||
return new Form().param("grant_type", "client_credentials");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extendFormSensitive(Form form) {
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -14,7 +14,11 @@
|
||||
*/
|
||||
package com.inteligr8.rs;
|
||||
|
||||
import javax.ws.rs.core.Form;
|
||||
import jakarta.ws.rs.core.Form;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class implements the OAuth Password Grant flow as an authorization
|
||||
@@ -23,6 +27,8 @@ import javax.ws.rs.core.Form;
|
||||
* @author brian@inteligr8.com
|
||||
*/
|
||||
public class OAuthPasswordGrantAuthorizationFilter extends OAuthAuthorizationFilter {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private final String username;
|
||||
private final String password;
|
||||
@@ -46,15 +52,20 @@ public class OAuthPasswordGrantAuthorizationFilter extends OAuthAuthorizationFil
|
||||
*/
|
||||
public OAuthPasswordGrantAuthorizationFilter(String tokenUrl, String clientId, String clientSecret, String username, String password) {
|
||||
super(tokenUrl, clientId, clientSecret);
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.username = StringUtils.trimToNull(username);
|
||||
this.password = StringUtils.trimToNull(password);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Form createForm() {
|
||||
this.logger.debug("Using OAuth grant_type 'password': {}", this.username);
|
||||
return new Form().param("grant_type", "password")
|
||||
.param("username", this.username)
|
||||
.param("password", this.password);
|
||||
.param("username", this.username);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extendFormSensitive(Form form) {
|
||||
form.param("password", this.password);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,35 +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;
|
||||
|
||||
/**
|
||||
* This interface defines additional configurations specific to the Jersey
|
||||
* JAX-RS library and its nuances.
|
||||
*
|
||||
* @author brian@inteligr8.com
|
||||
*/
|
||||
public interface ClientJerseyConfiguration extends ClientConfiguration {
|
||||
|
||||
/**
|
||||
* Jersey is automatically strict in its adherence to the ReST API
|
||||
* specifications. It requires a body to PUT calls by default.
|
||||
*
|
||||
* @return true to require body in PUT calls; false to make it optional
|
||||
*/
|
||||
default boolean isPutBodyRequired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user