Skip to content

Commit 55b17b2

Browse files
committed
Add retrofit module
1 parent dc6b459 commit 55b17b2

File tree

6 files changed

+569
-0
lines changed

6 files changed

+569
-0
lines changed

modules/retrofit/pom.xml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>dev.failsafe</groupId>
8+
<artifactId>failsafe-parent</artifactId>
9+
<version>3.2.2-SNAPSHOT</version>
10+
<relativePath>../../pom.xml</relativePath>
11+
</parent>
12+
13+
<artifactId>failsafe-retrofit</artifactId>
14+
<name>Failsafe Retrofit</name>
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>${project.groupId}</groupId>
19+
<artifactId>failsafe</artifactId>
20+
<version>${project.version}</version>
21+
</dependency>
22+
<dependency>
23+
<groupId>com.squareup.retrofit2</groupId>
24+
<artifactId>retrofit</artifactId>
25+
<version>2.9.0</version>
26+
</dependency>
27+
28+
<!-- Test Dependencies -->
29+
<dependency>
30+
<groupId>${project.groupId}</groupId>
31+
<artifactId>failsafe</artifactId>
32+
<version>${project.version}</version>
33+
<type>test-jar</type>
34+
<scope>test</scope>
35+
</dependency>
36+
<dependency>
37+
<groupId>com.google.code.gson</groupId>
38+
<artifactId>gson</artifactId>
39+
<version>2.9.0</version>
40+
<scope>test</scope>
41+
</dependency>
42+
<dependency>
43+
<groupId>com.squareup.retrofit2</groupId>
44+
<artifactId>converter-gson</artifactId>
45+
<version>2.3.0</version>
46+
<scope>test</scope>
47+
</dependency>
48+
<dependency>
49+
<groupId>com.github.tomakehurst</groupId>
50+
<artifactId>wiremock-jre8</artifactId>
51+
<version>2.32.0</version>
52+
<scope>test</scope>
53+
</dependency>
54+
</dependencies>
55+
</project>
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License
15+
*/
16+
package dev.failsafe.retrofit;
17+
18+
import dev.failsafe.*;
19+
import dev.failsafe.internal.util.Assert;
20+
import retrofit2.Callback;
21+
import retrofit2.Response;
22+
23+
import java.io.IOException;
24+
import java.util.concurrent.CompletableFuture;
25+
import java.util.concurrent.atomic.AtomicBoolean;
26+
27+
/**
28+
* A Failsafe wrapped Retrofit {@link Call}. Supports synchronous and asynchronous executions, and cancellation.
29+
*
30+
* @param <T> response type
31+
* @author Jonathan Halterman
32+
*/
33+
public final class FailsafeCall<T> {
34+
private final FailsafeExecutor<Response<T>> failsafe;
35+
private final retrofit2.Call<T> initialCall;
36+
37+
private volatile Call<Response<T>> failsafeCall;
38+
private volatile CompletableFuture<Response<T>> failsafeFuture;
39+
private AtomicBoolean cancelled = new AtomicBoolean();
40+
private AtomicBoolean executed = new AtomicBoolean();
41+
42+
private FailsafeCall(retrofit2.Call<T> call, FailsafeExecutor<Response<T>> failsafe) {
43+
this.initialCall = call;
44+
this.failsafe = failsafe;
45+
}
46+
47+
/**
48+
* Returns a FailsafeCall for the {@code call}, {@code outerPolicy} and {@code policies}. See {@link
49+
* Failsafe#with(Policy, Policy[])} for docs on how policy composition works.
50+
*
51+
* @param <T> response type
52+
* @param <P> policy type
53+
* @throws NullPointerException if {@code call} or {@code outerPolicy} are null
54+
*/
55+
@SafeVarargs
56+
public static <T, P extends Policy<Response<T>>> FailsafeCall<T> of(retrofit2.Call<T> call, P outerPolicy,
57+
P... policies) {
58+
return of(call, Failsafe.with(outerPolicy, policies));
59+
}
60+
61+
/**
62+
* Returns a FailsafeCall for the {@code call} and {@code failsafeExecutor}.
63+
*
64+
* @param <T> response type
65+
* @throws NullPointerException if {@code call} or {@code failsafeExecutor} are null
66+
*/
67+
public static <T> FailsafeCall<T> of(retrofit2.Call<T> call, FailsafeExecutor<Response<T>> failsafeExecutor) {
68+
return new FailsafeCall<>(Assert.notNull(call, "call"), Assert.notNull(failsafeExecutor, "failsafeExecutor"));
69+
}
70+
71+
/**
72+
* Cancels the call.
73+
*/
74+
public void cancel() {
75+
if (!cancelled.compareAndSet(false, true))
76+
return;
77+
if (failsafeCall != null)
78+
failsafeCall.cancel();
79+
if (failsafeFuture != null)
80+
failsafeFuture.cancel(false);
81+
}
82+
83+
/**
84+
* Returns a clone of the FailsafeCall.
85+
*/
86+
public FailsafeCall<T> clone() {
87+
return FailsafeCall.of(initialCall.clone(), failsafe);
88+
}
89+
90+
/**
91+
* Executes the call until a successful response is returned or the configured policies are exceeded.
92+
*
93+
* @throws IllegalStateException if the call has already been executed
94+
* @throws IOException if the request could not be executed due to cancellation, a connectivity problem, or timeout
95+
* @throws FailsafeException if the execution fails with a checked Exception. {@link FailsafeException#getCause()} can
96+
* be used to learn the underlying checked exception.
97+
*/
98+
public Response<T> execute() throws IOException {
99+
Assert.isTrue(executed.compareAndSet(false, true), "already executed");
100+
101+
failsafeCall = failsafe.getCall(ctx -> {
102+
return prepareCall(ctx).execute();
103+
});
104+
105+
try {
106+
return failsafeCall.execute();
107+
} catch (FailsafeException e) {
108+
if (e.getCause() instanceof IOException)
109+
throw (IOException) e.getCause();
110+
throw e;
111+
}
112+
}
113+
114+
/**
115+
* Executes the call asynchronously until a successful result is returned or the configured policies are exceeded.
116+
*/
117+
public CompletableFuture<Response<T>> executeAsync() {
118+
if (!executed.compareAndSet(false, true)) {
119+
CompletableFuture<Response<T>> result = new CompletableFuture<>();
120+
result.completeExceptionally(new IllegalStateException("already executed"));
121+
return result;
122+
}
123+
124+
failsafeFuture = failsafe.getAsyncExecution(exec -> {
125+
prepareCall(exec).enqueue(new Callback<T>() {
126+
@Override
127+
public void onResponse(retrofit2.Call<T> call, Response<T> response) {
128+
exec.recordResult(response);
129+
}
130+
131+
@Override
132+
public void onFailure(retrofit2.Call<T> call, Throwable throwable) {
133+
exec.recordException(throwable);
134+
}
135+
});
136+
});
137+
138+
return failsafeFuture;
139+
}
140+
141+
/**
142+
* Returns whether the call has been cancelled.
143+
*/
144+
public boolean isCanceled() {
145+
return cancelled.get();
146+
}
147+
148+
/**
149+
* Returns whether the call has been executed.
150+
*/
151+
public boolean isExecuted() {
152+
return executed.get();
153+
}
154+
155+
private retrofit2.Call<T> prepareCall(ExecutionContext<Response<T>> ctx) {
156+
retrofit2.Call<T> call = ctx.isFirstAttempt() ? initialCall : initialCall.clone();
157+
158+
// Propagate cancellation to the call
159+
ctx.onCancel(call::cancel);
160+
return call;
161+
}
162+
}

0 commit comments

Comments
 (0)