diff --git a/sdks/backend/java/component-api/src/main/java/com/bytechef/component/definition/Context.java b/sdks/backend/java/component-api/src/main/java/com/bytechef/component/definition/Context.java index 9a97c468834..6ce19e3f3dc 100644 --- a/sdks/backend/java/component-api/src/main/java/com/bytechef/component/definition/Context.java +++ b/sdks/backend/java/component-api/src/main/java/com/bytechef/component/definition/Context.java @@ -64,6 +64,14 @@ public interface Context { */ void logger(ContextConsumer logConsumer); + /** + * + * @param mimeTypeContextFunction + * @return + * @param + */ + R mimeType(ContextFunction mimeTypeContextFunction); + /** * * @param outputSchemaFunction @@ -1127,6 +1135,22 @@ interface Logger { void trace(String message, Exception exception); } + interface MimeType { + + /** + * + * @param ext + */ + String lookupMimeType(String ext); + + /** + * + * @param mimeType + */ + String lookupExt(String mimeType); + + } + interface OutputSchema { BaseValueProperty getOutputSchema(Object value); diff --git a/server/libs/core/commons/commons-util/src/main/java/com/bytechef/commons/util/MimeTypeUtils.java b/server/libs/core/commons/commons-util/src/main/java/com/bytechef/commons/util/MimeTypeUtils.java index d0230dd708a..81b6afdd630 100644 --- a/server/libs/core/commons/commons-util/src/main/java/com/bytechef/commons/util/MimeTypeUtils.java +++ b/server/libs/core/commons/commons-util/src/main/java/com/bytechef/commons/util/MimeTypeUtils.java @@ -54,6 +54,8 @@ public class MimeTypeUtils { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; public static final String MIME_APPLICATION_VND_SPREADSHEET = "application/vnd.oasis.opendocument.spreadsheet"; public static final String MIME_APPLICATION_VND_MSPOWERPOINT = "application/vnd.ms-powerpoint"; + public static final String MIME_APPLICATION_VND_PRESENTATION = + "application/vnd.openxmlformats-officedocument.presentationml.presentation"; public static final String MIME_APPLICATION_VND_RNREALMEDIA = "application/vnd.rn-realmedia"; public static final String MIME_APPLICATION_X_BCPIO = "application/x-bcpio"; public static final String MIME_APPLICATION_X_CDLINK = "application/x-cdlink"; @@ -255,6 +257,7 @@ private void put1(String key, String value) { put1("bcpio", MIME_APPLICATION_X_BCPIO); put1("rm", MIME_APPLICATION_VND_RNREALMEDIA); put1("ppt", MIME_APPLICATION_VND_MSPOWERPOINT); + put1("pptx", MIME_APPLICATION_VND_PRESENTATION); put1("mif", MIME_APPLICATION_VND_MIF); put1("grxml", MIME_APPLICATION_SRGS_XML); put1("gram", MIME_APPLICATION_SRGS); @@ -446,7 +449,9 @@ private void put1(String key, String value) { put1(MIME_APPLICATION_MSWORD_2007, "docx"); put1(MIME_APPLICATION_VND_TEXT, "odt"); put1(MIME_APPLICATION_VND_MSEXCEL, "xls"); + put1(MIME_APPLICATION_VND_MSEXCEL_2007, "xlsx"); put1(MIME_APPLICATION_VND_SPREADSHEET, "ods"); + put1(MIME_APPLICATION_VND_PRESENTATION, "pptx"); put1(MIME_APPLICATION_POSTSCRIPT, "ps"); put1(MIME_APPLICATION_PDF, "pdf"); put1(MIME_APPLICATION_OCTET_STREAM, "exe"); diff --git a/server/libs/modules/components/data-stream/src/main/java/com/bytechef/component/datastream/item/DataStreamContextImpl.java b/server/libs/modules/components/data-stream/src/main/java/com/bytechef/component/datastream/item/DataStreamContextImpl.java index d0dcd363c44..7acabb9a855 100644 --- a/server/libs/modules/components/data-stream/src/main/java/com/bytechef/component/datastream/item/DataStreamContextImpl.java +++ b/server/libs/modules/components/data-stream/src/main/java/com/bytechef/component/datastream/item/DataStreamContextImpl.java @@ -57,6 +57,11 @@ public void logger(ContextConsumer loggerConsumer) { actionContext.logger(loggerConsumer); } + @Override + public R mimeType(ContextFunction mimeTypeFunction) { + return actionContext.mimeType(mimeTypeFunction); + } + @Override public R outputSchema(ContextFunction outputSchemaFunction) { return actionContext.outputSchema(outputSchemaFunction); diff --git a/server/libs/modules/components/google/google-drive/src/main/java/com/bytechef/component/google/drive/action/GoogleDriveDownloadFileAction.java b/server/libs/modules/components/google/google-drive/src/main/java/com/bytechef/component/google/drive/action/GoogleDriveDownloadFileAction.java index 22e1542143f..7c7a00c5584 100644 --- a/server/libs/modules/components/google/google-drive/src/main/java/com/bytechef/component/google/drive/action/GoogleDriveDownloadFileAction.java +++ b/server/libs/modules/components/google/google-drive/src/main/java/com/bytechef/component/google/drive/action/GoogleDriveDownloadFileAction.java @@ -20,7 +20,6 @@ import static com.bytechef.component.definition.ComponentDsl.fileEntry; import static com.bytechef.component.definition.ComponentDsl.outputSchema; import static com.bytechef.component.definition.ComponentDsl.string; -import static com.bytechef.component.google.drive.constant.GoogleDriveConstants.APPLICATION_VND_GOOGLE_APPS_FOLDER; import static com.bytechef.component.google.drive.constant.GoogleDriveConstants.FILE_ID; import com.bytechef.component.definition.ActionContext; @@ -63,26 +62,57 @@ public static FileEntry perform( String fileId = inputParameters.getRequiredString(FILE_ID); - Drive.Files files = drive.files(); + Drive.Files.Get get = drive.files() + .get(fileId); - Drive.Files.Get get = files.get(fileId); + File googleFile = get.execute(); - try (InputStream inputStream = get.executeMediaAsInputStream()) { - String fileName = getFileName(files, fileId); + String mimeType = googleFile.getMimeType(); + String fileName = googleFile.getName(); + String exportMimeType = getExportMimeType(mimeType); - return actionContext.file(file -> file.storeContent(fileName, inputStream)); + if (exportMimeType != null) { + try (InputStream inputStream = drive.files() + .export(fileId, exportMimeType) + .executeMediaAsInputStream()) { + + String extension = actionContext.mimeType(mimeType1 -> mimeType1.lookupExt(exportMimeType)); + + String finalFileName = fileName + "." + extension; + + return actionContext.file(file -> file.storeContent(finalFileName, inputStream)); + } + } else { + String fileExtension = googleFile.getFileExtension(); + + if (fileExtension == null || fileExtension.isEmpty()) { + fileExtension = + actionContext.mimeType( + mimeType1 -> mimeType1.lookupExt(mimeType.equals("plain/text") ? "text/plain" : mimeType)); + if (fileExtension != null && !fileName.contains(fileExtension)) { + fileName += "." + fileExtension; + } + } + + try (InputStream inputStream = get.executeMediaAsInputStream()) { + String finalFilename = fileName; + + return actionContext.file(file -> file.storeContent(finalFilename, inputStream)); + } } } - private static String getFileName(Drive.Files files, String fileId) throws IOException { - return files.list() - .setQ("mimeType != '" + APPLICATION_VND_GOOGLE_APPS_FOLDER + "'") - .execute() - .getFiles() - .stream() - .filter(file -> fileId.equals(file.getId())) - .map(File::getName) - .findFirst() - .orElse("fileName"); + private static String getExportMimeType(String mimeType) { + String exportMimeType = null; + + if (mimeType.contains("application/vnd.google-apps.document")) { + exportMimeType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + } else if (mimeType.contains("application/vnd.google-apps.spreadsheet")) { + exportMimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + } else if (mimeType.contains("application/vnd.google-apps.presentation")) { + exportMimeType = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; + } + + return exportMimeType; } } diff --git a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/AbstractGoogleDriveActionTest.java b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/AbstractGoogleDriveActionTest.java index bcd0db946fa..509591a67e8 100644 --- a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/AbstractGoogleDriveActionTest.java +++ b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/AbstractGoogleDriveActionTest.java @@ -43,7 +43,7 @@ public abstract class AbstractGoogleDriveActionTest { protected ArgumentCaptor fileArgumentCaptor = ArgumentCaptor.forClass(File.class); protected ArgumentCaptor fileIdArgumentCaptor = ArgumentCaptor.forClass(String.class); protected MockedStatic googleServicesMockedStatic; - protected ActionContext mockedContext = mock(ActionContext.class); + protected ActionContext mockedActionContext = mock(ActionContext.class); protected Drive.Files.Create mockedCreate = mock(Drive.Files.Create.class); protected Drive.Files.Copy mockedCopy = mock(Drive.Files.Copy.class); protected Drive mockedDrive = mock(Drive.class); diff --git a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveCopyFileActionTest.java b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveCopyFileActionTest.java index 85470cff07f..210a89fd123 100644 --- a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveCopyFileActionTest.java +++ b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveCopyFileActionTest.java @@ -53,7 +53,8 @@ void testPerform() throws IOException { when(mockedCopy.execute()) .thenReturn(testFile); - File copiedFile = GoogleDriveCopyFileAction.perform(mockInputParameters, mockInputParameters, mockedContext); + File copiedFile = + GoogleDriveCopyFileAction.perform(mockInputParameters, mockInputParameters, mockedActionContext); verify(mockedDrive.files()).get("originalFileId"); verify(mockedFiles).copy("originalFileId", testFile); diff --git a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveCreateNewFolderActionTest.java b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveCreateNewFolderActionTest.java index 62aeb519aac..31d7c5b1321 100644 --- a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveCreateNewFolderActionTest.java +++ b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveCreateNewFolderActionTest.java @@ -46,7 +46,7 @@ void testPerform() throws IOException { when(mockedCreate.execute()) .thenReturn(mockedGoogleFile); - File result = GoogleDriveCreateNewFolderAction.perform(mockedParameters, mockedParameters, mockedContext); + File result = GoogleDriveCreateNewFolderAction.perform(mockedParameters, mockedParameters, mockedActionContext); assertEquals(mockedGoogleFile, result); diff --git a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveCreateNewTextFileActionTest.java b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveCreateNewTextFileActionTest.java index c53ded6a508..c124d92ea67 100644 --- a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveCreateNewTextFileActionTest.java +++ b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveCreateNewTextFileActionTest.java @@ -53,7 +53,8 @@ void testPerform() throws IOException { when(mockedCreate.execute()) .thenReturn(mockedGoogleFile); - File result = GoogleDriveCreateNewTextFileAction.perform(mockedParameters, mockedParameters, mockedContext); + File result = + GoogleDriveCreateNewTextFileAction.perform(mockedParameters, mockedParameters, mockedActionContext); assertEquals(mockedGoogleFile, result); diff --git a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveDeleteFileActionTest.java b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveDeleteFileActionTest.java index 6dfcca292ec..e9e1dbd6cc7 100644 --- a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveDeleteFileActionTest.java +++ b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveDeleteFileActionTest.java @@ -49,7 +49,7 @@ void testPerform() throws IOException { .when(mockedDelete) .execute(); - GoogleDriveDeleteFileAction.perform(mockedParameters, mockedParameters, mockedContext); + GoogleDriveDeleteFileAction.perform(mockedParameters, mockedParameters, mockedActionContext); assertEquals("testId", fileIdArgumentCaptor.getValue()); @@ -66,7 +66,7 @@ void testPerformThrowsIOException() throws IOException { .execute(); assertThrows(IOException.class, - () -> GoogleDriveDeleteFileAction.perform(mockedParameters, mockedParameters, mockedContext)); + () -> GoogleDriveDeleteFileAction.perform(mockedParameters, mockedParameters, mockedActionContext)); assertEquals("testId", fileIdArgumentCaptor.getValue()); } diff --git a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveDownloadFileActionTest.java b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveDownloadFileActionTest.java index 1d24ea92e56..cd3b37f0f3c 100644 --- a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveDownloadFileActionTest.java +++ b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveDownloadFileActionTest.java @@ -27,10 +27,8 @@ import com.bytechef.component.test.definition.MockParametersFactory; import com.google.api.services.drive.Drive; import com.google.api.services.drive.model.File; -import com.google.api.services.drive.model.FileList; import java.io.IOException; import java.io.InputStream; -import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -42,37 +40,62 @@ class GoogleDriveDownloadFileActionTest extends AbstractGoogleDriveActionTest { private final InputStream mockedInputStream = mock(InputStream.class); - private final Drive.Files.List mockedList = mock(Drive.Files.List.class); - private final FileList mockedFileList = mock(FileList.class); private final FileEntry mockedFileEntry = mock(FileEntry.class); private final Parameters mockedParameters = MockParametersFactory.create(Map.of(FILE_ID, "fileId")); - private final ArgumentCaptor qArgumentCaptor = ArgumentCaptor.forClass(String.class); + private final Drive.Files.Export mockedExport = mock(Drive.Files.Export.class); + private final ArgumentCaptor exportMimeTypeArgumentCaptor = ArgumentCaptor.forClass(String.class); @Test - void testPerform() throws IOException { + void testPerformWithGoogleDoc() throws IOException { when(mockedFiles.get(fileIdArgumentCaptor.capture())) .thenReturn(mockedGet); + when(mockedGet.execute()) + .thenReturn(new File().setName("testDoc") + .setMimeType("application/vnd.google-apps.document")); + + when(mockedFiles.export(fileIdArgumentCaptor.capture(), exportMimeTypeArgumentCaptor.capture())) + .thenReturn(mockedExport); + when(mockedExport.executeMediaAsInputStream()) + .thenReturn(mockedInputStream); + + when(mockedActionContext.mimeType(any())) + .thenReturn("type"); + when(mockedActionContext.file(any())) + .thenReturn(mockedFileEntry); + + FileEntry result = + GoogleDriveDownloadFileAction.perform(mockedParameters, mockedParameters, mockedActionContext); + + assertEquals(mockedFileEntry, result); + + assertEquals("fileId", fileIdArgumentCaptor.getValue()); + assertEquals( + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + exportMimeTypeArgumentCaptor.getValue()); + } + + @Test + void testPerformWithNonGoogleDoc() throws IOException { + when(mockedFiles.get(fileIdArgumentCaptor.capture())) + .thenReturn(mockedGet); + when(mockedGet.execute()) + .thenReturn(new File().setName("testDoc") + .setMimeType("application/pdf") + .setFileExtension("pdf")); + when(mockedGet.executeMediaAsInputStream()) .thenReturn(mockedInputStream); - when(mockedFiles.list()) - .thenReturn(mockedList); - when(mockedList.setQ(qArgumentCaptor.capture())) - .thenReturn(mockedList); - when(mockedList.execute()) - .thenReturn(mockedFileList); - when(mockedFileList.getFiles()) - .thenReturn(List.of(new File().setId("fileId") - .setName("fileName"))); - - when(mockedContext.file(any())) + when(mockedActionContext.mimeType(any())) + .thenReturn("type"); + when(mockedActionContext.file(any())) .thenReturn(mockedFileEntry); - FileEntry result = GoogleDriveDownloadFileAction.perform(mockedParameters, mockedParameters, mockedContext); + FileEntry result = + GoogleDriveDownloadFileAction.perform(mockedParameters, mockedParameters, mockedActionContext); assertEquals(mockedFileEntry, result); assertEquals("fileId", fileIdArgumentCaptor.getValue()); - assertEquals("mimeType != 'application/vnd.google-apps.folder'", qArgumentCaptor.getValue()); } } diff --git a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveGetFileActionTest.java b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveGetFileActionTest.java index bcc87224ce5..8dcf4e51004 100644 --- a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveGetFileActionTest.java +++ b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveGetFileActionTest.java @@ -48,7 +48,7 @@ void testPerform() throws IOException { when(mockedGet.execute()) .thenReturn(testFile); - File retrievedFile = GoogleDriveGetFileAction.perform(mockedParameters, mockedParameters, mockedContext); + File retrievedFile = GoogleDriveGetFileAction.perform(mockedParameters, mockedParameters, mockedActionContext); verify(mockedDrive.files()).get("testId"); diff --git a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveUploadFileActionTest.java b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveUploadFileActionTest.java index 33e76d71f6b..21c1c85402c 100644 --- a/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveUploadFileActionTest.java +++ b/server/libs/modules/components/google/google-drive/src/test/java/com/bytechef/component/google/drive/action/GoogleDriveUploadFileActionTest.java @@ -50,7 +50,7 @@ void testPerform() throws IOException { .thenReturn("name"); when(mockedFileEntry.getMimeType()) .thenReturn("mimeType"); - when(mockedContext.file(any())) + when(mockedActionContext.file(any())) .thenReturn(mockedFile); when(mockedFiles.create(fileArgumentCaptor.capture(), abstractInputStreamContentArgumentCaptor.capture())) @@ -58,7 +58,7 @@ void testPerform() throws IOException { when(mockedCreate.execute()) .thenReturn(mockedGoogleFile); - File result = GoogleDriveUploadFileAction.perform(mockedParameters, mockedParameters, mockedContext); + File result = GoogleDriveUploadFileAction.perform(mockedParameters, mockedParameters, mockedActionContext); assertEquals(mockedGoogleFile, result); diff --git a/server/libs/platform/platform-component/platform-component-service/src/main/java/com/bytechef/platform/component/definition/ContextImpl.java b/server/libs/platform/platform-component/platform-component-service/src/main/java/com/bytechef/platform/component/definition/ContextImpl.java index aad21deb60c..dfe04fc0d20 100644 --- a/server/libs/platform/platform-component/platform-component-service/src/main/java/com/bytechef/platform/component/definition/ContextImpl.java +++ b/server/libs/platform/platform-component/platform-component-service/src/main/java/com/bytechef/platform/component/definition/ContextImpl.java @@ -17,6 +17,7 @@ package com.bytechef.platform.component.definition; import com.bytechef.commons.util.JsonUtils; +import com.bytechef.commons.util.MimeTypeUtils; import com.bytechef.commons.util.XmlUtils; import com.bytechef.component.definition.Context; import com.bytechef.component.definition.FileEntry; @@ -52,6 +53,7 @@ class ContextImpl implements Context { private final Http http; private final Json json; private final Logger logger; + private final MimeType mimeType; private final OutputSchema outputSchema; private final Xml xml; @@ -65,6 +67,7 @@ public ContextImpl( componentName, componentVersion, componentOperationName, connection, this, httpClientExecutor); this.json = new JsonImpl(); this.logger = new LoggerImpl(componentName, componentOperationName); + this.mimeType = new MimeTypeImpl(); this.outputSchema = new OutputSchemaImpl(); this.xml = new XmlImpl(); } @@ -105,6 +108,15 @@ public void logger(ContextConsumer loggerConsumer) { } } + @Override + public R mimeType(ContextFunction mimeTypeContextFunction) { + try { + return mimeTypeContextFunction.apply(mimeType); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + } + @Override public R outputSchema(ContextFunction outputSchemaFunction) { try { @@ -620,6 +632,18 @@ public BaseProperty.BaseValueProperty getOutputSchema(Object value) { } } + private record MimeTypeImpl() implements MimeType { + @Override + public String lookupMimeType(String ext) { + return MimeTypeUtils.lookupMimeType(ext); + } + + @Override + public String lookupExt(String mimeType) { + return MimeTypeUtils.lookupExt(mimeType); + } + } + private record XmlImpl() implements Xml { @Override