Skip to content

Commit 0eb33d9

Browse files
authored
Merge pull request #16 from sashirestela/15-feature-to-download-files
Enable file downloading
2 parents 6d79422 + e332218 commit 0eb33d9

File tree

10 files changed

+179
-13
lines changed

10 files changed

+179
-13
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>io.github.sashirestela</groupId>
88
<artifactId>cleverclient</artifactId>
9-
<version>0.7.1</version>
9+
<version>0.8.0</version>
1010
<packaging>jar</packaging>
1111

1212
<name>cleverclient</name>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.github.sashirestela.cleverclient.example;
2+
3+
import java.io.FileOutputStream;
4+
import java.io.IOException;
5+
import java.io.InputStream;
6+
7+
import io.github.sashirestela.cleverclient.CleverClient;
8+
import io.github.sashirestela.cleverclient.annotation.GET;
9+
import io.github.sashirestela.cleverclient.annotation.Path;
10+
11+
public class FileDownloadExample {
12+
13+
static interface ImageService {
14+
15+
@GET("/150/{id}")
16+
InputStream getImage(@Path("id") String id);
17+
}
18+
19+
public static void main(String[] args) throws IOException {
20+
final var URL_BASE = "https://via.placeholder.com";
21+
22+
var cleverClient = CleverClient.builder()
23+
.urlBase(URL_BASE)
24+
.build();
25+
26+
var imageService = cleverClient.create(ImageService.class);
27+
var binaryData = imageService.getImage("92c952");
28+
var file = new FileOutputStream("src/test/resources/download.png");
29+
file.write(binaryData.readAllBytes());
30+
file.close();
31+
}
32+
}

src/main/java/io/github/sashirestela/cleverclient/http/ReturnType.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public class ReturnType {
66
private static final String ASYNC = "java.util.concurrent.CompletableFuture";
77
private static final String STREAM = "java.util.stream.Stream";
88
private static final String LIST = "java.util.List";
9+
private static final String INPUTSTREAM = "java.io.InputStream";
910
private static final String STRING = "java.lang.String";
1011

1112
private static final String REGEX = "[<>]";
@@ -70,6 +71,8 @@ private Category asyncCategory() {
7071
return Category.ASYNC_GENERIC;
7172
} else if (isObject()) {
7273
return Category.ASYNC_OBJECT;
74+
} else if (isBinary()) {
75+
return Category.ASYNC_BINARY;
7376
} else if (isPlainText()) {
7477
return Category.ASYNC_PLAIN_TEXT;
7578
} else {
@@ -86,6 +89,8 @@ private Category syncCategory() {
8689
return Category.SYNC_GENERIC;
8790
} else if (isObject()) {
8891
return Category.SYNC_OBJECT;
92+
} else if (isBinary()) {
93+
return Category.SYNC_BINARY;
8994
} else if (isPlainText()) {
9095
return Category.SYNC_PLAIN_TEXT;
9196
} else {
@@ -111,13 +116,21 @@ public boolean isGeneric() {
111116
}
112117

113118
public boolean isObject() {
114-
return !isString() && (size == 1 || (size == 2 && isAsync()));
119+
return !isInputStream() && !isString() && (size == 1 || (size == 2 && isAsync()));
120+
}
121+
122+
public boolean isBinary() {
123+
return isInputStream() && (size == 1 || (size == 2 && isAsync()));
115124
}
116125

117126
public boolean isPlainText() {
118127
return isString() && (size == 1 || (size == 2 && isAsync()));
119128
}
120129

130+
private boolean isInputStream() {
131+
return INPUTSTREAM.equals(returnTypeArray[lastIndex]);
132+
}
133+
121134
private boolean isString() {
122135
return STRING.equals(returnTypeArray[lastIndex]);
123136
}
@@ -127,11 +140,13 @@ public enum Category {
127140
ASYNC_LIST,
128141
ASYNC_GENERIC,
129142
ASYNC_OBJECT,
143+
ASYNC_BINARY,
130144
ASYNC_PLAIN_TEXT,
131145
SYNC_STREAM,
132146
SYNC_LIST,
133147
SYNC_GENERIC,
134148
SYNC_OBJECT,
149+
SYNC_BINARY,
135150
SYNC_PLAIN_TEXT;
136151
}
137152
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.github.sashirestela.cleverclient.sender;
2+
3+
import java.net.http.HttpClient;
4+
import java.net.http.HttpRequest;
5+
import java.net.http.HttpResponse.BodyHandlers;
6+
7+
public class HttpAsyncBinarySender extends HttpSender {
8+
9+
@Override
10+
@SuppressWarnings("unchecked")
11+
public <S, T> Object sendRequest(HttpClient httpClient, HttpRequest httpRequest, Class<T> responseClass,
12+
Class<S> genericClass) {
13+
14+
var httpResponseFuture = httpClient.sendAsync(httpRequest, BodyHandlers.ofInputStream());
15+
16+
return httpResponseFuture.thenApply(response -> {
17+
18+
throwExceptionIfErrorIsPresent(response, false);
19+
20+
logger.debug("Response : {}", response.body());
21+
22+
return (T) response.body();
23+
});
24+
}
25+
26+
}

src/main/java/io/github/sashirestela/cleverclient/sender/HttpSenderFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ private HttpSenderFactory() {
2323
sendersMap.put(Category.ASYNC_LIST, HttpAsyncListSender::new);
2424
sendersMap.put(Category.ASYNC_GENERIC, HttpAsyncGenericSender::new);
2525
sendersMap.put(Category.ASYNC_OBJECT, HttpAsyncObjectSender::new);
26+
sendersMap.put(Category.ASYNC_BINARY, HttpAsyncBinarySender::new);
2627
sendersMap.put(Category.ASYNC_PLAIN_TEXT, HttpAsyncPlainTextSender::new);
2728
sendersMap.put(Category.SYNC_STREAM, HttpSyncStreamSender::new);
2829
sendersMap.put(Category.SYNC_LIST, HttpSyncListSender::new);
2930
sendersMap.put(Category.SYNC_GENERIC, HttpSyncGenericSender::new);
3031
sendersMap.put(Category.SYNC_OBJECT, HttpSyncObjectSender::new);
32+
sendersMap.put(Category.SYNC_BINARY, HttpSyncBinarySender::new);
3133
sendersMap.put(Category.SYNC_PLAIN_TEXT, HttpSyncPlainTextSender::new);
3234
}
3335

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.github.sashirestela.cleverclient.sender;
2+
3+
import java.io.IOException;
4+
import java.net.http.HttpClient;
5+
import java.net.http.HttpRequest;
6+
import java.net.http.HttpResponse.BodyHandlers;
7+
8+
import io.github.sashirestela.cleverclient.support.CleverClientException;
9+
10+
public class HttpSyncBinarySender extends HttpSender {
11+
12+
@Override
13+
public <S, T> Object sendRequest(HttpClient httpClient, HttpRequest httpRequest, Class<T> responseClass,
14+
Class<S> genericClass) {
15+
try {
16+
17+
var httpResponse = httpClient.send(httpRequest, BodyHandlers.ofInputStream());
18+
19+
throwExceptionIfErrorIsPresent(httpResponse, false);
20+
21+
var rawData = httpResponse.body();
22+
23+
logger.debug("Response : {}", rawData);
24+
25+
return rawData;
26+
27+
} catch (IOException | InterruptedException e) {
28+
Thread.currentThread().interrupt();
29+
throw new CleverClientException(e.getMessage(), null, e);
30+
}
31+
}
32+
33+
}

src/test/java/io/github/sashirestela/cleverclient/http/HttpProcessorTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
import static org.mockito.Mockito.mock;
88
import static org.mockito.Mockito.when;
99

10+
import java.io.File;
11+
import java.io.FileInputStream;
12+
import java.io.FileNotFoundException;
1013
import java.io.IOException;
14+
import java.io.InputStream;
1115
import java.net.HttpURLConnection;
1216
import java.net.http.HttpClient;
1317
import java.net.http.HttpRequest;
@@ -29,6 +33,7 @@ class HttpProcessorTest {
2933
HttpClient httpClient = mock(HttpClient.class);
3034
HttpResponse<String> httpResponse = mock(HttpResponse.class);
3135
HttpResponse<Stream<String>> httpResponseStream = mock(HttpResponse.class);
36+
HttpResponse<InputStream> httpResponseBinary = mock(HttpResponse.class);
3237

3338
@BeforeEach
3439
void init() {
@@ -83,6 +88,30 @@ void shouldThrownExceptionWhenMethodReturnTypeIsAString() throws IOException, In
8388
assertThrows(CleverClientException.class, () -> service.getDemoPlain(100));
8489
}
8590

91+
@Test
92+
void shouldshouldReturnABinarySyncWhenMethodReturnTypeIsABinary() throws IOException, InterruptedException {
93+
InputStream binaryData = new FileInputStream(new File("src/test/resources/image.png"));
94+
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofInputStream().getClass())))
95+
.thenReturn(httpResponseBinary);
96+
when(httpResponseBinary.statusCode()).thenReturn(HttpURLConnection.HTTP_OK);
97+
when(httpResponseBinary.body()).thenReturn(binaryData);
98+
99+
var service = httpProcessor.createProxy(ITest.SyncService.class);
100+
var actualDemo = service.getDemoBinary(100);
101+
var expectedDemo = binaryData;
102+
103+
assertEquals(expectedDemo, actualDemo);
104+
}
105+
106+
@Test
107+
void shouldThrownExceptionWhenMethodReturnTypeIsABinary() throws IOException, InterruptedException {
108+
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofInputStream().getClass())))
109+
.thenThrow(new InterruptedException("The operation was interrupted"));
110+
111+
var service = httpProcessor.createProxy(ITest.SyncService.class);
112+
assertThrows(CleverClientException.class, () -> service.getDemoBinary(100));
113+
}
114+
86115
@Test
87116
void shouldReturnAnObjectSyncWhenMethodReturnTypeIsAnObject() throws IOException, InterruptedException {
88117
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofString().getClass())))
@@ -193,6 +222,21 @@ void shouldReturnAStringAsyncWhenMethodReturnTypeIsAString() {
193222
assertEquals(expectedDemo, actualDemo);
194223
}
195224

225+
@Test
226+
void shouldReturnABinaryAsyncWhenMethodReturnTypeIsABinary() throws FileNotFoundException {
227+
InputStream binaryData = new FileInputStream(new File("src/test/resources/image.png"));
228+
when(httpClient.sendAsync(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofInputStream().getClass())))
229+
.thenReturn(CompletableFuture.completedFuture(httpResponseBinary));
230+
when(httpResponseBinary.statusCode()).thenReturn(HttpURLConnection.HTTP_OK);
231+
when(httpResponseBinary.body()).thenReturn(binaryData);
232+
233+
var service = httpProcessor.createProxy(ITest.AsyncService.class);
234+
var actualDemo = service.getDemoBinary(100).join();
235+
var expectedDemo = binaryData;
236+
237+
assertEquals(expectedDemo, actualDemo);
238+
}
239+
196240
@Test
197241
void shouldReturnAnObjectAsyncWhenMethodReturnTypeIsAnObject() {
198242
when(httpClient.sendAsync(any(HttpRequest.class), any(HttpResponse.BodyHandlers.ofString().getClass())))

src/test/java/io/github/sashirestela/cleverclient/http/ITest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.github.sashirestela.cleverclient.http;
22

3+
import java.io.InputStream;
34
import java.util.List;
45
import java.util.Set;
56
import java.util.concurrent.CompletableFuture;
@@ -47,6 +48,9 @@ interface AsyncService {
4748
@GET("/{demoId}")
4849
CompletableFuture<String> getDemoPlain(@Path("demoId") Integer demoId);
4950

51+
@GET("/{demoId}")
52+
CompletableFuture<InputStream> getDemoBinary(@Path("demoId") Integer demoId);
53+
5054
@GET("/{demoId}")
5155
CompletableFuture<Demo> getDemo(@Path("demoId") Integer demoId);
5256

@@ -74,6 +78,9 @@ interface SyncService {
7478
@GET("/{demoId}")
7579
String getDemoPlain(@Path("demoId") Integer demoId);
7680

81+
@GET("/{demoId}")
82+
InputStream getDemoBinary(@Path("demoId") Integer demoId);
83+
7784
@GET("/{demoId}")
7885
Demo getDemo(@Path("demoId") Integer demoId);
7986

src/test/java/io/github/sashirestela/cleverclient/http/ReturnTypeTest.java

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static org.junit.jupiter.api.Assertions.assertEquals;
44
import static org.junit.jupiter.api.Assertions.assertNull;
55

6+
import java.io.InputStream;
67
import java.util.List;
78
import java.util.Map;
89
import java.util.Set;
@@ -24,17 +25,19 @@ void shouldReturnBaseClassForAMethod() throws NoSuchMethodException, SecurityExc
2425

2526
@Test
2627
void shouldReturnCategoryAccordingToTheMethodType() throws NoSuchMethodException, SecurityException {
27-
var testData = Map.of(
28-
"asyncStreamMethod", ReturnType.Category.ASYNC_STREAM,
29-
"asyncListMethod", ReturnType.Category.ASYNC_LIST,
30-
"asyncGenericMethod", ReturnType.Category.ASYNC_GENERIC,
31-
"asyncObjectMethod", ReturnType.Category.ASYNC_OBJECT,
32-
"asyncStringMethod", ReturnType.Category.ASYNC_PLAIN_TEXT,
33-
"syncStreamMethod", ReturnType.Category.SYNC_STREAM,
34-
"syncListMethod", ReturnType.Category.SYNC_LIST,
35-
"syncGenericMethod", ReturnType.Category.SYNC_GENERIC,
36-
"syncObjectMethod", ReturnType.Category.SYNC_OBJECT,
37-
"syncStringMethod", ReturnType.Category.SYNC_PLAIN_TEXT);
28+
var testData = Map.ofEntries(
29+
Map.entry("asyncStreamMethod", ReturnType.Category.ASYNC_STREAM),
30+
Map.entry("asyncListMethod", ReturnType.Category.ASYNC_LIST),
31+
Map.entry("asyncGenericMethod", ReturnType.Category.ASYNC_GENERIC),
32+
Map.entry("asyncObjectMethod", ReturnType.Category.ASYNC_OBJECT),
33+
Map.entry("asyncBinaryMethod", ReturnType.Category.ASYNC_BINARY),
34+
Map.entry("asyncStringMethod", ReturnType.Category.ASYNC_PLAIN_TEXT),
35+
Map.entry("syncStreamMethod", ReturnType.Category.SYNC_STREAM),
36+
Map.entry("syncListMethod", ReturnType.Category.SYNC_LIST),
37+
Map.entry("syncGenericMethod", ReturnType.Category.SYNC_GENERIC),
38+
Map.entry("syncObjectMethod", ReturnType.Category.SYNC_OBJECT),
39+
Map.entry("syncBinaryMethod", ReturnType.Category.SYNC_BINARY),
40+
Map.entry("syncStringMethod", ReturnType.Category.SYNC_PLAIN_TEXT));
3841
for (String methodName : testData.keySet()) {
3942
var method = TestInterface.class.getMethod(methodName, new Class[] {});
4043
var returnType = new ReturnType(method);
@@ -64,6 +67,8 @@ static interface TestInterface {
6467

6568
CompletableFuture<Object> asyncObjectMethod();
6669

70+
CompletableFuture<InputStream> asyncBinaryMethod();
71+
6772
CompletableFuture<String> asyncStringMethod();
6873

6974
CompletableFuture<Set<Object>> asyncSetMethod();
@@ -76,6 +81,8 @@ static interface TestInterface {
7681

7782
Object syncObjectMethod();
7883

84+
InputStream syncBinaryMethod();
85+
7986
String syncStringMethod();
8087

8188
Set<Object> syncSetMethod();

src/test/resources/download.png

1.1 KB
Loading

0 commit comments

Comments
 (0)