Skip to content

Commit 4189f93

Browse files
authored
Merge pull request #148 from dazzleai/file_upload
Implement file upload and download in MessageBirdClient.
2 parents d52a708 + 672f87e commit 4189f93

File tree

6 files changed

+236
-9
lines changed

6 files changed

+236
-9
lines changed

api/src/main/java/com/messagebird/MessageBirdClient.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.messagebird.objects.ContactList;
99
import com.messagebird.objects.ContactRequest;
1010
import com.messagebird.objects.ErrorReport;
11+
import com.messagebird.objects.FileUploadResponse;
1112
import com.messagebird.objects.Group;
1213
import com.messagebird.objects.GroupList;
1314
import com.messagebird.objects.GroupRequest;
@@ -98,6 +99,7 @@ public class MessageBirdClient {
9899
private static final String CONVERSATIONS_BASE_URL = "https://conversations.messagebird.com/v1";
99100
static final String VOICE_CALLS_BASE_URL = "https://voice.messagebird.com";
100101
static final String NUMBERS_CALLS_BASE_URL = "https://numbers.messagebird.com/v1";
102+
static final String MESSAGING_BASE_URL = "https://messaging.messagebird.com/v1";
101103
private static String[] supportedLanguages = {"de-DE", "en-AU", "en-UK", "en-US", "es-ES", "es-LA", "fr-FR", "it-IT", "nl-NL", "pt-BR"};
102104

103105
private static final String BALANCEPATH = "/balance";
@@ -120,6 +122,8 @@ public class MessageBirdClient {
120122
static final String WEBHOOKS = "/webhooks";
121123
static final String VOICECALLFLOWPATH = "/call-flows";
122124
private static final String VOICELEGS_SUFFIX_PATH = "/legs";
125+
static final String FILES_PATH = "/files";
126+
123127
static final String RECORDING_DOWNLOAD_FORMAT = ".wav";
124128

125129
static final String TRANSCRIPTION_DOWNLOAD_FORMAT = ".txt";
@@ -1767,4 +1771,60 @@ public void cancelNumber(String number) throws UnauthorizedException, GeneralExc
17671771
final String url = String.format("%s/phone-numbers", NUMBERS_CALLS_BASE_URL);
17681772
messageBirdService.deleteByID(url, number);
17691773
}
1774+
1775+
/**
1776+
* Uploads a file and returns the assigned ID.
1777+
*
1778+
* @param binary the bytes of the file to upload.
1779+
* @param contentType the content type of the file (e.g. "image/png").
1780+
* @param filename optional filename to set in the upload request headers.
1781+
* @return FileUploadResponse
1782+
* @throws GeneralException general exception
1783+
* @throws UnauthorizedException if client is unauthorized
1784+
* @see #downloadFile
1785+
*/
1786+
public FileUploadResponse uploadFile(byte[] binary, String contentType, String filename) throws GeneralException, UnauthorizedException {
1787+
if (binary == null) {
1788+
throw new IllegalArgumentException("File binary must be specified.");
1789+
}
1790+
if (contentType == null) {
1791+
throw new IllegalArgumentException("Content type must be specified.");
1792+
}
1793+
1794+
final String url = MESSAGING_BASE_URL + FILES_PATH;
1795+
final Map<String, String> headers = new HashMap<>();
1796+
headers.put("Content-Type", contentType);
1797+
if (filename != null) {
1798+
headers.put("filename", filename);
1799+
}
1800+
return messageBirdService.sendPayLoad("POST", url, headers, binary, FileUploadResponse.class);
1801+
}
1802+
1803+
/**
1804+
* Downloads a file and stores it with the provided filename in the basePath directory. The
1805+
* basePath may be null. If basePath is null, the default download directory will be the
1806+
* /Download folder in the user home directory. The filename may be null. If filename is null,
1807+
* the provided id will be used as the filename instead.
1808+
*
1809+
* @param id the ID of the file, provided when the file was uploaded
1810+
* @param basePath store location. It should be a directory. Property is nullable if $HOME is accessible
1811+
* @param filename the name of the file to download to.
1812+
* @return the path where the downloaded file is stored
1813+
* @throws NotFoundException if the file does not exist
1814+
* @throws GeneralException general exception
1815+
* @throws UnauthorizedException if client is unauthorized
1816+
* @see #uploadFile
1817+
*/
1818+
public String downloadFile(String id, String filename, String basePath) throws GeneralException, UnauthorizedException, NotFoundException {
1819+
if (id == null) {
1820+
throw new IllegalArgumentException("File ID must be specified.");
1821+
}
1822+
1823+
if (filename == null) {
1824+
filename = id;
1825+
}
1826+
1827+
final String url = String.format("%s%s/%s", MESSAGING_BASE_URL, FILES_PATH, id);
1828+
return messageBirdService.getBinaryData(url, basePath, filename);
1829+
}
17701830
}

api/src/main/java/com/messagebird/MessageBirdService.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@ public interface MessageBirdService {
107107
*/
108108
<R, P> R sendPayLoad(String method, String request, P payload, Class<R> clazz) throws UnauthorizedException, GeneralException;
109109

110+
/**
111+
* Send a payload with the provided method and headers and receive a payload object.
112+
*
113+
* @param method HTTP method to use for the request
114+
* @param request path to the request, for example "/messages"
115+
* @param headers additional headers to set on the request
116+
* @param payload payload to send to the server
117+
* @param clazz object type to return
118+
* @return base class
119+
* @throws UnauthorizedException if client is unauthorized
120+
* @throws GeneralException general exception
121+
*/
122+
<R, P> R sendPayLoad(String method, String request, Map<String, String> headers, P payload, Class<R> clazz) throws UnauthorizedException, GeneralException;
123+
110124
/**
111125
* Gets the data from the request URL and stores it to basePath/fileName
112126
*

api/src/main/java/com/messagebird/MessageBirdServiceImpl.java

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,17 @@ public <R, P> R sendPayLoad(String request, P payload, Class<R> clazz) throws Un
169169

170170
@Override
171171
public <R, P> R sendPayLoad(String method, String request, P payload, Class<R> clazz) throws UnauthorizedException, GeneralException {
172+
return sendPayLoad(method, request, new HashMap<>(), payload, clazz);
173+
}
174+
175+
@Override
176+
public <R, P> R sendPayLoad(String method, String request, Map<String, String> headers, P payload, Class<R> clazz) throws UnauthorizedException, GeneralException {
172177
if (!REQUEST_METHODS_WITH_PAYLOAD.contains(method)) {
173178
throw new IllegalArgumentException(String.format(REQUEST_METHOD_NOT_ALLOWED, method));
174179
}
175180

176181
try {
177-
return getJsonData(request, payload, method, clazz);
182+
return getJsonData(request, payload, method, headers, clazz);
178183
} catch (NotFoundException e) {
179184
throw new GeneralException(e);
180185
}
@@ -204,6 +209,10 @@ public String getBinaryData(String request, String basePath, String fileName) th
204209

205210

206211
public <T, P> T getJsonData(final String request, final P payload, final String requestType, final Class<T> clazz) throws UnauthorizedException, GeneralException, NotFoundException {
212+
return getJsonData(request, payload, requestType, new HashMap<>(), clazz);
213+
}
214+
215+
public <T, P> T getJsonData(final String request, final P payload, final String requestType, final Map<String, String> headers, final Class<T> clazz) throws UnauthorizedException, GeneralException, NotFoundException {
207216
if (request == null) {
208217
throw new IllegalArgumentException(REQUEST_VALUE_MUST_BE_SPECIFIED);
209218
}
@@ -212,7 +221,7 @@ public <T, P> T getJsonData(final String request, final P payload, final String
212221
if (!isURLAbsolute(url)) {
213222
url = serviceUrl + url;
214223
}
215-
final APIResponse apiResponse = doRequest(requestType, url, payload);
224+
final APIResponse apiResponse = doRequest(requestType, url, headers, payload);
216225

217226
final String body = apiResponse.getBody();
218227
final int status = apiResponse.getStatus();
@@ -256,11 +265,12 @@ private void handleHttpFailStatuses(final int status, String body) throws Unauth
256265
*
257266
* @param method HTTP method.
258267
* @param url Absolute URL.
268+
* @param headers additional headers to set on the request.
259269
* @param payload Payload to JSON encode for the request body. May be null.
260270
* @param <P> Type of the payload.
261271
* @return APIResponse containing the response's body and status.
262272
*/
263-
<P> APIResponse doRequest(final String method, final String url, final P payload) throws GeneralException {
273+
<P> APIResponse doRequest(final String method, final String url, final Map<String, String> headers, final P payload) throws GeneralException {
264274
HttpURLConnection connection = null;
265275
InputStream inputStream = null;
266276

@@ -275,7 +285,7 @@ <P> APIResponse doRequest(final String method, final String url, final P payload
275285
}
276286

277287
try {
278-
connection = getConnection(url, payload, method);
288+
connection = getConnection(url, payload, method, headers);
279289
int status = connection.getResponseCode();
280290

281291
if (APIResponse.isSuccessStatus(status)) {
@@ -450,6 +460,20 @@ private boolean isURLAbsolute(String url) {
450460
* @throws IOException io exception
451461
*/
452462
public <P> HttpURLConnection getConnection(final String serviceUrl, final P body, final String requestType) throws IOException {
463+
return getConnection(serviceUrl, body, requestType, new HashMap<>());
464+
}
465+
466+
/**
467+
* Create a HttpURLConnection connection object
468+
*
469+
* @param serviceUrl URL that needs to be requested
470+
* @param body body could not be empty for POST or PUT requests
471+
* @param requestType Request type POST requests without a payload will generate a exception
472+
* @param headers additional headers to set on the request
473+
* @return base class
474+
* @throws IOException io exception
475+
*/
476+
public <P> HttpURLConnection getConnection(final String serviceUrl, final P body, final String requestType, final Map<String, String> headers) throws IOException {
453477
if (requestType == null || !REQUEST_METHODS.contains(requestType)) {
454478
throw new IllegalArgumentException(String.format(REQUEST_METHOD_NOT_ALLOWED, requestType));
455479
}
@@ -485,22 +509,40 @@ public <P> HttpURLConnection getConnection(final String serviceUrl, final P body
485509
DateFormat df = getDateFormat();
486510
mapper.setDateFormat(df);
487511

488-
final String json = mapper.writeValueAsString(body);
489-
connection.getOutputStream().write(json.getBytes(String.valueOf(StandardCharsets.UTF_8)));
512+
setAdditionalHeaders(connection, headers);
513+
514+
byte[] bodyBytes;
515+
if (body instanceof byte[]) {
516+
bodyBytes = (byte[]) body;
517+
} else {
518+
final String json = mapper.writeValueAsString(body);
519+
bodyBytes = json.getBytes(StandardCharsets.UTF_8);
520+
}
521+
connection.getOutputStream().write(bodyBytes);
490522
} else if ("DELETE".equals(requestType)) {
491523
// could have just used rquestType as it is
492524
connection.setDoOutput(false);
493525
connection.setRequestMethod("DELETE");
494526
connection.setRequestProperty("Content-Type", "text/plain");
527+
528+
setAdditionalHeaders(connection, headers);
495529
} else {
496530
connection.setDoOutput(false);
497531
connection.setRequestMethod("GET");
498532
connection.setRequestProperty("Content-Type", "text/plain");
533+
534+
setAdditionalHeaders(connection, headers);
499535
}
500536

501537
return connection;
502538
}
503539

540+
private void setAdditionalHeaders(HttpURLConnection connection, Map<String, String> headers) {
541+
for (Map.Entry<String, String> header : headers.entrySet()) {
542+
connection.setRequestProperty(header.getKey(), header.getValue());
543+
}
544+
}
545+
504546
private DateFormat getDateFormat() {
505547
double javaVersion = DEFAULT_JAVA_VERSION;
506548
try {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.messagebird.objects;
2+
3+
public class FileUploadResponse {
4+
5+
private String id;
6+
7+
public String getId() {
8+
return id;
9+
}
10+
11+
public void setId(String id) {
12+
this.id = id;
13+
}
14+
15+
@Override
16+
public String toString() {
17+
return "FileUploadResponse{" +
18+
"id='" + id + '\'' +
19+
'}';
20+
}
21+
}

api/src/test/java/com/messagebird/MessageBirdClientTest.java

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010
import org.junit.Test;
1111
import org.mockito.Mockito;
1212

13-
import java.io.UnsupportedEncodingException;
13+
import java.io.*;
1414
import java.math.BigInteger;
15-
import java.util.Arrays;
1615
import java.util.Collections;
1716
import java.util.HashMap;
1817
import java.util.LinkedHashMap;
@@ -954,4 +953,93 @@ public void testDeleteRecording() throws NotFoundException, GeneralException, Un
954953
messageBirdClientMock.deleteRecording("ANY_CALL_ID", "ANY_LEG_ID","recordingID");
955954
verify(messageBirdServiceMock, times(1)).deleteByID(url , "recordingID");
956955
}
956+
957+
@Test
958+
public void testMockUploadFile() throws GeneralException, UnauthorizedException {
959+
MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
960+
MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
961+
byte[] binary = {1, 2, 3, 4, 5, 6};
962+
String contentType = "image/png";
963+
String filename = "filename.png";
964+
messageBirdClient.uploadFile(binary, contentType, filename);
965+
String url = MESSAGING_BASE_URL + FILES_PATH;
966+
final Map<String, String> headers = new HashMap<>();
967+
headers.put("Content-Type", contentType);
968+
headers.put("filename", filename);
969+
verify(messageBirdServiceMock, times(1)).sendPayLoad("POST", url, headers, binary, FileUploadResponse.class);
970+
}
971+
972+
@Test
973+
public void testUploadFileWithNullBinary() {
974+
MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
975+
MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
976+
String contentType = "image/png";
977+
String filename = "filename.png";
978+
assertThrows(IllegalArgumentException.class, () -> messageBirdClient.uploadFile(null, contentType, filename));
979+
}
980+
981+
@Test
982+
public void testUploadFileWithNullContentType() {
983+
MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
984+
MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
985+
byte[] binary = {1, 2, 3, 4, 5, 6};
986+
String filename = "filename.png";
987+
assertThrows(IllegalArgumentException.class, () -> messageBirdClient.uploadFile(binary, null, filename));
988+
}
989+
990+
@Test
991+
public void testUploadFileWithNullFilename() throws GeneralException, UnauthorizedException {
992+
MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
993+
MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
994+
byte[] binary = {1, 2, 3, 4, 5, 6};
995+
String contentType = "image/png";
996+
messageBirdClient.uploadFile(binary, contentType, null);
997+
String url = MESSAGING_BASE_URL + FILES_PATH;
998+
final Map<String, String> headers = new HashMap<>();
999+
headers.put("Content-Type", contentType);
1000+
verify(messageBirdServiceMock, times(1)).sendPayLoad("POST", url, headers, binary, FileUploadResponse.class);
1001+
}
1002+
1003+
@Test
1004+
public void testMockDownloadFile() throws GeneralException, UnauthorizedException, NotFoundException {
1005+
MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
1006+
MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
1007+
String id = "8144d3bf-6228-4b0e-903f-022d8917f297";
1008+
String filename = "file.png";
1009+
String basePath = "/base/path";
1010+
messageBirdClient.downloadFile(id, filename, basePath);
1011+
String url = MESSAGING_BASE_URL + FILES_PATH + "/" + id;
1012+
verify(messageBirdServiceMock, times(1)).getBinaryData(url, basePath, filename);
1013+
}
1014+
1015+
@Test
1016+
public void testDownloadFileWithNullId() {
1017+
MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
1018+
MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
1019+
String filename = "file.png";
1020+
String basePath = "/base/path";
1021+
assertThrows(IllegalArgumentException.class, () -> messageBirdClient.downloadFile(null, filename, basePath));
1022+
}
1023+
1024+
@Test
1025+
public void testDownloadFileWithNullFilename() throws GeneralException, UnauthorizedException, NotFoundException {
1026+
MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
1027+
MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
1028+
String id = "8144d3bf-6228-4b0e-903f-022d8917f297";
1029+
String basePath = "/base/path";
1030+
messageBirdClient.downloadFile(id, null, basePath);
1031+
String url = MESSAGING_BASE_URL + FILES_PATH + "/" + id;
1032+
verify(messageBirdServiceMock, times(1)).getBinaryData(url, basePath, id);
1033+
}
1034+
1035+
@Test
1036+
public void testDownloadFileWithNullBasePath() throws GeneralException, UnauthorizedException, NotFoundException {
1037+
MessageBirdService messageBirdServiceMock = mock(MessageBirdService.class);
1038+
MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdServiceMock);
1039+
String id = "8144d3bf-6228-4b0e-903f-022d8917f297";
1040+
String filename = "file.png";
1041+
messageBirdClient.downloadFile(id, filename, null);
1042+
String url = MESSAGING_BASE_URL + FILES_PATH + "/" + id;
1043+
verify(messageBirdServiceMock, times(1)).getBinaryData(url, null, filename);
1044+
}
9571045
}

api/src/test/java/com/messagebird/SpyService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.messagebird.exceptions.GeneralException;
44

5+
import java.util.HashMap;
6+
57
import static org.mockito.Mockito.doReturn;
68
import static org.mockito.Mockito.spy;
79

@@ -147,7 +149,7 @@ MessageBirdService andReturns(final APIResponse apiResponse) throws GeneralExcep
147149
}
148150

149151
MessageBirdServiceImpl messageBirdService = spy(new MessageBirdServiceImpl(getAccessKey()));
150-
doReturn(apiResponse).when(messageBirdService).doRequest(method, url, payload);
152+
doReturn(apiResponse).when(messageBirdService).doRequest(method, url, new HashMap<>(), payload);
151153

152154
return messageBirdService;
153155
}

0 commit comments

Comments
 (0)