diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultAbsSender.java b/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultAbsSender.java index e7cbcec0..b84e012a 100644 --- a/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultAbsSender.java +++ b/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultAbsSender.java @@ -2,7 +2,6 @@ package org.telegram.telegrambots.bots; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.io.FileUtils; import org.apache.http.HttpEntity; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; @@ -14,6 +13,7 @@ import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import org.telegram.telegrambots.facilities.TelegramHttpClientBuilder; +import org.telegram.telegrambots.facilities.filedownloader.TelegramFileDownloader; import org.telegram.telegrambots.meta.api.methods.BotApiMethod; import org.telegram.telegrambots.meta.api.methods.groupadministration.SetChatPhoto; import org.telegram.telegrambots.meta.api.methods.send.*; @@ -36,10 +36,9 @@ import org.telegram.telegrambots.meta.updateshandlers.DownloadFileCallback; import org.telegram.telegrambots.meta.updateshandlers.SentCallback; import java.io.IOException; +import java.io.InputStream; import java.io.Serializable; import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.ExecutorService; @@ -61,6 +60,7 @@ public abstract class DefaultAbsSender extends AbsSender { private final DefaultBotOptions options; private volatile CloseableHttpClient httpClient; private volatile RequestConfig requestConfig; + private final TelegramFileDownloader telegramFileDownloader = new TelegramFileDownloader(this::getBotToken); protected DefaultAbsSender(DefaultBotOptions options) { super(); @@ -109,37 +109,35 @@ public abstract class DefaultAbsSender extends AbsSender { // Send Requests public final java.io.File downloadFile(String filePath) throws TelegramApiException { - if(filePath == null || filePath.isEmpty()){ - throw new TelegramApiException("Parameter file can not be null"); - } - String url = File.getFileUrl(getBotToken(), filePath); - String tempFileName = Long.toString(System.currentTimeMillis()); - return downloadToTemporaryFileWrappingExceptions(url, tempFileName); + return telegramFileDownloader.downloadFile(filePath); } public final java.io.File downloadFile(File file) throws TelegramApiException { - assertParamNotNull(file, "file"); - String url = file.getFileUrl(getBotToken()); - String tempFileName = file.getFileId(); - return downloadToTemporaryFileWrappingExceptions(url, tempFileName); + return telegramFileDownloader.downloadFile(file); + } + + public final java.io.File downloadFile(File file, java.io.File outputFile) throws TelegramApiException { + return telegramFileDownloader.downloadFile(file, outputFile); + } + + public final java.io.File downloadFile(String filePath, java.io.File outputFile) throws TelegramApiException { + return telegramFileDownloader.downloadFile(filePath, outputFile); } public final void downloadFileAsync(String filePath, DownloadFileCallback callback) throws TelegramApiException { - if(filePath == null || filePath.isEmpty()){ - throw new TelegramApiException("Parameter filePath can not be null"); - } - assertParamNotNull(callback, "callback"); - String url = File.getFileUrl(getBotToken(), filePath); - String tempFileName = Long.toString(System.currentTimeMillis()); - exe.submit(getDownloadFileAsyncJob(filePath, callback, url, tempFileName)); + telegramFileDownloader.downloadFileAsync(filePath, callback); } public final void downloadFileAsync(File file, DownloadFileCallback callback) throws TelegramApiException { - assertParamNotNull(file, "file"); - assertParamNotNull(callback, "callback"); - String url = file.getFileUrl(getBotToken()); - String tempFileName = file.getFileId(); - exe.submit(getDownloadFileAsyncJob(file, callback, url, tempFileName)); + telegramFileDownloader.downloadFileAsync(file, callback); + } + + public final InputStream downloadFileAsStream(String filePath) throws TelegramApiException { + return telegramFileDownloader.downloadFileAsStream(filePath); + } + + public final InputStream downloadFileAsStream(File file) throws TelegramApiException { + return telegramFileDownloader.downloadFileAsStream(file); } // Specific Send Requests @@ -720,38 +718,6 @@ public abstract class DefaultAbsSender extends AbsSender { } } - private Runnable getDownloadFileAsyncJob(T fileIdentifier, DownloadFileCallback callback, String url, String tempFileName) { - //noinspection Convert2Lambda - return new Runnable() { - @Override - public void run() { - try { - callback.onResult(fileIdentifier, downloadToTemporaryFile(url, tempFileName)); - } catch (MalformedURLException e) { - callback.onException(fileIdentifier, new TelegramApiException("Wrong url for file: " + url)); - } catch (IOException e) { - callback.onException(fileIdentifier, new TelegramApiRequestException("Error downloading the file", e)); - } - } - }; - } - - private java.io.File downloadToTemporaryFileWrappingExceptions(String url, String tempFileName) throws TelegramApiException { - try { - return downloadToTemporaryFile(url, tempFileName); - } catch (MalformedURLException e) { - throw new TelegramApiException("Wrong url for file: " + url); - } catch (IOException e) { - throw new TelegramApiRequestException("Error downloading the file", e); - } - } - - private java.io.File downloadToTemporaryFile(String url, String tempFileName) throws IOException { - java.io.File output = java.io.File.createTempFile(tempFileName, ".tmp"); - FileUtils.copyURLToFile(new URL(url), output); - return output; - } - private > String sendMethodRequest(Method method) throws TelegramApiValidationException, IOException { method.validate(); String url = getBaseUrl() + method.getMethod(); diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/facilities/filedownloader/DownloadFileException.java b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/filedownloader/DownloadFileException.java new file mode 100644 index 00000000..b458b958 --- /dev/null +++ b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/filedownloader/DownloadFileException.java @@ -0,0 +1,10 @@ +package org.telegram.telegrambots.facilities.filedownloader; + +/** + * Runtime Exception to wrap Exceptions thrown during file download + */ +public class DownloadFileException extends RuntimeException { + public DownloadFileException(final String message, final Throwable e) { + super(message, e); + } +} diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/facilities/filedownloader/TelegramFileDownloader.java b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/filedownloader/TelegramFileDownloader.java new file mode 100644 index 00000000..02c34c9f --- /dev/null +++ b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/filedownloader/TelegramFileDownloader.java @@ -0,0 +1,172 @@ +package org.telegram.telegrambots.facilities.filedownloader; + +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClients; +import org.telegram.telegrambots.meta.api.objects.File; +import org.telegram.telegrambots.meta.exceptions.TelegramApiException; +import org.telegram.telegrambots.meta.updateshandlers.DownloadFileCallback; + +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; + +import static org.apache.commons.io.FileUtils.copyInputStreamToFile; +import static org.apache.http.HttpStatus.SC_OK; + +/** + * Wraps the file downloading code into one class. + * @author Chase22 + * @version 1.0 + */ +public class TelegramFileDownloader { + private final HttpClient httpClient; + private final Supplier botTokenSupplier; + + public TelegramFileDownloader(final Supplier botTokenSupplier) { + this.botTokenSupplier = botTokenSupplier; + httpClient = HttpClients.createDefault(); + } + + public TelegramFileDownloader(final HttpClient httpClient, final Supplier botTokenSupplier) { + this.httpClient = httpClient; + this.botTokenSupplier = botTokenSupplier; + } + + public final java.io.File downloadFile(String filePath) throws TelegramApiException { + String tempFileName = Long.toString(System.currentTimeMillis()); + return downloadFile(filePath, getTempFile(tempFileName)); + } + + public final java.io.File downloadFile(File file) throws TelegramApiException { + return downloadFile(file, getTempFile(file.getFileId())); + } + + public final java.io.File downloadFile(File file, java.io.File outputFile) throws TelegramApiException { + if (file == null) { + throw new TelegramApiException("Parameter file can not be null"); + } + String url = file.getFileUrl(botTokenSupplier.get()); + return downloadToFile(url, outputFile); + } + + public final java.io.File downloadFile(String filePath, java.io.File outputFile) throws TelegramApiException { + if (filePath == null || filePath.isEmpty()) { + throw new TelegramApiException("Parameter file can not be null"); + } + String url = File.getFileUrl(botTokenSupplier.get(), filePath); + return downloadToFile(url, outputFile); + } + + public final InputStream downloadFileAsStream(String filePath) throws TelegramApiException { + try { + return getFileDownloadStreamFuture(File.getFileUrl(botTokenSupplier.get(), filePath)).get(); + } catch (InterruptedException e) { + throw new TelegramApiException("Error downloading file", e); + } catch (ExecutionException e) { + throw new TelegramApiException("Error downloading file", e.getCause()); + } + } + + public final InputStream downloadFileAsStream(File file) throws TelegramApiException { + try { + return getFileDownloadStreamFuture(file.getFileUrl(botTokenSupplier.get())).get(); + } catch (InterruptedException e) { + throw new TelegramApiException("Error downloading file", e); + } catch (ExecutionException e) { + throw new TelegramApiException("Error downloading file", e.getCause()); + } + } + + public final void downloadFileAsync(String filePath, DownloadFileCallback callback) throws TelegramApiException { + if (filePath == null || filePath.isEmpty()) { + throw new TelegramApiException("Parameter filePath can not be null"); + } + if (callback == null) { + throw new TelegramApiException("Parameter callback can not be null"); + } + String url = File.getFileUrl(botTokenSupplier.get(), filePath); + String tempFileName = Long.toString(System.currentTimeMillis()); + + getFileDownloadFuture(url, getTempFile(tempFileName)) + .thenAccept(output -> callback.onResult(filePath, output)) + .exceptionally(throwable -> { + // Unwrap java.util.concurrent.CompletionException + if (throwable instanceof CompletionException) { + callback.onException(filePath, new TelegramApiException("Error downloading file", throwable.getCause())); + } else { + callback.onException(filePath, new TelegramApiException("Error downloading file", throwable)); + } + return null; + }); + } + + public final void downloadFileAsync(File file, DownloadFileCallback callback) throws TelegramApiException { + if (file == null) { + throw new TelegramApiException("Parameter file can not be null"); + } + if (callback == null) { + throw new TelegramApiException("Parameter callback can not be null"); + } + String url = file.getFileUrl(botTokenSupplier.get()); + String tempFileName = file.getFileId(); + + getFileDownloadFuture(url, getTempFile(tempFileName)) + .thenAccept(output -> callback.onResult(file, output)) + .exceptionally(throwable -> { + callback.onException(file, new TelegramApiException("Error downloading file", throwable)); + return null; + }); + + } + + private java.io.File getTempFile(String tempFileName) throws TelegramApiException { + try { + return java.io.File.createTempFile(tempFileName, ".tmp"); + } catch (IOException e) { + throw new TelegramApiException("Error downloading file", e); + } + } + + private java.io.File downloadToFile(String url, java.io.File output) throws TelegramApiException { + try { + return getFileDownloadFuture(url, output).get(); + } catch (InterruptedException e) { + throw new TelegramApiException("File Download got interrupted", e); + } catch (ExecutionException e) { + throw new TelegramApiException("Error downloading file", e.getCause()); + } + } + + private CompletableFuture getFileDownloadFuture(String url, java.io.File output) { + return getFileDownloadStreamFuture(url).thenApply(stream -> { + try { + copyInputStreamToFile(stream, output); + return output; + } catch (IOException e) { + throw new DownloadFileException("Error writing downloaded file", e); + } + }); + } + + private CompletableFuture getFileDownloadStreamFuture(final String url) { + return CompletableFuture.supplyAsync(() -> { + try { + HttpResponse response = httpClient.execute(new HttpGet(url)); + final int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode == SC_OK) { + return response.getEntity().getContent(); + } else { + throw new TelegramApiException("Unexpected Status code while downloading file. Expected 200 got " + statusCode); + } + } catch (IOException | TelegramApiException e) { + throw new DownloadFileException("Error downloading file", e); + } + }); + } + +} diff --git a/telegrambots/src/test/java/org/telegram/telegrambots/test/TelegramFileDownloaderTest.java b/telegrambots/src/test/java/org/telegram/telegrambots/test/TelegramFileDownloaderTest.java new file mode 100644 index 00000000..d185f869 --- /dev/null +++ b/telegrambots/src/test/java/org/telegram/telegrambots/test/TelegramFileDownloaderTest.java @@ -0,0 +1,112 @@ +package org.telegram.telegrambots.test; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.message.BasicStatusLine; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.telegram.telegrambots.facilities.filedownloader.TelegramFileDownloader; +import org.telegram.telegrambots.meta.exceptions.TelegramApiException; +import org.telegram.telegrambots.meta.updateshandlers.DownloadFileCallback; + +import java.io.File; +import java.io.IOException; +import java.util.function.Supplier; + +import static java.nio.charset.Charset.defaultCharset; +import static org.apache.commons.io.FileUtils.readFileToString; +import static org.apache.commons.io.IOUtils.toInputStream; +import static org.apache.http.HttpVersion.HTTP_1_1; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class TelegramFileDownloaderTest { + + private TelegramFileDownloader telegramFileDownloader; + + @Mock + private DownloadFileCallback downloadFileCallbackMock; + + @Mock + private HttpClient httpClientMock; + + @Mock + private HttpResponse httpResponseMock; + + @Mock + private HttpEntity httpEntityMock; + + private Supplier tokenSupplierMock = () -> "someToken"; + + + @Before + public void setup() throws IOException { + + + when(httpResponseMock.getStatusLine()).thenReturn(new BasicStatusLine(HTTP_1_1, 200, "emptyString")); + when(httpResponseMock.getEntity()).thenReturn(httpEntityMock); + + when(httpEntityMock.getContent()).thenReturn(toInputStream("Some File Content", defaultCharset())); + + when(httpClientMock.execute(any(HttpUriRequest.class))).thenReturn(httpResponseMock); + + telegramFileDownloader = new TelegramFileDownloader(httpClientMock, tokenSupplierMock); + } + + @Test + public void testFileDownload() throws TelegramApiException, IOException { + File returnFile = telegramFileDownloader.downloadFile("someFilePath"); + String content = readFileToString(returnFile, defaultCharset()); + + assertEquals("Some File Content", content); + } + + @Test(expected = TelegramApiException.class) + public void testDownloadException() throws TelegramApiException { + when(httpResponseMock.getStatusLine()).thenReturn(new BasicStatusLine(HTTP_1_1, 500, "emptyString")); + + telegramFileDownloader.downloadFile("someFilePath"); + } + + @Test + public void testAsyncDownload() throws TelegramApiException, IOException { + final ArgumentCaptor fileArgumentCaptor = ArgumentCaptor.forClass(File.class); + + telegramFileDownloader.downloadFileAsync("someFilePath", downloadFileCallbackMock); + + verify(downloadFileCallbackMock, timeout(100) + .times(1)) + .onResult(any(), fileArgumentCaptor.capture()); + + String content = readFileToString(fileArgumentCaptor.getValue(), defaultCharset()); + assertEquals("Some File Content", content); + } + + @Test + public void testAsyncException() throws TelegramApiException { + final ArgumentCaptor exceptionArgumentCaptor = ArgumentCaptor.forClass(Exception.class); + + when(httpResponseMock.getStatusLine()).thenReturn(new BasicStatusLine(HTTP_1_1, 500, "emptyString")); + + telegramFileDownloader.downloadFileAsync("someFilePath", downloadFileCallbackMock); + + verify(downloadFileCallbackMock, timeout(100) + .times(1)) + .onException(any(), exceptionArgumentCaptor.capture()); + + Exception e = exceptionArgumentCaptor.getValue(); + assertThat(e, instanceOf(TelegramApiException.class)); + assertEquals(e.getCause().getCause().getMessage(), "Unexpected Status code while downloading file. Expected 200 got 500"); + } + +}