2
\$\begingroup\$

I've created a service that connects to Dropbox and makes it possible to download and upload files, create and list folders and remove file or folder.

Full code is available on GitHub: https://github.com/MajewskiKrzysztof/spring-boot-dropbox

DropboxConfiguration

@Configuration
class DropboxConfiguration {
 @Value("${dropbox.accessToken}")
 private String accessToken;
 @Bean
 public DbxClientV2 dropboxClient() {
 DbxRequestConfig config = DbxRequestConfig.newBuilder("example-app").build();
 return new DbxClientV2(config, accessToken);
 }
}

DropboxServiceImpl

@Service
class DropboxServiceImpl implements DropboxService {
 private final DbxClientV2 client;
 public DropboxServiceImpl(DbxClientV2 client) {
 this.client = client;
 }
 @Override
 public InputStream downloadFile(String filePath) throws DropboxException {
 return handleDropboxAction(() -> client.files().download(filePath).getInputStream(),
 String.format("Error downloading file: %s", filePath));
 }
 @Override
 public FileMetadata uploadFile(String filePath, InputStream fileStream) throws DropboxException {
 return handleDropboxAction(() -> client.files().uploadBuilder(filePath).uploadAndFinish(fileStream),
 String.format("Error uploading file: %s", filePath));
 }
 @Override
 public CreateFolderResult createFolder(String folderPath) {
 return handleDropboxAction(() -> client.files().createFolderV2(folderPath), "Error creating folder");
 }
 @Override
 public FolderMetadata getFolderDetails(String folderPath) throws DropboxException {
 return getMetadata(folderPath, FolderMetadata.class, String.format("Error getting folder details: %s", folderPath));
 }
 @Override
 public FileMetadata getFileDetails(String filePath) throws DropboxException {
 return getMetadata(filePath, FileMetadata.class, String.format("Error getting file details: %s", filePath));
 }
 @Override
 public ListFolderResult listFolder(String folderPath, Boolean recursiveListing, Long limit) throws DropboxException {
 ListFolderBuilder listFolderBuilder = client.files().listFolderBuilder(folderPath);
 if (Objects.nonNull(recursiveListing)) {
 listFolderBuilder.withRecursive(recursiveListing);
 }
 if (Objects.nonNull(limit)) {
 listFolderBuilder.withLimit(limit);
 }
 return handleDropboxAction(listFolderBuilder::start, String.format("Error listing folder: %s", folderPath));
 }
 @Override
 public ListFolderResult listFolderContinue(String cursor) throws DropboxException {
 return handleDropboxAction(() -> client.files().listFolderContinue(cursor), "Error listing folder");
 }
 @Override
 public void deleteFile(String filePath) {
 handleDropboxAction(() -> client.files().deleteV2(filePath), String.format("Error deleting file: %s", filePath));
 }
 @Override
 public void deleteFolder(String folderPath) {
 handleDropboxAction(() -> client.files().deleteV2(folderPath), String.format("Error deleting folder: %s", folderPath));
 }
 private <T> T handleDropboxAction(DropboxActionResolver<T> action, String exceptionMessage) {
 try {
 return action.perform();
 } catch (Exception e) {
 String messageWithCause = String.format("%s with cause: %s", exceptionMessage, e.getMessage());
 throw new DropboxException(messageWithCause, e);
 }
 }
 @SuppressWarnings("unchecked")
 private <T> T getMetadata(String path, Class<T> type, String message) {
 Metadata metadata = handleDropboxAction(() -> client.files().getMetadata(path),
 String.format("Error accessing details of: %s", path));
 checkIfMetadataIsInstanceOfGivenType(metadata, type, message);
 return (T) metadata;
 }
 private <T> void checkIfMetadataIsInstanceOfGivenType(Metadata metadata, Class<T> validType, String exceptionMessage) {
 boolean isValidType = validType.isInstance(metadata);
 if (!isValidType) {
 throw new DropboxException(exceptionMessage);
 }
 }
}

DropboxIntTest

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringBootSharepointRestApplication.class)
public class DropboxIntTest {
 private static final String TEST_FOLDER_PATH = "/Test Folder";
 private static final String TEST_FILE_PATH = String.format("%s/%s", TEST_FOLDER_PATH, "testFile.txt");
 private static final Integer TEST_FILE_SIZE = 17;
 @Autowired
 private DropboxService dropboxService;
 @Rule
 public final ExpectedException exceptions = ExpectedException.none();
 @Rule
 public final TemporaryFolder temporaryFolder = new TemporaryFolder();
 @Before
 public void createTestFolder() throws Exception {
 dropboxService.createFolder(TEST_FOLDER_PATH);
 File tempUploadFile = temporaryFolder.newFile("testFile.txt");
 FileUtils.writeStringToFile(tempUploadFile, "test file content", "UTF-8");
 String testFilePath = String.format("%s/%s", TEST_FOLDER_PATH, "testFile.txt");
 dropboxService.uploadFile(testFilePath, new FileInputStream(tempUploadFile));
 }
 @After
 public void deleteTestFolder() {
 dropboxService.deleteFolder(TEST_FOLDER_PATH);
 }
 @Test
 public void downloadFile_shouldReturnNotEmptyInputStream() throws Exception {
 InputStream inputStream = dropboxService.downloadFile(TEST_FILE_PATH);
 assertThat(inputStream.available()).isEqualTo(TEST_FILE_SIZE);
 }
 @Test
 public void downloadFile_shouldThrowExceptionIfFileNotExists() {
 exceptions.expect(DropboxException.class);
 dropboxService.downloadFile("not-existing-file");
 }
 @Test
 public void uploadFile_shouldReturnUploadedFileDetails() throws Exception {
 File tempUploadFile = temporaryFolder.newFile("teamLogo.png");
 String filePath = String.format("%s/%s", TEST_FOLDER_PATH, tempUploadFile.getName());
 FileMetadata fileMetadata = dropboxService.uploadFile(filePath, new FileInputStream(tempUploadFile));
 assertThat(fileMetadata.getId()).isNotBlank();
 }
 @Test
 public void uploadFile_shouldCreateFolderIfNotExists() throws Exception {
 File tempUploadFile = temporaryFolder.newFile("teamLogo.png");
 String filePath = String.format("%s/%s/%s", TEST_FOLDER_PATH, "not existing folder", tempUploadFile.getName());
 dropboxService.uploadFile(filePath, new FileInputStream(tempUploadFile));
 FolderMetadata folderDetails = dropboxService
 .getFolderDetails(String.format("%s/%s", TEST_FOLDER_PATH, "not existing folder"));
 assertThat(folderDetails.getId()).isNotBlank();
 }
 @Test
 public void createFolder_shouldCreateFolder() {
 String folderPath = String.format("%s/%s", TEST_FOLDER_PATH, "new folder");
 CreateFolderResult folder = dropboxService.createFolder(folderPath);
 FolderMetadata folderDetails = dropboxService.getFolderDetails(folderPath);
 assertThat(folderDetails.getId()).isNotBlank();
 assertThat(folderDetails.getId()).isEqualToIgnoringCase(folder.getMetadata().getId());
 }
 @Test
 public void createFolder_shouldThrowExceptionIfFolderAlreadyExists() {
 String folderPath = String.format("%s/%s", TEST_FOLDER_PATH, "new folder");
 dropboxService.createFolder(folderPath);
 exceptions.expect(DropboxException.class);
 dropboxService.createFolder(folderPath);
 }
 @Test
 public void getFolderDetails_shouldReturnFolderDetails() {
 FolderMetadata folderDetails = dropboxService.getFolderDetails(TEST_FOLDER_PATH);
 assertThat(folderDetails.getId()).isNotBlank();
 assertThat(folderDetails.getName()).isNotBlank();
 assertThat(folderDetails.getPathDisplay()).isNotBlank();
 }
 @Test
 public void getFolderDetails_shouldThrowExceptionIfFolderNotExists() {
 exceptions.expect(DropboxException.class);
 dropboxService.getFolderDetails("/not existing folder");
 }
 @Test
 public void getFileDetails_shouldReturnFileDetails() {
 FileMetadata fileDetails = dropboxService.getFileDetails(TEST_FILE_PATH);
 assertThat(fileDetails.getId()).isNotBlank();
 assertThat(fileDetails.getPathDisplay()).isNotBlank();
 assertThat(fileDetails.getName()).isNotBlank();
 assertThat(fileDetails.getSize()).isEqualTo(TEST_FILE_SIZE.longValue());
 assertThat(fileDetails.getServerModified()).isNotNull();
 assertThat(fileDetails.getServerModified()).isBefore(new Date());
 }
 @Test
 public void getFileDetails_shouldThrowExceptionIfFileNotExists() {
 exceptions.expect(DropboxException.class);
 dropboxService.getFileDetails("/not-existing-file.pdf");
 }
 @Test
 public void listFolder_shouldReturnFolderItems() throws Exception {
 File tempUploadFile1 = temporaryFolder.newFile("testFile2.txt");
 FileUtils.writeStringToFile(tempUploadFile1, "test file content", "UTF-8");
 String testFilePath1 = String.format("%s/%s", TEST_FOLDER_PATH, "testFile2.txt");
 dropboxService.uploadFile(testFilePath1, new FileInputStream(tempUploadFile1));
 File tempUploadFile2 = temporaryFolder.newFile("testFile3.txt");
 FileUtils.writeStringToFile(tempUploadFile2, "test file content", "UTF-8");
 String testFilePath2 = String.format("%s/%s/%s", TEST_FOLDER_PATH, "inner folder", "testFile3.txt");
 dropboxService.uploadFile(testFilePath2, new FileInputStream(tempUploadFile2));
 ListFolderResult listFolderResult = dropboxService.listFolder(TEST_FOLDER_PATH, true, 10L);
 assertThat(listFolderResult.getEntries()).hasSize(5);
 List<FileMetadata> files = listFolderResult.getEntries().stream()
 .filter(entity -> entity instanceof FileMetadata)
 .map(entity -> (FileMetadata) entity)
 .collect(Collectors.toList());
 assertThat(files).hasSize(3);
 List<FolderMetadata> folders = listFolderResult.getEntries().stream()
 .filter(entity -> entity instanceof FolderMetadata)
 .map(entity -> (FolderMetadata) entity)
 .collect(Collectors.toList());
 assertThat(folders).hasSize(2);
 }
 @Test
 public void listFolder_shouldTrowExceptionIfFolderNotExists() {
 exceptions.expect(DropboxException.class);
 dropboxService.listFolder("/not existing folder", true, 10L);
 }
 @Test
 public void listFolderContinue_shouldListNextPathOfItems() throws Exception {
 File tempUploadFile = temporaryFolder.newFile("testFile2.txt");
 FileUtils.writeStringToFile(tempUploadFile, "test file content", "UTF-8");
 String testFilePath1 = String.format("%s/%s", TEST_FOLDER_PATH, "testFile2.txt");
 dropboxService.uploadFile(testFilePath1, new FileInputStream(tempUploadFile));
 ListFolderResult listFolderResult = dropboxService.listFolder(TEST_FOLDER_PATH, false, 1L);
 assertThat(listFolderResult.getEntries()).hasSize(1);
 String cursor = listFolderResult.getCursor();
 listFolderResult = dropboxService.listFolderContinue(cursor);
 assertThat(listFolderResult.getEntries()).hasSize(1);
 cursor = listFolderResult.getCursor();
 listFolderResult = dropboxService.listFolderContinue(cursor);
 assertThat(listFolderResult.getEntries()).hasSize(0);
 }
 @Test
 public void listFolderContinue_shouldThrowExceptionIfWrongCursorProvided() {
 exceptions.expect(DropboxException.class);
 dropboxService.listFolderContinue(UUID.randomUUID().toString());
 }
 @Test
 public void deleteFile_shouldDeleteFile() {
 FileMetadata fileDetails = dropboxService.getFileDetails(TEST_FILE_PATH);
 assertThat(fileDetails.getId()).isNotBlank();
 dropboxService.deleteFile(TEST_FILE_PATH);
 exceptions.expect(DropboxException.class);
 dropboxService.getFileDetails(TEST_FILE_PATH);
 }
 @Test
 public void deleteFile_shouldThrowExceptionIfFileNotExists() {
 exceptions.expect(DropboxException.class);
 dropboxService.deleteFolder("/not-existing-file");
 }
 @Test
 public void deleteFolder_shouldDeleteFolder() {
 String testFolder = String.format("%s/%s", TEST_FOLDER_PATH, "test folder");
 dropboxService.createFolder(testFolder);
 FolderMetadata folderDetails = dropboxService.getFolderDetails(testFolder);
 assertThat(folderDetails.getId()).isNotBlank();
 dropboxService.deleteFolder(testFolder);
 exceptions.expect(DropboxException.class);
 dropboxService.getFolderDetails(testFolder);
 }
 @Test
 public void deleteFolder_shouldThrowExceptionIfFolderNotExists() {
 exceptions.expect(DropboxException.class);
 dropboxService.deleteFolder("/not-existing-folder");
 }
}

DropboxException

public class DropboxException extends RuntimeException {
 public DropboxException(String message) {
 super(message);
 }
 public DropboxException(String message, Exception cause) {
 super(message, cause);
 }
}

DropboxActionResolver

@FunctionalInterface
interface DropboxActionResolver<T> {
 T perform() throws Exception;
}

What do you think of it?

asked Jul 30, 2018 at 10:25
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

For the most part it looks fine. There are a few parts that may be changed. For example, I think that the handle handleDropboxAction method and the usage of the DropboxActionResolver functional interface might be a bit of an overkill.

You could achieve the exact same behavior without creating a custom interface to essentially wrap it an try-catch clause.

listFolder arguments could be made primitive (to avoid the null checks).

Your various throws DropboxException declaration could be omitted.

answered Jul 30, 2018 at 13:39
\$\endgroup\$
2
  • \$\begingroup\$ Hey, thanks for your review. I will apply some of your suggestions. I used 'DropboxActionResolver' because some of Dropbox API methods throw checked exceptions and this can't be used with standard functional interfaces \$\endgroup\$ Commented Jul 30, 2018 at 13:56
  • \$\begingroup\$ That make sense, but you can always create a wrapper exception object that will extend RuntimeException to propagate an exception outside of a lambda body. \$\endgroup\$ Commented Jul 30, 2018 at 14:04

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.