From 5f19e4eeeeade177a61c2a73460d45f181386fd8 Mon Sep 17 00:00:00 2001 From: Trung Mai Date: Thu, 15 Aug 2024 13:43:08 +0700 Subject: [PATCH] TE-644: Fix review code to release first version --- .github/ISSUE_TEMPLATE/bug_report.md | 40 + .github/ISSUE_TEMPLATE/feature_request.md | 22 + .github/workflows/ci.yml | 2 + .github/workflows/dev.yml | 2 + CODE_OF_CONDUCT.md | 24 + README.md | 2 +- .../.settings/ch.ivyteam.ivy.designer.prefs | 4 +- .../config/variables.yaml | 11 + .../connector/azure/blob/demo/Data.ivyClass | 2 + .../azure/blob/demo/UploadData.ivyClass | 2 + azure-blob-connector-demo/pom.xml | 55 +- .../processes/Start Processes/Upload.p.json | 6 +- .../connector/azure/blob/demo/bean/Blob.java | 21 + .../azure/blob/demo/bean/UploadBean.java | 318 +++++ .../demo/bean/UploadByCallSubprocess.java | 233 ++++ .../azure/blob/demo/utils/UploadUtils.java | 21 + .../blob/demo/Upload/Upload.rddescriptor | 7 + .../azure/blob/demo/Upload/Upload.xhtml | 158 +++ .../blob/demo/Upload/UploadData.ivyClass | 6 + .../blob/demo/Upload/UploadProcess.p.json | 447 +++++++ .../UploadByCallSubprocess.rddescriptor | 7 + .../UploadByCallSubprocess.xhtml | 156 +++ .../UploadByCallSubprocessData.ivyClass | 12 + .../UploadByCallSubprocessProcess.p.json | 1065 +++++++++++++++++ .../layouts/frame-10-full-width.xhtml | 59 + .../webContent/layouts/styles/upload-view.css | 36 + azure-blob-connector-product/.classpath | 22 + .../.settings/ch.ivyteam.ivy.designer.prefs | 4 +- .../org.eclipse.wst.common.component | 28 +- azure-blob-connector-product/README.md | 58 +- azure-blob-connector-product/README_DE.md | 168 +++ .../images/DevAccountKey.png | Bin 0 -> 8290 bytes azure-blob-connector-product/pom.xml | 130 +- azure-blob-connector-product/product.json | 10 +- azure-blob-connector-test/.gitignore | 1 + .../.settings/ch.ivyteam.ivy.designer.prefs | 4 +- azure-blob-connector-test/pom.xml | 16 +- .../resource_test/picture/singapore.png | Bin 9197 -> 7317 bytes .../integration/AbstractIntegrationTest.java | 67 ++ .../AzureBlobStorageServiceTest.java | 131 ++ .../.settings/ch.ivyteam.ivy.designer.prefs | 4 +- .../azure/blob/BlobStorageData.ivyClass | 15 + azure-blob-connector/pom.xml | 4 +- .../processes/BlobStorage.p.json | 4 +- .../azure/blob/BlobServiceClientHelper.java | 66 + .../connector/azure/blob/StorageService.java | 128 ++ .../internal/AzureBlobStorageService.java | 252 ++++ .../internal/bean/AzureBlobStorageBean.java | 36 + .../blob/internal/helper/BlobSASHelper.java | 32 + .../webContent/icon/azure-blob-icon.png | Bin 2140 -> 2040 bytes pom.xml | 6 +- 51 files changed, 3768 insertions(+), 136 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 azure-blob-connector-demo/dataclasses/com/axonivy/connector/azure/blob/demo/Data.ivyClass create mode 100644 azure-blob-connector-demo/dataclasses/com/axonivy/connector/azure/blob/demo/UploadData.ivyClass create mode 100644 azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/bean/Blob.java create mode 100644 azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/bean/UploadBean.java create mode 100644 azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/bean/UploadByCallSubprocess.java create mode 100644 azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/utils/UploadUtils.java create mode 100644 azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/Upload.rddescriptor create mode 100644 azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/Upload.xhtml create mode 100644 azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/UploadData.ivyClass create mode 100644 azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/UploadProcess.p.json create mode 100644 azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocess.rddescriptor create mode 100644 azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocess.xhtml create mode 100644 azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocessData.ivyClass create mode 100644 azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocessProcess.p.json create mode 100644 azure-blob-connector-demo/webContent/layouts/frame-10-full-width.xhtml create mode 100644 azure-blob-connector-demo/webContent/layouts/styles/upload-view.css create mode 100644 azure-blob-connector-product/.classpath create mode 100644 azure-blob-connector-product/README_DE.md create mode 100644 azure-blob-connector-product/images/DevAccountKey.png create mode 100644 azure-blob-connector-test/src_test/com/axonivy/connector/azure/blob/test/integration/AbstractIntegrationTest.java create mode 100644 azure-blob-connector-test/src_test/com/axonivy/connector/azure/blob/test/integration/AzureBlobStorageServiceTest.java create mode 100644 azure-blob-connector/dataclasses/com/axonivy/connector/azure/blob/BlobStorageData.ivyClass create mode 100644 azure-blob-connector/src/com/axonivy/connector/azure/blob/BlobServiceClientHelper.java create mode 100644 azure-blob-connector/src/com/axonivy/connector/azure/blob/StorageService.java create mode 100644 azure-blob-connector/src/com/axonivy/connector/azure/blob/internal/AzureBlobStorageService.java create mode 100644 azure-blob-connector/src/com/axonivy/connector/azure/blob/internal/bean/AzureBlobStorageBean.java create mode 100644 azure-blob-connector/src/com/axonivy/connector/azure/blob/internal/helper/BlobSASHelper.java diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..1d8d93f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +Dear @ivy-sgi, we have found the following bug: + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..3285507 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +Dear @ivy-sgi, it would be cool to have the following feature in the market place: + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87d78f0..596401f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,3 +10,5 @@ on: jobs: build: uses: axonivy-market/github-workflows/.github/workflows/ci.yml@v4 + secrets: + mvnArgs: -Dazureblob.account=${{ secrets.AZURE_BLOB_ACCOUNT }} -Dazureblob.key=${{ secrets.AZURE_BLOB_ACCOUNT_KEY }} diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index c2fee37..b672da0 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -9,3 +9,5 @@ on: jobs: build: uses: axonivy-market/github-workflows/.github/workflows/dev.yml@v4 + secrets: + mvnArgs: -Dazureblob.account=${{ secrets.AZURE_BLOB_ACCOUNT }} -Dazureblob.key=${{ secrets.AZURE_BLOB_ACCOUNT_KEY }} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..ec0fe32 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,24 @@ +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. +As part of the Ricoh Group, Axon Ivy is guided by [The spirit of the three loves](https://www.ricoh.com/about/ricoh-way): + +- **Love your neighbor** ๐Ÿค +We love to get in touch with people and are willing to help others when we are aware of their issues and ideas. Everyone who participates as a user or contributor in this repository is our neighbor. + +- **Love your country** ๐Ÿ—บ +We love the place weโ€™re located at and enjoy the nature around us. We take care of the environment and are eager to learn from cultures around the globe. + +- **Love your work** ๐Ÿ‘ทโ€โ™‚๏ธ +We are passionate developers, eager to work with new technologies, and are happy to be part of the digital transformation. We love to be creative at work and see our visions accomplished. + +## Our Guidelines + +This repository is intended to facilitate a friendly and inspiring exchange in which we focus on technical content. + +- Be friendly and patient. +- Be welcoming. +- Be considerate. +- Be respectful. +- Be careful in the words that you choose. +- When we disagree, try to understand why. diff --git a/README.md b/README.md index b7dc1a8..ff60a5a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Azure Blob Connector -[![CI Build](https://github.com/axonivy-professional-services/market-azure-blob-connector/actions/workflows/ci.yml/badge.svg)](https://github.com/axonivy-professional-services/market-market-azure-blob-connector/actions/workflows/ci.yml) +[![CI Build](https://github.com/axonivy-market/azure-blob-connector/actions/workflows/ci.yml/badge.svg)](https://github.com/axonivy-market/azure-blob-connector/actions/workflows/ci.yml) - Upload file to Azure blob - Get temporary download link diff --git a/azure-blob-connector-demo/.settings/ch.ivyteam.ivy.designer.prefs b/azure-blob-connector-demo/.settings/ch.ivyteam.ivy.designer.prefs index 6ea0e31..e93fcf9 100644 --- a/azure-blob-connector-demo/.settings/ch.ivyteam.ivy.designer.prefs +++ b/azure-blob-connector-demo/.settings/ch.ivyteam.ivy.designer.prefs @@ -1,5 +1,5 @@ -ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_DATA_CLASS=com.axonivy.cloud.storage.demo.Data -ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_NAMESPACE=com.axonivy.cloud.storage.demo +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_DATA_CLASS=com.axonivy.connector.azure.blob.demo.Data +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_NAMESPACE=com.axonivy.connector.azure.blob.demo ch.ivyteam.ivy.project.preferences\:PRIMEFACES_VERSION=11 ch.ivyteam.ivy.project.preferences\:PROJECT_VERSION=100000 eclipse.preferences.version=1 diff --git a/azure-blob-connector-demo/config/variables.yaml b/azure-blob-connector-demo/config/variables.yaml index 99a327a..245f493 100644 --- a/azure-blob-connector-demo/config/variables.yaml +++ b/azure-blob-connector-demo/config/variables.yaml @@ -3,3 +3,14 @@ # You can define here your project Variables. # Variables: + AzureBlob: + # The application ID that's assigned to your app. + ClientId: '' + # The client secret that you generated for your app in the app registration portal. + ClientSecret: '' + # The directory tenant the application plans to operate against, in GUID or domain-name format. + TenantId: '' + # https://.blob.core.windows.net/ + EndPoint: '' + # Your container name. + ContainterName: '' \ No newline at end of file diff --git a/azure-blob-connector-demo/dataclasses/com/axonivy/connector/azure/blob/demo/Data.ivyClass b/azure-blob-connector-demo/dataclasses/com/axonivy/connector/azure/blob/demo/Data.ivyClass new file mode 100644 index 0000000..82cb1e8 --- /dev/null +++ b/azure-blob-connector-demo/dataclasses/com/axonivy/connector/azure/blob/demo/Data.ivyClass @@ -0,0 +1,2 @@ +Data #class +com.axonivy.connector.azure.blob.demo #namespace diff --git a/azure-blob-connector-demo/dataclasses/com/axonivy/connector/azure/blob/demo/UploadData.ivyClass b/azure-blob-connector-demo/dataclasses/com/axonivy/connector/azure/blob/demo/UploadData.ivyClass new file mode 100644 index 0000000..39a8304 --- /dev/null +++ b/azure-blob-connector-demo/dataclasses/com/axonivy/connector/azure/blob/demo/UploadData.ivyClass @@ -0,0 +1,2 @@ +UploadData #class +com.axonivy.connector.azure.blob.demo #namespace diff --git a/azure-blob-connector-demo/pom.xml b/azure-blob-connector-demo/pom.xml index 16e68d8..a632b99 100644 --- a/azure-blob-connector-demo/pom.xml +++ b/azure-blob-connector-demo/pom.xml @@ -1,29 +1,30 @@ - - 4.0.0 - com.axonivy.cloud.storage - azure-blob-connector-demo - 10.0.21-SNAPSHOT - iar - - 10.0.16 - - - - com.axonivy.cloud.storage - azure-blob-connector - ${project.version} - iar - - - - - - com.axonivy.ivy.ci - project-build-plugin - ${project.build.plugin.version} - true - - - + + 4.0.0 + com.axonivy.connector.azure.blob + azure-blob-connector-demo + 10.0.22-SNAPSHOT + iar + + 10.0.16 + + + + com.axonivy.connector.azure.blob + azure-blob-connector + ${project.version} + iar + + + + + + com.axonivy.ivy.ci + project-build-plugin + ${project.build.plugin.version} + true + + + diff --git a/azure-blob-connector-demo/processes/Start Processes/Upload.p.json b/azure-blob-connector-demo/processes/Start Processes/Upload.p.json index 34fb7c5..1c02aa5 100644 --- a/azure-blob-connector-demo/processes/Start Processes/Upload.p.json +++ b/azure-blob-connector-demo/processes/Start Processes/Upload.p.json @@ -2,7 +2,7 @@ "format" : "10.0.0", "id" : "19010D5E49BD2F7F", "config" : { - "data" : "com.axonivy.cloud.storage.demo.UploadData" + "data" : "com.axonivy.connector.azure.blob.demo.UploadData" }, "elements" : [ { "id" : "f0", @@ -28,7 +28,7 @@ "type" : "DialogCall", "name" : "Upload", "config" : { - "dialogId" : "com.axonivy.cloud.storage.demo.Upload", + "dialogId" : "com.axonivy.connector.azure.blob.demo.Upload", "startMethod" : "start()" }, "visual" : { @@ -54,7 +54,7 @@ "type" : "DialogCall", "name" : "Upload", "config" : { - "dialogId" : "com.axonivy.cloud.storage.demo.UploadByCallSubprocess", + "dialogId" : "com.axonivy.connector.azure.blob.demo.UploadByCallSubprocess", "startMethod" : "start()" }, "visual" : { diff --git a/azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/bean/Blob.java b/azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/bean/Blob.java new file mode 100644 index 0000000..3c4f2e0 --- /dev/null +++ b/azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/bean/Blob.java @@ -0,0 +1,21 @@ +package com.axonivy.connector.azure.blob.demo.bean; + +import com.azure.storage.blob.models.BlobItem; + +public class Blob { + private BlobItem blobItem; + private String linkDownLoad; + + public BlobItem getBlobItem() { + return blobItem; + } + public void setBlobItem(BlobItem BlobItem) { + this.blobItem = BlobItem; + } + public String getLinkDownLoad() { + return linkDownLoad; + } + public void setLinkDownLoad(String linkDownLoad) { + this.linkDownLoad = linkDownLoad; + } +} diff --git a/azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/bean/UploadBean.java b/azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/bean/UploadBean.java new file mode 100644 index 0000000..a3cb4f8 --- /dev/null +++ b/azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/bean/UploadBean.java @@ -0,0 +1,318 @@ +package com.axonivy.connector.azure.blob.demo.bean; + +import static org.apache.commons.lang3.StringUtils.isNotEmpty; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.primefaces.model.file.UploadedFile; + +import com.axonivy.connector.azure.blob.BlobServiceClientHelper; +import com.axonivy.connector.azure.blob.StorageService; +import com.axonivy.connector.azure.blob.demo.utils.UploadUtils; +import com.axonivy.connector.azure.blob.internal.AzureBlobStorageService; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.models.BlobItem; + +import ch.ivyteam.ivy.environment.Ivy; + +public class UploadBean { + private String url; + private String localPath; + private UploadedFile uploadedFile; + private String fileName; + private String fileNamePath; + private String fileNameURL; + private byte[] content; + private InputStream inputStreamContent; + private List blobs; + private String uploadToFolderByPrimefaces; + private String uploadToFolderByLocalPath; + private String uploadToFolderByURL; + private Date startDate; + private String blobName; + private Boolean isFileAlreadyExist; + private Boolean isOverwriteFile; + + private Boolean isFileAlreadyExistURL; + private Boolean isOverwriteFileURL; + + private Boolean isFileAlreadyExistPath; + private Boolean isOverwriteFilePath; + + private StorageService storageService = null; + private static BlobServiceClient blobServiceClient = null; + + + public void init() { + String clientId = Ivy.var().get("AzureBlob.ClientId"); + String clientSecret = Ivy.var().get("AzureBlob.ClientSecret"); + String tenantId = Ivy.var().get("AzureBlob.TenantId"); + String endPoint = Ivy.var().get("AzureBlob.EndPoint"); + String containerName = Ivy.var().get("AzureBlob.ContainterName"); + + blobServiceClient = BlobServiceClientHelper.getBlobServiceClient(clientId, clientSecret, tenantId, endPoint); + storageService = new AzureBlobStorageService(blobServiceClient, containerName); + this.blobs = getAllBlobs(); + isFileAlreadyExist = false; + isFileAlreadyExistURL = false; + isFileAlreadyExistPath = false; + } + + public String getUrl() { + return url; + } + public void setUrl(String url) { + this.url = url; + } + public String getLocalPath() { + return localPath; + } + public void setLocalPath(String localPath) { + this.localPath = localPath; + } + public UploadedFile getUploadedFile() { + return uploadedFile; + } + public void setUploadedFile(UploadedFile uploadedFile) { + this.uploadedFile = uploadedFile; + } + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public List getBlobs() { + return blobs; + } + + public void setBlobs(List blobs) { + this.blobs = blobs; + } + + public byte[] getContent() { + return content; + } + + public void setContent(byte[] content) { + this.content = content; + } + + public InputStream getInputStreamContent() { + return inputStreamContent; + } + + public void setInputStreamContent(InputStream inputStreamContent) { + this.inputStreamContent = inputStreamContent; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getFileNamePath() { + return fileNamePath; + } + + public void setFileNamePath(String fileNamePath) { + this.fileNamePath = fileNamePath; + } + + public String getFileNameURL() { + return fileNameURL; + } + + public void setFileNameURL(String fileNameURL) { + this.fileNameURL = fileNameURL; + } + + public String getBlobName() { + return blobName; + } + + public void setBlobName(String blobName) { + this.blobName = blobName; + } + + public Boolean getIsFileAlreadyExist() { + return isFileAlreadyExist; + } + + public void setIsFileAlreadyExist(Boolean isFileAlreadyExist) { + this.isFileAlreadyExist = isFileAlreadyExist; + } + + public Boolean getIsOverwriteFile() { + return isOverwriteFile; + } + + public void setIsOverwriteFile(Boolean isOverwriteFile) { + this.isOverwriteFile = isOverwriteFile; + } + + public Boolean getIsFileAlreadyExistURL() { + return isFileAlreadyExistURL; + } + + public void setIsFileAlreadyExistURL(Boolean isFileAlreadyExistURL) { + this.isFileAlreadyExistURL = isFileAlreadyExistURL; + } + + public Boolean getIsOverwriteFileURL() { + return isOverwriteFileURL; + } + + public void setIsOverwriteFileURL(Boolean isOverwriteFileURL) { + this.isOverwriteFileURL = isOverwriteFileURL; + } + + public Boolean getIsFileAlreadyExistPath() { + return isFileAlreadyExistPath; + } + + public void setIsFileAlreadyExistPath(Boolean isFileAlreadyExistPath) { + this.isFileAlreadyExistPath = isFileAlreadyExistPath; + } + + public Boolean getIsOverwriteFilePath() { + return isOverwriteFilePath; + } + + public void setIsOverwriteFilePath(Boolean isOverwriteFilePath) { + this.isOverwriteFilePath = isOverwriteFilePath; + } + + public StorageService getStorageService() { + return storageService; + } + + public void setStorageService(StorageService storageService) { + this.storageService = storageService; + } + + public String getUploadToFolderByPrimefaces() { + return uploadToFolderByPrimefaces; + } + + public void setUploadToFolderByPrimefaces(String uploadToFolderByPrimefaces) { + this.uploadToFolderByPrimefaces = uploadToFolderByPrimefaces; + } + + public String getUploadToFolderByLocalPath() { + return uploadToFolderByLocalPath; + } + + public void setUploadToFolderByLocalPath(String uploadToFolderByLocalPath) { + this.uploadToFolderByLocalPath = uploadToFolderByLocalPath; + } + + public String getUploadToFolderByURL() { + return uploadToFolderByURL; + } + + public void setUploadToFolderByURL(String uploadToFolderByURL) { + this.uploadToFolderByURL = uploadToFolderByURL; + } + + public void upload() throws Exception { + boolean isExistFile = checkFileAlreadyExist(fileName); + if (!isExistFile || isOverwriteFile) { + try { + String name = storageService.upload(content, fileName, uploadToFolderByPrimefaces, isOverwriteFile); + if (StringUtils.isNotEmpty(name)) { + fetchAllBlobsAndAddMessage("Uploaded blobs successfully"); + } + } catch (Exception e) { + throw new Exception("upload file error " + e.getMessage(), e); + } + } else { + isFileAlreadyExist = true; + } + } + + public void uploadFromURL() { + fileNameURL = UploadUtils.getFileNameFromUrl(url); + boolean isExistFile = checkFileAlreadyExist(fileNameURL); + if (!isExistFile || isOverwriteFileURL) { + String name = storageService.uploadFromUrl(url, uploadToFolderByURL, isOverwriteFileURL); + if (StringUtils.isNotEmpty(name)) { + fetchAllBlobsAndAddMessage("Uploaded blobs successfully"); + } + } else { + isFileAlreadyExistURL = true; + } + } + + public void uploadFromPath() { + fileNamePath = FilenameUtils.getName(localPath); + boolean isExistFile = checkFileAlreadyExist(fileNamePath); + if (!isExistFile || isOverwriteFilePath) { + String name = storageService.uploadFromFile(localPath, uploadToFolderByLocalPath, isOverwriteFilePath); + if (isNotEmpty(name)) { + fetchAllBlobsAndAddMessage("Uploaded blobs successfully"); + } + } else { + isFileAlreadyExistPath = true; + } + } + + public void deleteBlobs(Date date) { + storageService.delete(date); + fetchAllBlobsAndAddMessage("Delete blobs successfully"); + } + + public void deleteBlob(String blobName) { + if (storageService.delete(blobName)) { + fetchAllBlobsAndAddMessage("Delete blobs successfully"); + } + } + + public void undeleteBlob(String blobName) { + storageService.restore(blobName); + fetchAllBlobsAndAddMessage("Undelete blobs successfully"); + } + + public void showCopiedMessage() { + doAddInfoMessageForInstance("Download link is copied"); + } + + private void fetchAllBlobsAndAddMessage(String message) { + this.blobs = getAllBlobs(); + doAddInfoMessageForInstance(message); + } + + private List getAllBlobs() { + List blobItems = storageService.getBlobs(); + List blobs = new ArrayList<>(); + for (BlobItem item : blobItems) { + Blob b = new Blob(); + b.setBlobItem(item); + b.setLinkDownLoad(storageService.getDownloadLink(item.getName())); + blobs.add(b); + } + return blobs; + } + + private Boolean checkFileAlreadyExist(String name) { + return storageService.getBlobs().stream().anyMatch(b -> b.getName().equals(name)); + } + + private void doAddInfoMessageForInstance(String message) { + FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, message, null)); + } +} + diff --git a/azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/bean/UploadByCallSubprocess.java b/azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/bean/UploadByCallSubprocess.java new file mode 100644 index 0000000..54910fb --- /dev/null +++ b/azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/bean/UploadByCallSubprocess.java @@ -0,0 +1,233 @@ +package com.axonivy.connector.azure.blob.demo.bean; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; + +import org.apache.commons.lang3.StringUtils; +import org.primefaces.model.file.UploadedFile; + +import com.azure.storage.blob.models.BlobItem; + +public class UploadByCallSubprocess { + private String url; + private String localPath; + private UploadedFile uploadedFile; + private String fileName; + private String fileNamePath; + private String fileNameURL; + private byte[] content; + private InputStream inputStreamContent; + private List blobs; + private List blobItems; + private String uploadToFolderByPrimefaces; + private String uploadToFolderByLocalPath; + private String uploadToFolderByURL; + private Date startDate; + private String blobName; + private Boolean isFileAlreadyExist; + private Boolean isOverwriteFile; + + private Boolean isFileAlreadyExistURL; + private Boolean isOverwriteFileURL; + + private Boolean isFileAlreadyExistPath; + private Boolean isOverwriteFilePath; + + public void init() { + isFileAlreadyExist = false; + isFileAlreadyExistURL = false; + isFileAlreadyExistPath = false; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getLocalPath() { + return localPath; + } + + public void setLocalPath(String localPath) { + this.localPath = localPath; + } + + public UploadedFile getUploadedFile() { + return uploadedFile; + } + + public void setUploadedFile(UploadedFile uploadedFile) { + this.uploadedFile = uploadedFile; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public List getBlobs() { + return blobs; + } + + public void setBlobs(List blobs) { + this.blobs = blobs; + } + + public List getBlobItems() { + return blobItems; + } + + public void setBlobItems(List blobItems) { + this.blobItems = blobItems; + } + + public byte[] getContent() { + return content; + } + + public void setContent(byte[] content) { + this.content = content; + } + + public InputStream getInputStreamContent() { + return inputStreamContent; + } + + public void setInputStreamContent(InputStream inputStreamContent) { + this.inputStreamContent = inputStreamContent; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getFileNamePath() { + return fileNamePath; + } + + public void setFileNamePath(String fileNamePath) { + this.fileNamePath = fileNamePath; + } + + public String getFileNameURL() { + return fileNameURL; + } + + public void setFileNameURL(String fileNameURL) { + this.fileNameURL = fileNameURL; + } + + public String getBlobName() { + return blobName; + } + + public void setBlobName(String blobName) { + this.blobName = blobName; + } + + public Boolean getIsFileAlreadyExist() { + return isFileAlreadyExist; + } + + public void setIsFileAlreadyExist(Boolean isFileAlreadyExist) { + this.isFileAlreadyExist = isFileAlreadyExist; + } + + public Boolean getIsOverwriteFile() { + return isOverwriteFile; + } + + public void setIsOverwriteFile(Boolean isOverwriteFile) { + this.isOverwriteFile = isOverwriteFile; + } + + public Boolean getIsFileAlreadyExistURL() { + return isFileAlreadyExistURL; + } + + public void setIsFileAlreadyExistURL(Boolean isFileAlreadyExistURL) { + this.isFileAlreadyExistURL = isFileAlreadyExistURL; + } + + public Boolean getIsOverwriteFileURL() { + return isOverwriteFileURL; + } + + public void setIsOverwriteFileURL(Boolean isOverwriteFileURL) { + this.isOverwriteFileURL = isOverwriteFileURL; + } + + public Boolean getIsFileAlreadyExistPath() { + return isFileAlreadyExistPath; + } + + public void setIsFileAlreadyExistPath(Boolean isFileAlreadyExistPath) { + this.isFileAlreadyExistPath = isFileAlreadyExistPath; + } + + public Boolean getIsOverwriteFilePath() { + return isOverwriteFilePath; + } + + public void setIsOverwriteFilePath(Boolean isOverwriteFilePath) { + this.isOverwriteFilePath = isOverwriteFilePath; + } + + public String getUploadToFolderByPrimefaces() { + return uploadToFolderByPrimefaces; + } + + public void setUploadToFolderByPrimefaces(String uploadToFolderByPrimefaces) { + this.uploadToFolderByPrimefaces = uploadToFolderByPrimefaces; + } + + public String getUploadToFolderByLocalPath() { + return uploadToFolderByLocalPath; + } + + public void setUploadToFolderByLocalPath(String uploadToFolderByLocalPath) { + this.uploadToFolderByLocalPath = uploadToFolderByLocalPath; + } + + public String getUploadToFolderByURL() { + return uploadToFolderByURL; + } + + public void setUploadToFolderByURL(String uploadToFolderByURL) { + this.uploadToFolderByURL = uploadToFolderByURL; + } + + public void getBlobs(List blobItems) { + blobs = new ArrayList<>(); + for (BlobItem item : blobItems) { + Blob b = new Blob(); + b.setBlobItem(item); + blobs.add(b); + } + } + + public String createBlobPath(String folderName, String fileName) { + String path = StringUtils.isNotBlank(folderName) ? folderName + "/" : StringUtils.EMPTY; + return path + fileName; + } + + public void showCopiedMessage() { + FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Download link is copied", null)); + } + +} diff --git a/azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/utils/UploadUtils.java b/azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/utils/UploadUtils.java new file mode 100644 index 0000000..30f9404 --- /dev/null +++ b/azure-blob-connector-demo/src/com/axonivy/connector/azure/blob/demo/utils/UploadUtils.java @@ -0,0 +1,21 @@ +package com.axonivy.connector.azure.blob.demo.utils; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Paths; + +import org.apache.commons.lang3.StringUtils; + +import ch.ivyteam.ivy.environment.Ivy; + +public class UploadUtils { + + public static String getFileNameFromUrl(String url) { + try { + return Paths.get(new URI(url).getPath()).getFileName().toString(); + } catch (URISyntaxException e) { + Ivy.log().warn("Can not get file name from " + url, e); + } + return StringUtils.EMPTY; + } +} diff --git a/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/Upload.rddescriptor b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/Upload.rddescriptor new file mode 100644 index 0000000..ae605f0 --- /dev/null +++ b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/Upload.rddescriptor @@ -0,0 +1,7 @@ + + + + viewTechnology + JSF + + diff --git a/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/Upload.xhtml b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/Upload.xhtml new file mode 100644 index 0000000..17c6e5a --- /dev/null +++ b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/Upload.xhtml @@ -0,0 +1,158 @@ + + + + + UploadDialog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+
+ + +
+ + + + + +
+ +
+ + +
+
+
+
+ + + + diff --git a/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/UploadData.ivyClass b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/UploadData.ivyClass new file mode 100644 index 0000000..b1ea3d2 --- /dev/null +++ b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/UploadData.ivyClass @@ -0,0 +1,6 @@ +UploadData #class +com.axonivy.connector.azure.blob.demo.Upload #namespace +bean com.axonivy.connector.azure.blob.demo.bean.UploadBean #field +bean PERSISTENT #fieldModifier +link String #field +link PERSISTENT #fieldModifier diff --git a/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/UploadProcess.p.json b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/UploadProcess.p.json new file mode 100644 index 0000000..30aadea --- /dev/null +++ b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/Upload/UploadProcess.p.json @@ -0,0 +1,447 @@ +{ + "format" : "10.0.0", + "id" : "19010B658CC93485", + "kind" : "HTML_DIALOG", + "config" : { + "data" : "com.axonivy.connector.azure.blob.demo.Upload.UploadData" + }, + "elements" : [ { + "id" : "f0", + "type" : "HtmlDialogStart", + "name" : "start()", + "config" : { + "callSignature" : "start", + "guid" : "19010B658D1EDEBA" + }, + "visual" : { + "at" : { "x" : 96, "y" : 64 } + }, + "connect" : { "id" : "f7", "to" : "f6" } + }, { + "id" : "f1", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 376, "y" : 64 } + } + }, { + "id" : "f3", + "type" : "HtmlDialogEventStart", + "name" : "close", + "config" : { + "guid" : "19010B658F2419A3" + }, + "visual" : { + "at" : { "x" : 96, "y" : 160 } + }, + "connect" : { "id" : "f5", "to" : "f4" } + }, { + "id" : "f4", + "type" : "HtmlDialogExit", + "visual" : { + "at" : { "x" : 224, "y" : 160 } + } + }, { + "id" : "f6", + "type" : "Script", + "name" : "init", + "config" : { + "output" : { + "code" : "in.bean.init();" + } + }, + "visual" : { + "at" : { "x" : 256, "y" : 64 } + }, + "connect" : { "id" : "f2", "to" : "f1" } + }, { + "id" : "f8", + "type" : "HtmlDialogMethodStart", + "name" : "uploadFileHandle(FileUploadEvent)", + "config" : { + "callSignature" : "uploadFileHandle", + "input" : { + "params" : [ + { "name" : "event", "type" : "org.primefaces.event.FileUploadEvent" } + ], + "map" : { + "out.bean.uploadedFile" : "param.event.getFile()" + } + }, + "guid" : "19010C11B0E3872B" + }, + "visual" : { + "at" : { "x" : 96, "y" : 264 } + }, + "connect" : { "id" : "f11", "to" : "f10" } + }, { + "id" : "f9", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 384, "y" : 264 } + } + }, { + "id" : "f10", + "type" : "Script", + "name" : "update content", + "config" : { + "output" : { + "code" : [ + "import org.apache.commons.io.IOUtils;", + "", + "if (!in.bean.uploadedFile.getFileName().isEmpty()) {", + " in.bean.fileName = in.bean.uploadedFile.getFileName();", + " in.bean.content = IOUtils.toByteArray(in.bean.uploadedFile.getInputStream());", + "} " + ] + } + }, + "visual" : { + "at" : { "x" : 264, "y" : 264 } + }, + "connect" : { "id" : "f12", "to" : "f9" } + }, { + "id" : "f13", + "type" : "HtmlDialogEventStart", + "name" : "addBlob", + "config" : { + "guid" : "19010C824702EA03" + }, + "visual" : { + "at" : { "x" : 96, "y" : 384 } + }, + "connect" : { "id" : "f63", "to" : "f14" } + }, { + "id" : "f14", + "type" : "Script", + "name" : "save blob", + "config" : { + "output" : { + "code" : "in.bean.upload();" + } + }, + "visual" : { + "at" : { "x" : 272, "y" : 384 } + }, + "connect" : { "id" : "f17", "to" : "f15" } + }, { + "id" : "f15", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 392, "y" : 384 } + } + }, { + "id" : "f18", + "type" : "HtmlDialogMethodStart", + "name" : "getLink(String)", + "config" : { + "callSignature" : "getLink", + "input" : { + "params" : [ + { "name" : "link", "type" : "String" } + ], + "map" : { + "out.link" : "param.link" + } + }, + "guid" : "19010CAC30297BE7" + }, + "visual" : { + "at" : { "x" : 96, "y" : 480 } + }, + "connect" : { "id" : "f20", "to" : "f19" } + }, { + "id" : "f19", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 344, "y" : 480 } + } + }, { + "id" : "f21", + "type" : "HtmlDialogEventStart", + "name" : "addBlobByLocalPath", + "config" : { + "guid" : "19010CCFF013F9CE" + }, + "visual" : { + "at" : { "x" : 96, "y" : 568 } + }, + "connect" : { "id" : "f24", "to" : "f22" } + }, { + "id" : "f22", + "type" : "Script", + "name" : "save blob", + "config" : { + "output" : { + "code" : "in.bean.uploadFromPath();" + } + }, + "visual" : { + "at" : { "x" : 216, "y" : 568 } + }, + "connect" : { "id" : "f25", "to" : "f23" } + }, { + "id" : "f23", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 344, "y" : 568 } + } + }, { + "id" : "f26", + "type" : "HtmlDialogEventStart", + "name" : "addBlobByURL", + "config" : { + "guid" : "19010D1A61BCD315" + }, + "visual" : { + "at" : { "x" : 96, "y" : 672 } + }, + "connect" : { "id" : "f29", "to" : "f27" } + }, { + "id" : "f27", + "type" : "Script", + "name" : "save blob", + "config" : { + "output" : { + "code" : "in.bean.uploadFromURL();" + } + }, + "visual" : { + "at" : { "x" : 216, "y" : 672 } + }, + "connect" : { "id" : "f30", "to" : "f28" } + }, { + "id" : "f28", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 336, "y" : 672 } + } + }, { + "id" : "f31", + "type" : "HtmlDialogEventStart", + "name" : "deleteBlobs", + "config" : { + "guid" : "1903507E6AA2A1EC" + }, + "visual" : { + "at" : { "x" : 88, "y" : 776 } + }, + "connect" : { "id" : "f34", "to" : "f32" } + }, { + "id" : "f32", + "type" : "Script", + "name" : "delete blob list", + "config" : { + "output" : { + "code" : "in.bean.deleteBlobs(in.bean.startDate);" + } + }, + "visual" : { + "at" : { "x" : 216, "y" : 776 } + }, + "connect" : { "id" : "f35", "to" : "f33" } + }, { + "id" : "f33", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 336, "y" : 776 } + } + }, { + "id" : "f36", + "type" : "HtmlDialogMethodStart", + "name" : "deleteBlob(String)", + "config" : { + "callSignature" : "deleteBlob", + "input" : { + "params" : [ + { "name" : "blobName", "type" : "String" } + ], + "map" : { + "out.bean.blobName" : "param.blobName" + } + }, + "guid" : "19039F5750C9EF46" + }, + "visual" : { + "at" : { "x" : 88, "y" : 872 } + }, + "connect" : { "id" : "f39", "to" : "f37" } + }, { + "id" : "f37", + "type" : "Script", + "name" : "delete blob", + "config" : { + "output" : { + "code" : "in.bean.deleteBlob(in.bean.blobName);" + } + }, + "visual" : { + "at" : { "x" : 216, "y" : 872 } + }, + "connect" : { "id" : "f40", "to" : "f38" } + }, { + "id" : "f38", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 336, "y" : 872 } + } + }, { + "id" : "f41", + "type" : "HtmlDialogMethodStart", + "name" : "unDeleteBlob(String)", + "config" : { + "callSignature" : "unDeleteBlob", + "input" : { + "params" : [ + { "name" : "blobName", "type" : "String" } + ], + "map" : { + "out.bean.blobName" : "param.blobName" + } + }, + "guid" : "1903A08B533830F4" + }, + "visual" : { + "at" : { "x" : 88, "y" : 976 } + }, + "connect" : { "id" : "f44", "to" : "f42" } + }, { + "id" : "f42", + "type" : "Script", + "name" : "undelete", + "config" : { + "output" : { + "code" : "in.bean.undeleteBlob(in.bean.blobName);" + } + }, + "visual" : { + "at" : { "x" : 216, "y" : 976 } + }, + "connect" : { "id" : "f45", "to" : "f43" } + }, { + "id" : "f43", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 336, "y" : 976 } + } + }, { + "id" : "f46", + "type" : "HtmlDialogEventStart", + "name" : "overwriteFile", + "config" : { + "guid" : "19058AF03B67EC96" + }, + "visual" : { + "at" : { "x" : 88, "y" : 1072 } + }, + "connect" : { "id" : "f49", "to" : "f47" } + }, { + "id" : "f47", + "type" : "Script", + "name" : "update status", + "config" : { + "output" : { + "code" : "in.bean.isFileAlreadyExist = false;" + } + }, + "visual" : { + "at" : { "x" : 216, "y" : 1072 } + }, + "connect" : { "id" : "f50", "to" : "f48" } + }, { + "id" : "f48", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 328, "y" : 1072 } + } + }, { + "id" : "f51", + "type" : "HtmlDialogEventStart", + "name" : "overwriteFilePath", + "config" : { + "guid" : "1905D2E31F62E71C" + }, + "visual" : { + "at" : { "x" : 88, "y" : 1168 } + }, + "connect" : { "id" : "f54", "to" : "f52" } + }, { + "id" : "f52", + "type" : "Script", + "name" : "update status", + "config" : { + "output" : { + "code" : "in.bean.isFileAlreadyExistPath = false;" + } + }, + "visual" : { + "at" : { "x" : 216, "y" : 1168 } + }, + "connect" : { "id" : "f55", "to" : "f53" } + }, { + "id" : "f53", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 336, "y" : 1168 } + } + }, { + "id" : "f56", + "type" : "HtmlDialogEventStart", + "name" : "overwriteFileURL", + "config" : { + "guid" : "1905D2F260AC5762" + }, + "visual" : { + "at" : { "x" : 88, "y" : 1288 } + }, + "connect" : { "id" : "f59", "to" : "f57" } + }, { + "id" : "f57", + "type" : "Script", + "name" : "update status", + "config" : { + "output" : { + "code" : "in.bean.isFileAlreadyExistURL = false;" + } + }, + "visual" : { + "at" : { "x" : 216, "y" : 1288 } + }, + "connect" : { "id" : "f60", "to" : "f58" } + }, { + "id" : "f58", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 336, "y" : 1288 } + } + }, { + "id" : "f16", + "type" : "HtmlDialogEventStart", + "name" : "showCopiedMessage", + "config" : { + "guid" : "1914EBCF1F569E49" + }, + "visual" : { + "at" : { "x" : 80, "y" : 1400 } + }, + "connect" : { "id" : "f62", "to" : "f61" } + }, { + "id" : "f61", + "type" : "Script", + "name" : "show messsage copied", + "config" : { + "output" : { + "code" : "in.bean.showCopiedMessage();" + } + }, + "visual" : { + "at" : { "x" : 272, "y" : 1400 } + }, + "connect" : { "id" : "f65", "to" : "f64" } + }, { + "id" : "f64", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 464, "y" : 1400 } + } + } ] +} \ No newline at end of file diff --git a/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocess.rddescriptor b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocess.rddescriptor new file mode 100644 index 0000000..ae605f0 --- /dev/null +++ b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocess.rddescriptor @@ -0,0 +1,7 @@ + + + + viewTechnology + JSF + + diff --git a/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocess.xhtml b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocess.xhtml new file mode 100644 index 0000000..568b87f --- /dev/null +++ b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocess.xhtml @@ -0,0 +1,156 @@ + + + + + UploadDialog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+
+ + +
+ + + + + +
+
+ + +
+
+
+
+ + + + diff --git a/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocessData.ivyClass b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocessData.ivyClass new file mode 100644 index 0000000..5ea1c1a --- /dev/null +++ b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocessData.ivyClass @@ -0,0 +1,12 @@ +UploadByCallSubprocessData #class +com.axonivy.connector.azure.blob.demo.UploadByCallSubprocess #namespace +bean com.axonivy.connector.azure.blob.demo.bean.UploadByCallSubprocess #field +bean PERSISTENT #fieldModifier +link String #field +link PERSISTENT #fieldModifier +isSuccess Boolean #field +isSuccess PERSISTENT #fieldModifier +isExist Boolean #field +isExist PERSISTENT #fieldModifier +combineFileNameAndFolder String #field +combineFileNameAndFolder PERSISTENT #fieldModifier diff --git a/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocessProcess.p.json b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocessProcess.p.json new file mode 100644 index 0000000..c74c221 --- /dev/null +++ b/azure-blob-connector-demo/src_hd/com/axonivy/connector/azure/blob/demo/UploadByCallSubprocess/UploadByCallSubprocessProcess.p.json @@ -0,0 +1,1065 @@ +{ + "format" : "10.0.0", + "id" : "190719BB1F7DBAD8", + "kind" : "HTML_DIALOG", + "config" : { + "data" : "com.axonivy.connector.azure.blob.demo.UploadByCallSubprocess.UploadByCallSubprocessData" + }, + "elements" : [ { + "id" : "f0", + "type" : "HtmlDialogStart", + "name" : "start()", + "config" : { + "callSignature" : "start", + "guid" : "19010B658D1EDEBA" + }, + "visual" : { + "at" : { "x" : 96, "y" : 64 } + }, + "connect" : { "id" : "f7", "to" : "f6" } + }, { + "id" : "f1", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 696, "y" : 64 } + } + }, { + "id" : "f3", + "type" : "HtmlDialogEventStart", + "name" : "close", + "config" : { + "guid" : "19010B658F2419A3" + }, + "visual" : { + "at" : { "x" : 96, "y" : 160 } + }, + "connect" : { "id" : "f5", "to" : "f4" } + }, { + "id" : "f4", + "type" : "HtmlDialogExit", + "visual" : { + "at" : { "x" : 224, "y" : 160 } + } + }, { + "id" : "f6", + "type" : "Script", + "name" : "init", + "config" : { + "output" : { + "code" : "in.bean.init();" + } + }, + "visual" : { + "at" : { "x" : 256, "y" : 64 } + }, + "connect" : { "id" : "f76", "to" : "f73" } + }, { + "id" : "f8", + "type" : "HtmlDialogMethodStart", + "name" : "uploadFileHandle(FileUploadEvent)", + "config" : { + "callSignature" : "uploadFileHandle", + "input" : { + "params" : [ + { "name" : "event", "type" : "org.primefaces.event.FileUploadEvent" } + ], + "map" : { + "out.bean.uploadedFile" : "param.event.getFile()" + } + }, + "guid" : "19010C11B0E3872B" + }, + "visual" : { + "at" : { "x" : 96, "y" : 240 } + }, + "connect" : { "id" : "f11", "to" : "f10" } + }, { + "id" : "f9", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 384, "y" : 240 } + } + }, { + "id" : "f10", + "type" : "Script", + "name" : "update content", + "config" : { + "output" : { + "code" : [ + "import org.apache.commons.io.IOUtils;", + "", + "if (!in.bean.uploadedFile.getFileName().isEmpty()) {", + " in.bean.fileName = in.bean.uploadedFile.getFileName();", + " in.bean.inputStreamContent = in.bean.uploadedFile.getInputStream();", + "} " + ] + } + }, + "visual" : { + "at" : { "x" : 264, "y" : 240 } + }, + "connect" : { "id" : "f12", "to" : "f9" } + }, { + "id" : "f13", + "type" : "HtmlDialogEventStart", + "name" : "addBlob", + "config" : { + "guid" : "19010C824702EA03" + }, + "visual" : { + "at" : { "x" : 96, "y" : 352 } + }, + "connect" : { "id" : "f127", "to" : "f126" } + }, { + "id" : "f15", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 1240, "y" : 352 } + } + }, { + "id" : "f18", + "type" : "HtmlDialogMethodStart", + "name" : "getLink(String)", + "config" : { + "callSignature" : "getLink", + "input" : { + "params" : [ + { "name" : "blobName", "type" : "String" } + ], + "map" : { + "out.bean.blobName" : "param.blobName" + } + }, + "guid" : "19010CAC30297BE7" + }, + "visual" : { + "at" : { "x" : 96, "y" : 504 } + }, + "connect" : { "id" : "f125", "to" : "f124" } + }, { + "id" : "f19", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 352, "y" : 504 } + } + }, { + "id" : "f21", + "type" : "HtmlDialogEventStart", + "name" : "addBlobByLocalPath", + "config" : { + "guid" : "19010CCFF013F9CE" + }, + "visual" : { + "at" : { "x" : 104, "y" : 624 } + }, + "connect" : { "id" : "f103", "to" : "f25" } + }, { + "id" : "f23", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 1200, "y" : 624 } + } + }, { + "id" : "f26", + "type" : "HtmlDialogEventStart", + "name" : "addBlobByURL", + "config" : { + "guid" : "19010D1A61BCD315" + }, + "visual" : { + "at" : { "x" : 104, "y" : 840 } + }, + "connect" : { "id" : "f116", "to" : "f29" } + }, { + "id" : "f28", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 1208, "y" : 840 } + } + }, { + "id" : "f31", + "type" : "HtmlDialogEventStart", + "name" : "deleteBlobs", + "config" : { + "guid" : "1903507E6AA2A1EC" + }, + "visual" : { + "at" : { "x" : 96, "y" : 1072 } + }, + "connect" : { "id" : "f78", "to" : "f77" } + }, { + "id" : "f33", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 808, "y" : 1072 } + } + }, { + "id" : "f36", + "type" : "HtmlDialogMethodStart", + "name" : "deleteBlob(String)", + "config" : { + "callSignature" : "deleteBlob", + "input" : { + "params" : [ + { "name" : "blobName", "type" : "String" } + ], + "map" : { + "out.bean.blobName" : "param.blobName" + } + }, + "guid" : "19039F5750C9EF46" + }, + "visual" : { + "at" : { "x" : 96, "y" : 1184 } + }, + "connect" : { "id" : "f35", "to" : "f32" } + }, { + "id" : "f41", + "type" : "HtmlDialogMethodStart", + "name" : "unDeleteBlob(String)", + "config" : { + "callSignature" : "unDeleteBlob", + "input" : { + "params" : [ + { "name" : "blobName", "type" : "String" } + ], + "map" : { + "out.bean.blobName" : "param.blobName" + } + }, + "guid" : "1903A08B533830F4" + }, + "visual" : { + "at" : { "x" : 96, "y" : 1280 } + }, + "connect" : { "id" : "f84", "to" : "f39" } + }, { + "id" : "f43", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 808, "y" : 1280 } + } + }, { + "id" : "f46", + "type" : "HtmlDialogEventStart", + "name" : "overwriteFile", + "config" : { + "guid" : "19058AF03B67EC96" + }, + "visual" : { + "at" : { "x" : 96, "y" : 1384 } + }, + "connect" : { "id" : "f49", "to" : "f47" } + }, { + "id" : "f47", + "type" : "Script", + "name" : "update status", + "config" : { + "output" : { + "code" : "in.bean.isFileAlreadyExist = false;" + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 1384 } + }, + "connect" : { "id" : "f50", "to" : "f48" } + }, { + "id" : "f48", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 336, "y" : 1384 } + } + }, { + "id" : "f51", + "type" : "HtmlDialogEventStart", + "name" : "overwriteFilePath", + "config" : { + "guid" : "1905D2E31F62E71C" + }, + "visual" : { + "at" : { "x" : 96, "y" : 1464 } + }, + "connect" : { "id" : "f54", "to" : "f52" } + }, { + "id" : "f52", + "type" : "Script", + "name" : "update status", + "config" : { + "output" : { + "code" : "in.bean.isFileAlreadyExistPath = false;" + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 1464 } + }, + "connect" : { "id" : "f55", "to" : "f53" } + }, { + "id" : "f53", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 344, "y" : 1464 } + } + }, { + "id" : "f56", + "type" : "HtmlDialogEventStart", + "name" : "overwriteFileURL", + "config" : { + "guid" : "1905D2F260AC5762" + }, + "visual" : { + "at" : { "x" : 96, "y" : 1552 } + }, + "connect" : { "id" : "f59", "to" : "f57" } + }, { + "id" : "f57", + "type" : "Script", + "name" : "update status", + "config" : { + "output" : { + "code" : "in.bean.isFileAlreadyExistURL = false;" + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 1552 } + }, + "connect" : { "id" : "f60", "to" : "f58" } + }, { + "id" : "f58", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 336, "y" : 1552 } + } + }, { + "id" : "f61", + "type" : "SubProcessCall", + "name" : "save blob", + "config" : { + "processCall" : "BlobStorage:uploadFromFile(java.io.InputStream,String,String,Boolean)", + "output" : { + "map" : { + "out" : "in", + "out.bean.blobName" : "result.blobName" + } + }, + "call" : { + "params" : [ + { "name" : "content", "type" : "java.io.InputStream" }, + { "name" : "blobName", "type" : "String" }, + { "name" : "uploadToFolder", "type" : "String" }, + { "name" : "isOverwriteFile", "type" : "Boolean" } + ], + "map" : { + "param.content" : "in.bean.inputStreamContent", + "param.blobName" : "in.bean.fileName", + "param.uploadToFolder" : "in.bean.uploadToFolderByPrimefaces", + "param.isOverwriteFile" : "in.bean.isOverwriteFile" + } + } + }, + "visual" : { + "at" : { "x" : 688, "y" : 352 } + }, + "connect" : { "id" : "f67", "to" : "f66" } + }, { + "id" : "f14", + "type" : "Alternative", + "name" : "is exist file ?", + "visual" : { + "at" : { "x" : 504, "y" : 352 }, + "labelOffset" : { "x" : 16, "y" : -24 } + }, + "connect" : [ + { "id" : "f62", "to" : "f61", "label" : { + "name" : "no" + }, "condition" : "in.bean.isOverwriteFile || !in.isExist" }, + { "id" : "f64", "to" : "f63", "label" : { + "name" : "yes" + } } + ] + }, { + "id" : "f63", + "type" : "Script", + "name" : "update value isFileAlreadyExist", + "config" : { + "output" : { + "code" : "in.bean.isFileAlreadyExist = true;" + } + }, + "visual" : { + "at" : { "x" : 504, "y" : 432 } + }, + "connect" : { "id" : "f65", "to" : "f15", "via" : [ { "x" : 1240, "y" : 432 } ] } + }, { + "id" : "f66", + "type" : "Alternative", + "name" : [ + "is blob name ", + "not null ?" + ], + "visual" : { + "at" : { "x" : 816, "y" : 352 }, + "labelOffset" : { "x" : 16, "y" : 40 } + }, + "connect" : [ + { "id" : "f69", "to" : "f68", "label" : { + "name" : "yes" + }, "condition" : "org.apache.commons.lang3.StringUtils.isNotBlank(in.bean.blobName)" }, + { "id" : "f70", "to" : "f15", "via" : [ { "x" : 816, "y" : 280 }, { "x" : 1240, "y" : 280 } ], "label" : { + "name" : "no", + "segment" : 1.47 + } } + ] + }, { + "id" : "f68", + "type" : "SubProcessCall", + "name" : "get list blob", + "config" : { + "processCall" : "BlobStorage:getBlobs()", + "output" : { + "map" : { + "out" : "in", + "out.bean.blobItems" : "result.blobItems" + } + } + }, + "visual" : { + "at" : { "x" : 944, "y" : 352 } + }, + "connect" : { "id" : "f72", "to" : "f71" } + }, { + "id" : "f71", + "type" : "Script", + "name" : "convert list an show message", + "config" : { + "output" : { + "code" : [ + "import javax.faces.application.FacesMessage;", + "import javax.faces.context.FacesContext;", + "", + "in.bean.getBlobs(in.bean.blobItems);", + "FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, \"Uploaded blobs successfully\", null));" + ] + } + }, + "visual" : { + "at" : { "x" : 1104, "y" : 352 } + }, + "connect" : { "id" : "f16", "to" : "f15" } + }, { + "id" : "f73", + "type" : "SubProcessCall", + "name" : "get list blob", + "config" : { + "processCall" : "BlobStorage:getBlobs()", + "output" : { + "map" : { + "out" : "in", + "out.bean.blobItems" : "result.blobItems" + } + } + }, + "visual" : { + "at" : { "x" : 408, "y" : 64 } + }, + "connect" : { "id" : "f75", "to" : "f74" } + }, { + "id" : "f74", + "type" : "Script", + "name" : "convert list an show message", + "config" : { + "output" : { + "code" : "in.bean.getBlobs(in.bean.blobItems);" + } + }, + "visual" : { + "at" : { "x" : 568, "y" : 64 } + }, + "connect" : { "id" : "f2", "to" : "f1" } + }, { + "id" : "f77", + "type" : "SubProcessCall", + "name" : "delete by date", + "config" : { + "processCall" : "BlobStorage:delete(java.util.Date)", + "output" : { + "map" : { + "out" : "in", + "out.isSuccess" : "result.isSucess" + } + }, + "call" : { + "params" : [ + { "name" : "date", "type" : "java.util.Date" } + ], + "map" : { + "param.date" : "in.bean.startDate" + } + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 1072 } + }, + "connect" : { "id" : "f88", "to" : "f87" } + }, { + "id" : "f79", + "type" : "SubProcessCall", + "name" : "get list blob", + "config" : { + "processCall" : "BlobStorage:getBlobs()", + "output" : { + "map" : { + "out" : "in", + "out.bean.blobItems" : "result.blobItems" + } + } + }, + "visual" : { + "at" : { "x" : 480, "y" : 1072 } + }, + "connect" : { "id" : "f81", "to" : "f80" } + }, { + "id" : "f80", + "type" : "Script", + "name" : "convert list an show message", + "config" : { + "output" : { + "code" : [ + "import javax.faces.application.FacesMessage;", + "import javax.faces.context.FacesContext;", + "", + "in.bean.getBlobs(in.bean.blobItems);", + "FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, \"Delete blobs successfully\", null));" + ] + } + }, + "visual" : { + "at" : { "x" : 656, "y" : 1072 } + }, + "connect" : { "id" : "f34", "to" : "f33" } + }, { + "id" : "f32", + "type" : "SubProcessCall", + "name" : "delete by name", + "config" : { + "processCall" : "BlobStorage:detete(String)", + "output" : { + "map" : { + "out" : "in", + "out.isSuccess" : "result.isSuccess" + } + }, + "call" : { + "params" : [ + { "name" : "blobName", "type" : "String" } + ], + "map" : { + "param.blobName" : "in.bean.blobName" + } + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 1184 } + }, + "connect" : { "id" : "f89", "to" : "f87", "via" : [ { "x" : 352, "y" : 1184 } ] } + }, { + "id" : "f39", + "type" : "SubProcessCall", + "name" : "undelete", + "config" : { + "processCall" : "BlobStorage:restore(String)", + "output" : { + "map" : { + "out" : "in", + "out.isSuccess" : "result.isSuccess" + } + }, + "call" : { + "params" : [ + { "name" : "blobName", "type" : "String" } + ], + "map" : { + "param.blobName" : "in.bean.blobName" + } + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 1280 } + }, + "connect" : { "id" : "f45", "to" : "f42" } + }, { + "id" : "f44", + "type" : "SubProcessCall", + "name" : "get list blob", + "config" : { + "processCall" : "BlobStorage:getBlobs()", + "output" : { + "map" : { + "out" : "in", + "out.bean.blobItems" : "result.blobItems" + } + } + }, + "visual" : { + "at" : { "x" : 480, "y" : 1280 } + }, + "connect" : { "id" : "f38", "to" : "f37" } + }, { + "id" : "f37", + "type" : "Script", + "name" : "convert list an show message", + "config" : { + "output" : { + "code" : [ + "import javax.faces.application.FacesMessage;", + "import javax.faces.context.FacesContext;", + "", + "in.bean.getBlobs(in.bean.blobItems);", + "FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, \"Undelete blob successfully\", null));" + ] + } + }, + "visual" : { + "at" : { "x" : 656, "y" : 1280 } + }, + "connect" : { "id" : "f40", "to" : "f43" } + }, { + "id" : "f42", + "type" : "Alternative", + "name" : "is success", + "visual" : { + "at" : { "x" : 352, "y" : 1280 }, + "labelOffset" : { "x" : 8, "y" : -16 } + }, + "connect" : [ + { "id" : "f85", "to" : "f44", "label" : { + "name" : "yes" + }, "condition" : "in.isSuccess" }, + { "id" : "f86", "to" : "f43", "via" : [ { "x" : 352, "y" : 1328 }, { "x" : 808, "y" : 1328 } ], "label" : { + "name" : "no", + "segment" : 1.49 + } } + ] + }, { + "id" : "f87", + "type" : "Alternative", + "name" : "is success", + "visual" : { + "at" : { "x" : 352, "y" : 1072 }, + "labelOffset" : { "x" : -16, "y" : -8 } + }, + "connect" : [ + { "id" : "f82", "to" : "f79", "label" : { + "name" : "yes" + }, "condition" : "in.isSuccess" }, + { "id" : "f83", "to" : "f33", "via" : [ { "x" : 352, "y" : 1016 }, { "x" : 808, "y" : 1016 } ], "label" : { + "name" : "no", + "segment" : 1.49 + } } + ] + }, { + "id" : "f90", + "type" : "Alternative", + "name" : "is exist file ?", + "visual" : { + "at" : { "x" : 520, "y" : 624 }, + "labelOffset" : { "x" : 16, "y" : -24 } + }, + "connect" : [ + { "id" : "f99", "to" : "f92", "condition" : "in.bean.isOverwriteFilePath || !in.isExist" }, + { "id" : "f96", "to" : "f91" } + ] + }, { + "id" : "f91", + "type" : "Script", + "name" : "update value isFileAlreadyExistPath", + "config" : { + "output" : { + "code" : "in.bean.isFileAlreadyExistPath = true;" + } + }, + "visual" : { + "at" : { "x" : 520, "y" : 712 } + }, + "connect" : { "id" : "f22", "to" : "f23", "via" : [ { "x" : 1200, "y" : 712 } ] } + }, { + "id" : "f92", + "type" : "SubProcessCall", + "name" : "save blob", + "config" : { + "processCall" : "BlobStorage:uploadFromFile(String,String,String,Boolean)", + "output" : { + "map" : { + "out" : "in", + "out.bean.blobName" : "result.blobName" + } + }, + "call" : { + "params" : [ + { "name" : "localPath", "type" : "String" }, + { "name" : "blobName", "type" : "String" }, + { "name" : "uploadToFolder", "type" : "String" }, + { "name" : "isOverwriteFile", "type" : "Boolean" } + ], + "map" : { + "param.localPath" : "in.bean.localPath", + "param.blobName" : "in.bean.fileNamePath", + "param.uploadToFolder" : "in.bean.uploadToFolderByLocalPath", + "param.isOverwriteFile" : "in.bean.isOverwriteFilePath" + } + } + }, + "visual" : { + "at" : { "x" : 640, "y" : 624 } + }, + "connect" : { "id" : "f100", "to" : "f93" } + }, { + "id" : "f93", + "type" : "Alternative", + "name" : [ + "is blob name ", + "not null ?" + ], + "visual" : { + "at" : { "x" : 776, "y" : 624 }, + "labelOffset" : { "x" : 16, "y" : 40 } + }, + "connect" : [ + { "id" : "f97", "to" : "f94", "label" : { + "name" : "yes" + }, "condition" : "org.apache.commons.lang3.StringUtils.isNotBlank(in.bean.blobName)" }, + { "id" : "f102", "to" : "f23", "via" : [ { "x" : 776, "y" : 552 }, { "x" : 1200, "y" : 552 } ], "label" : { + "name" : "no", + "segment" : 1.48 + } } + ] + }, { + "id" : "f94", + "type" : "SubProcessCall", + "name" : "get list blob", + "config" : { + "processCall" : "BlobStorage:getBlobs()", + "output" : { + "map" : { + "out" : "in", + "out.bean.blobItems" : "result.blobItems" + } + } + }, + "visual" : { + "at" : { "x" : 912, "y" : 624 } + }, + "connect" : { "id" : "f98", "to" : "f95" } + }, { + "id" : "f95", + "type" : "Script", + "name" : "convert list an show message", + "config" : { + "output" : { + "code" : [ + "import javax.faces.application.FacesMessage;", + "import javax.faces.context.FacesContext;", + "", + "in.bean.getBlobs(in.bean.blobItems);", + "FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, \"Uploaded blobs successfully\", null));" + ] + } + }, + "visual" : { + "at" : { "x" : 1072, "y" : 624 } + }, + "connect" : { "id" : "f24", "to" : "f23" } + }, { + "id" : "f25", + "type" : "Script", + "name" : "get file name", + "config" : { + "output" : { + "code" : [ + "import org.apache.commons.io.FilenameUtils;", + "", + "in.bean.fileNamePath = FilenameUtils.getName(in.bean.localPath);", + "in.combineFileNameAndFolder = in.bean.createBlobPath(in.bean.uploadToFolderByLocalPath, in.bean.fileNamePath);" + ] + } + }, + "visual" : { + "at" : { "x" : 232, "y" : 624 } + }, + "connect" : { "id" : "f121", "to" : "f120" } + }, { + "id" : "f29", + "type" : "Script", + "name" : "get file name", + "config" : { + "output" : { + "code" : [ + "import com.axonivy.connector.azure.blob.demo.utils.UploadUtils;", + "in.bean.fileNameURL = UploadUtils.getFileNameFromUrl(in.bean.url);", + "in.combineFileNameAndFolder = in.bean.createBlobPath(in.bean.uploadToFolderByURL, in.bean.fileNameURL);" + ] + } + }, + "visual" : { + "at" : { "x" : 232, "y" : 840 } + }, + "connect" : { "id" : "f123", "to" : "f122" } + }, { + "id" : "f104", + "type" : "Alternative", + "name" : "is exist file ?", + "visual" : { + "at" : { "x" : 520, "y" : 840 }, + "labelOffset" : { "x" : 16, "y" : -24 } + }, + "connect" : [ + { "id" : "f111", "to" : "f106", "condition" : "in.bean.isOverwriteFileURL || !in.isExist" }, + { "id" : "f112", "to" : "f105" } + ] + }, { + "id" : "f105", + "type" : "Script", + "name" : [ + "update value ", + "isFileAlreadyExistURL" + ], + "config" : { + "output" : { + "code" : "in.bean.isFileAlreadyExistURL = true;" + } + }, + "visual" : { + "at" : { "x" : 520, "y" : 928 } + }, + "connect" : { "id" : "f118", "to" : "f28", "via" : [ { "x" : 1208, "y" : 928 } ] } + }, { + "id" : "f106", + "type" : "SubProcessCall", + "name" : "save blob", + "config" : { + "processCall" : "BlobStorage:uploadFromUrl(String,String,String,Boolean)", + "output" : { + "map" : { + "out" : "in", + "out.bean.blobName" : "result.blobName" + } + }, + "call" : { + "params" : [ + { "name" : "url", "type" : "String" }, + { "name" : "blobName", "type" : "String" }, + { "name" : "uploadToFolder", "type" : "String" }, + { "name" : "isOverwriteFile", "type" : "Boolean" } + ], + "map" : { + "param.url" : "in.bean.url", + "param.blobName" : "in.bean.fileNameURL", + "param.uploadToFolder" : "in.bean.uploadToFolderByURL", + "param.isOverwriteFile" : "in.bean.isOverwriteFileURL" + } + } + }, + "visual" : { + "at" : { "x" : 640, "y" : 840 } + }, + "connect" : { "id" : "f115", "to" : "f107" } + }, { + "id" : "f107", + "type" : "Alternative", + "name" : [ + "is blob name ", + "not null ?" + ], + "visual" : { + "at" : { "x" : 776, "y" : 840 }, + "labelOffset" : { "x" : 16, "y" : 40 } + }, + "connect" : [ + { "id" : "f114", "to" : "f108", "label" : { + "name" : "yes" + }, "condition" : "org.apache.commons.lang3.StringUtils.isNotBlank(in.bean.blobName)" }, + { "id" : "f27", "to" : "f28", "via" : [ { "x" : 776, "y" : 776 }, { "x" : 1208, "y" : 776 } ], "label" : { + "name" : "no", + "segment" : 1.49 + } } + ] + }, { + "id" : "f108", + "type" : "SubProcessCall", + "name" : "get list blob", + "config" : { + "processCall" : "BlobStorage:getBlobs()", + "output" : { + "map" : { + "out" : "in", + "out.bean.blobItems" : "result.blobItems" + } + } + }, + "visual" : { + "at" : { "x" : 912, "y" : 840 } + }, + "connect" : { "id" : "f113", "to" : "f109" } + }, { + "id" : "f109", + "type" : "Script", + "name" : "convert list an show message", + "config" : { + "output" : { + "code" : [ + "import javax.faces.application.FacesMessage;", + "import javax.faces.context.FacesContext;", + "", + "in.bean.getBlobs(in.bean.blobItems);", + "FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, \"Uploaded blobs successfully\", null));" + ] + } + }, + "visual" : { + "at" : { "x" : 1072, "y" : 840 } + }, + "connect" : { "id" : "f117", "to" : "f28" } + }, { + "id" : "f30", + "type" : "SubProcessCall", + "name" : "check blob is exist", + "config" : { + "processCall" : "BlobStorage:checkBlobIsExist(String)", + "output" : { + "map" : { + "out" : "in", + "out.isExist" : "result.isExist" + } + }, + "call" : { + "params" : [ + { "name" : "blobName", "type" : "String" } + ], + "map" : { + "param.blobName" : "in.combineFileNameAndFolder" + } + } + }, + "visual" : { + "at" : { "x" : 384, "y" : 352 } + }, + "connect" : { "id" : "f17", "to" : "f14" } + }, { + "id" : "f120", + "type" : "SubProcessCall", + "name" : "check blob is exist", + "config" : { + "processCall" : "BlobStorage:checkBlobIsExist(String)", + "output" : { + "map" : { + "out" : "in", + "out.isExist" : "result.isExist" + } + }, + "call" : { + "params" : [ + { "name" : "blobName", "type" : "String" } + ], + "map" : { + "param.blobName" : "in.combineFileNameAndFolder" + } + } + }, + "visual" : { + "at" : { "x" : 392, "y" : 624 } + }, + "connect" : { "id" : "f101", "to" : "f90" } + }, { + "id" : "f122", + "type" : "SubProcessCall", + "name" : "check blob is exist", + "config" : { + "processCall" : "BlobStorage:checkBlobIsExist(String)", + "output" : { + "map" : { + "out" : "in", + "out.isExist" : "result.isExist" + } + }, + "call" : { + "params" : [ + { "name" : "blobName", "type" : "String" } + ], + "map" : { + "param.blobName" : "in.combineFileNameAndFolder" + } + } + }, + "visual" : { + "at" : { "x" : 392, "y" : 840 } + }, + "connect" : { "id" : "f110", "to" : "f104" } + }, { + "id" : "f124", + "type" : "SubProcessCall", + "name" : "get link download", + "config" : { + "processCall" : "BlobStorage:getLinkDownload(String)", + "output" : { + "map" : { + "out" : "in", + "out.link" : "result.linkDownload" + } + }, + "call" : { + "params" : [ + { "name" : "blobName", "type" : "String" } + ], + "map" : { + "param.blobName" : "in.bean.blobName" + } + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 504 } + }, + "connect" : { "id" : "f20", "to" : "f19" } + }, { + "id" : "f126", + "type" : "Script", + "name" : "combine name and folder", + "config" : { + "output" : { + "code" : "in.combineFileNameAndFolder = in.bean.createBlobPath(in.bean.uploadToFolderByPrimefaces, in.bean.fileName);" + } + }, + "visual" : { + "at" : { "x" : 224, "y" : 352 } + }, + "connect" : { "id" : "f119", "to" : "f30" } + }, { + "id" : "f128", + "type" : "HtmlDialogEventStart", + "name" : "showCopiedMessage", + "config" : { + "guid" : "1914ECBCBC61A296" + }, + "visual" : { + "at" : { "x" : 80, "y" : 1672 } + }, + "connect" : { "id" : "f130", "to" : "f129" } + }, { + "id" : "f129", + "type" : "Script", + "name" : "show messsage copied", + "config" : { + "output" : { + "code" : "in.bean.showCopiedMessage();" + } + }, + "visual" : { + "at" : { "x" : 272, "y" : 1672 } + }, + "connect" : { "id" : "f132", "to" : "f131" } + }, { + "id" : "f131", + "type" : "HtmlDialogEnd", + "visual" : { + "at" : { "x" : 464, "y" : 1672 } + } + } ] +} \ No newline at end of file diff --git a/azure-blob-connector-demo/webContent/layouts/frame-10-full-width.xhtml b/azure-blob-connector-demo/webContent/layouts/frame-10-full-width.xhtml new file mode 100644 index 0000000..ee652b9 --- /dev/null +++ b/azure-blob-connector-demo/webContent/layouts/frame-10-full-width.xhtml @@ -0,0 +1,59 @@ + + + + + + + + + + <ui:insert name="title">Ivy Html Dialog</ui:insert> + + + + + + + + + + +
+ + default content + +
+ + + + + + + +
+ \ No newline at end of file diff --git a/azure-blob-connector-demo/webContent/layouts/styles/upload-view.css b/azure-blob-connector-demo/webContent/layouts/styles/upload-view.css new file mode 100644 index 0000000..a36de27 --- /dev/null +++ b/azure-blob-connector-demo/webContent/layouts/styles/upload-view.css @@ -0,0 +1,36 @@ +.column-name { + width : 40%; +} + +.column-status { + width : 20%; +} + +.column-created-date { + width : 20%; +} + +.column-action { + width : 20%; +} + +.link-input { + width : 75%; +} + +.copy-action { + width : 20%; + float : right; +} +.float-right { + float : right; +} + +.upload-fieldset { + margin-top: 10px; +} + +body .ui-fileupload .ui-fileupload-buttonbar , body .ui-fileupload .ui-fileupload-content{ + border : none !important; + padding-top: 0px; +} \ No newline at end of file diff --git a/azure-blob-connector-product/.classpath b/azure-blob-connector-product/.classpath new file mode 100644 index 0000000..e28d7fa --- /dev/null +++ b/azure-blob-connector-product/.classpath @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/azure-blob-connector-product/.settings/ch.ivyteam.ivy.designer.prefs b/azure-blob-connector-product/.settings/ch.ivyteam.ivy.designer.prefs index ecc50e4..3767d0e 100644 --- a/azure-blob-connector-product/.settings/ch.ivyteam.ivy.designer.prefs +++ b/azure-blob-connector-product/.settings/ch.ivyteam.ivy.designer.prefs @@ -1,5 +1,5 @@ -ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_DATA_CLASS=com.axonivy.cloud.storage.azure.blob.connector.product.Data -ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_NAMESPACE=com.axonivy.cloud.storage.azure.blob.connector.product +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_DATA_CLASS=com.axonivy.connector.azure.blob.product.Data +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_NAMESPACE=com.axonivy.connector.azure.blob.product ch.ivyteam.ivy.project.preferences\:PRIMEFACES_VERSION=11 ch.ivyteam.ivy.project.preferences\:PROJECT_VERSION=100000 eclipse.preferences.version=1 diff --git a/azure-blob-connector-product/.settings/org.eclipse.wst.common.component b/azure-blob-connector-product/.settings/org.eclipse.wst.common.component index 7168897..f435d2a 100644 --- a/azure-blob-connector-product/.settings/org.eclipse.wst.common.component +++ b/azure-blob-connector-product/.settings/org.eclipse.wst.common.component @@ -1,20 +1,28 @@ - + + - + + - + + - + + - + + - - + + - + + - - + + + + diff --git a/azure-blob-connector-product/README.md b/azure-blob-connector-product/README.md index 82b12a1..87086b3 100644 --- a/azure-blob-connector-product/README.md +++ b/azure-blob-connector-product/README.md @@ -1,9 +1,11 @@ -# Azure Blob Connector +# Azure Blob Storage -Axon Ivy’s Azure Blob Connector helps you to connector Azure Blob Services quitly: -- Configuration to authorize access to blobs. -- Support upload content to blob with many kind of inputs. -- Support get download link with expired time. +Azure Blob Storage is a cloud-based object storage service provided by Microsoft Azure. It allows you to store large amounts of unstructured data, such as images, videos, and documents, in a scalable and cost-effective manner. Data is stored in containers within a storage account, and it can be accessed via HTTP/HTTPS, making it ideal for the integration in your Axon Ivy Business processes. + +This connector: +- Supports you in implementing access to Azure Blob Storage. +- Supports you in uploading content to your Azure Blob Storage - directly from an Axon Ivy business process. +- Creates a download link with an expiry date. ## Setup @@ -12,7 +14,7 @@ In the project, you only add the dependency in your pom.xml and call public APIs **1. Add dependency** ```XML - com.axonivy.cloud.storage + com.axonivy.connector azure-blob-connector ${process.analyzer.version} @@ -35,7 +37,7 @@ Variables: ContainterName: '' ``` -## For Process GUI +### For Process GUI **1. What is support in BlobStorage Callable Sub Process?** ![azure-blob-connector](images/BlobStorageFunctions.png) @@ -47,7 +49,7 @@ Variables: ![azure-blob-connector](images/AddBlobStorageAndCallFunction.png) -## For Java Developer +### For Java Developer **1. Call the constructor to set some basic information. Each instance of the advanced process analyzer should care about one specific process model. This way we can store some private information (e.g. simplified model) in the instance and reuse it for different calculations on this object.** ```java /** @@ -111,7 +113,7 @@ Variables: public String getDownloadLink(String blobName); ``` -## Example +### Example Below is a simple example for upload a file from url and get temporary download link. ``` @@ -124,5 +126,43 @@ Below is a simple example for upload a file from url and get temporary download String downloadLink = storageService.getDownloadLink(blobName); ``` +## Demo + +### Run with Azurite at local + +Start docker local: +You can run with docker or docker-compose + +#### Run Azurite V3 docker image + +> Note. Find more docker images tags in + +```bash +docker pull mcr.microsoft.com/azure-storage/azurite +``` + +```bash +docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite +``` + +`-p 10000:10000` will expose blob service's default listening port. +`-p 10001:10001` will expose queue service's default listening port. +`-p 10002:10002` will expose table service's default listening port. + +#### Run docker compose at root folder of project + +``` +make app_local_compose_up +``` + +For other ways, read out [DockerHub](https://github.com/Azure/Azurite/blob/main/README.md#dockerhub) + +### How to explorer data? + +- Install https://azure.microsoft.com/en-us/products/storage/storage-explorer +- Setup to access the local +Read our [Storage Explorer](https://learn.microsoft.com/en-us/azure/storage/storage-explorer/vs-azure-tools-storage-manage-with-storage-explorer) +Provide the account name and account key in varibles.yaml with [Well Known Storage Account And Key](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio%2Cblob-storage#well-known-storage-account-and-key) +[StorageAccountAndKey](images/DevAccountKey.png) \ No newline at end of file diff --git a/azure-blob-connector-product/README_DE.md b/azure-blob-connector-product/README_DE.md new file mode 100644 index 0000000..2ec6943 --- /dev/null +++ b/azure-blob-connector-product/README_DE.md @@ -0,0 +1,168 @@ +# Azure Blob Storage + +Azure Blob Storage ist ein Cloud-basierter Objektspeicherdienst, der von Microsoft Azure bereitgestellt wird. Er ermรถglicht es dir, groรŸe Mengen unstrukturierter Daten wie Bilder, Videos und Dokumente auf skalierbare und kostengรผnstige Weise zu speichern. Die Daten werden in Containern innerhalb eines Speicherkontos gespeichert und kรถnnen รผber HTTP/HTTPS abgerufen werden, wodurch sie sich ideal fรผr die Integration in Ihre Axon Ivy Business Prozesse eignen. + +Dieser Konnektor: +- Unterstรผtzt dich bei der Implementierung des Zugriffs auf Azure Blob Storage. +- Unterstรผtzt dich beim Hochladen von Inhalten auf Ihren Azure Blob Storage - direkt aus einem Axon Ivy Geschรคftsprozess. +- Erstellt einen Download-Link mit expiry date (โ€œVerfallsdatumโ€). + +## Setup + +In the project, you only add the dependency in your pom.xml and call public APIs + +**1. Add dependency** +```XML + + com.axonivy.connector + azure-blob-connector + ${process.analyzer.version} + +``` +**2. Azure Blob connection in variables** + +You need to provide Azure Blob connection in variables.yaml. Below is an example for connect by client secret +```yaml +Variables: + AzureBlob: + # The application ID that's assigned to your app. + ClientId: '' + # The client secret that you generated for your app in the app registration portal. + ClientSecret: '' + # The directory tenant the application plans to operate against, in GUID or domain-name format. + TenantId: '' + # https://.blob.core.windows.net/ + EndPoint: '' + # Your container name. + ContainterName: '' +``` + +### For Process GUI +**1. What is support in BlobStorage Callable Sub Process?** + ![azure-blob-connector](images/BlobStorageFunctions.png) + +**2. How to call an event from BlobStorage Callable Sub Process?** +- From Extensions on Tool Bar, we can see a BlobStorage element +![azure-blob-connector](images/ElementInExtensions.png) + +- We can draw a process with uploadFromUrl selection and field some information like: external url, blob name, the directory on Azure Blob Container, .. + +![azure-blob-connector](images/AddBlobStorageAndCallFunction.png) + +### For Java Developer +**1. Call the constructor to set some basic information. Each instance of the advanced process analyzer should care about one specific process model. This way we can store some private information (e.g. simplified model) in the instance and reuse it for different calculations on this object.** +```java + /** + * @param process - The process that should be analyzed. + */ + public AzureBlobStorageService(BlobServiceClient blobServiceClient, String container) +``` + +**2. Application requests to Azure Blob Storage must be authorized. You must to create a BlobServiceClient.** + + - This credential authenticates the created service principal through its client secret +```java + /** + * Create client to a storage account. + * @param clientId - the client ID of the application + * @param clientSecret - the secret value of the Microsoft Entra application. + * @param tenantId - the tenant ID of the application. + * @param endpoint - URL of the service + * @return a {@link BlobServiceClient} created from the configurations in this builder + */ + public static BlobServiceClient getBlobServiceClient(String clientId, String clientSecret, String tenantId, String endpoint) {} +``` + + - This credential authenticates the created service principal through its connection string +```java + /** + * Create client to a storage account. + * @param connectionString - connection string of the storage account + * @param endpoint - URL of the service + * @return a {@link BlobServiceClient} created from the configurations in this builder + */ + public static BlobServiceClient getBlobServiceClient(String connectionString, String endpoint) { +``` + + - This credential authenticates the created service principal through its account and key. +```java + /** + * * Create client to a storage account. + * @param accountName The account name associated with the request. + * @param accountKey The account access key used to authenticate the request. + * @param endpoint - URL of the service + * @return a {@link BlobServiceClient} created from the configurations in this builder + */ + public static BlobServiceClient getBlobServiceClient(String accountName, String accountKey, String endpoint) {} +``` + +**3. You can call `uploadFromUrl` to upload a file from url, `getDownloadLink` to get download link of a blob.** +```java + /** + * The API to copy operation from a source object URL + * @param url - The source URL to upload from + * @return - The blob name + */ + public String uploadFromUrl(String url); + + /** + * The API to create a temporary download link with expired time + * @param url - The blob name + * @return - The url for download + */ + public String getDownloadLink(String blobName); +``` + +### Example + +Below is a simple example for upload a file from url and get temporary download link. +``` + BlobServiceClient blobServiceClient = BlobServiceClientHelper.getBlobServiceClient(CLIENT_ID, SECRET_VALUE,TENANT_ID, END_POINT); + StorageService storageService = new AzureBlobStorageService(blobServiceClient, TEST_CONTAINTER); + + // Upload file from url + String blobName = storageService.uploadFromUrl("https://sample.com/video.mp4"); + // Get temporary download link + String downloadLink = storageService.getDownloadLink(blobName); +``` + +## Demo + +### Run with Azurite at local + +Start docker local: +You can run with docker or docker-compose + +#### Run Azurite V3 docker image + +> Note. Find more docker images tags in + +```bash +docker pull mcr.microsoft.com/azure-storage/azurite +``` + +```bash +docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite +``` + +`-p 10000:10000` will expose blob service's default listening port. +`-p 10001:10001` will expose queue service's default listening port. +`-p 10002:10002` will expose table service's default listening port. + +#### Run docker compose at root folder of project + +``` +make app_local_compose_up +``` + +For other ways, read out [DockerHub](https://github.com/Azure/Azurite/blob/main/README.md#dockerhub) + +### How to explorer data? + +- Install https://azure.microsoft.com/en-us/products/storage/storage-explorer +- Setup to access the local +Read our [Storage Explorer](https://learn.microsoft.com/en-us/azure/storage/storage-explorer/vs-azure-tools-storage-manage-with-storage-explorer) + +Provide the account name and account key in varibles.yaml with [Well Known Storage Account And Key](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio%2Cblob-storage#well-known-storage-account-and-key) + +[StorageAccountAndKey](images/DevAccountKey.png) \ No newline at end of file diff --git a/azure-blob-connector-product/images/DevAccountKey.png b/azure-blob-connector-product/images/DevAccountKey.png new file mode 100644 index 0000000000000000000000000000000000000000..c0b4721905c64a569a897e7f54718d40bf64b6f2 GIT binary patch literal 8290 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 - 4.0.0 - com.axonivy.cloud.storage - azure-blob-connector-product - 10.0.21-SNAPSHOT - pom - - - ../workflow-estimator/config/variables.yaml - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.3.0 - - - package - - single - - - false - - zip.xml - - - - - - - maven-antrun-plugin - 1.7 - - - generate-sources - - ${skip-readme} - - - - - - - - - - - run - - - - - - - - - org.apache.maven.plugins - maven-deploy-plugin - 3.0.0-M1 - - - - + + + 4.0.0 + com.axonivy.connector.azure.blob + azure-blob-connector-product + 10.0.22-SNAPSHOT + pom + + ../azure-blob-connector/config/variables.yaml + + + + + + maven-deploy-plugin + 3.0.0-M1 + + + + + + maven-assembly-plugin + 3.3.0 + + + package + + single + + + false + + zip.xml + + + + + + + maven-antrun-plugin + 1.7 + + + generate-sources + + run + + + ${skip-readme} + + + + + + + + + + + + + + diff --git a/azure-blob-connector-product/product.json b/azure-blob-connector-product/product.json index 416b995..3c0edd7 100644 --- a/azure-blob-connector-product/product.json +++ b/azure-blob-connector-product/product.json @@ -6,10 +6,16 @@ "data": { "projects": [ { - "groupId": "com.axonivy.cloud.storage", + "groupId": "com.axonivy.connector.azure.blob", "artifactId": "azure-blob-connector-demo", "version": "${version}", "type": "iar" + }, + { + "groupId": "com.axonivy.connector.azure.blob", + "artifactId": "azure-blob-connector", + "version": "${version}", + "type": "iar" } ], "repositories": [ @@ -28,7 +34,7 @@ "data": { "dependencies": [ { - "groupId": "com.axonivy.cloud.storage", + "groupId": "com.axonivy.connector.azure.blob", "artifactId": "azure-blob-connector", "version": "${version}", "type": "iar" diff --git a/azure-blob-connector-test/.gitignore b/azure-blob-connector-test/.gitignore index 1b2547b..064fd4a 100644 --- a/azure-blob-connector-test/.gitignore +++ b/azure-blob-connector-test/.gitignore @@ -17,3 +17,4 @@ classes/ src_dataClasses/ src_wsproc/ logs/ +credentials.properties diff --git a/azure-blob-connector-test/.settings/ch.ivyteam.ivy.designer.prefs b/azure-blob-connector-test/.settings/ch.ivyteam.ivy.designer.prefs index f2c4666..bcba9f6 100644 --- a/azure-blob-connector-test/.settings/ch.ivyteam.ivy.designer.prefs +++ b/azure-blob-connector-test/.settings/ch.ivyteam.ivy.designer.prefs @@ -1,5 +1,5 @@ -ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_DATA_CLASS=com.axonivy.cloud.storage.azure.blob.connector.test.Data -ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_NAMESPACE=com.axonivy.cloud.storage.azure.blob.connector.test +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_DATA_CLASS=com.axonivy.connector.azure.blob.test.Data +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_NAMESPACE=com.axonivy.connector.azure.blob.test ch.ivyteam.ivy.project.preferences\:PRIMEFACES_VERSION=11 ch.ivyteam.ivy.project.preferences\:PROJECT_VERSION=100000 eclipse.preferences.version=1 diff --git a/azure-blob-connector-test/pom.xml b/azure-blob-connector-test/pom.xml index 45acdd5..de909fe 100644 --- a/azure-blob-connector-test/pom.xml +++ b/azure-blob-connector-test/pom.xml @@ -1,9 +1,9 @@ 4.0.0 - com.axonivy.cloud.storage + com.axonivy.connector.azure.blob azure-blob-connector-test - 10.0.21-SNAPSHOT + 10.0.22-SNAPSHOT iar 10.0.16 @@ -11,7 +11,7 @@ - com.axonivy.cloud.storage + com.axonivy.connector.azure.blob azure-blob-connector ${project.version} iar @@ -62,6 +62,16 @@ ${project.build.plugin.version} true + + maven-surefire-plugin + 3.0.0-M5 + + + ${azureblob.account} + ${azureblob.key} + + + diff --git a/azure-blob-connector-test/resource_test/picture/singapore.png b/azure-blob-connector-test/resource_test/picture/singapore.png index 8ee3c1f47f4e632cd4e945a0ec14f9a035e5a318..ab8ffa9eb2fcea6eb3c301395e31df4c7c0426a1 100644 GIT binary patch literal 7317 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 Mvoj|Oe{;F>Uv+pHwg3PC literal 9197 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 D#uuaK diff --git a/azure-blob-connector-test/src_test/com/axonivy/connector/azure/blob/test/integration/AbstractIntegrationTest.java b/azure-blob-connector-test/src_test/com/axonivy/connector/azure/blob/test/integration/AbstractIntegrationTest.java new file mode 100644 index 0000000..a62a177 --- /dev/null +++ b/azure-blob-connector-test/src_test/com/axonivy/connector/azure/blob/test/integration/AbstractIntegrationTest.java @@ -0,0 +1,67 @@ +package com.axonivy.connector.azure.blob.test.integration; + +import java.util.UUID; +import java.io.IOException; +import java.util.Properties; + +import org.apache.commons.lang3.StringUtils; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +import com.axonivy.connector.azure.blob.BlobServiceClientHelper; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.specialized.BlockBlobClient; + +abstract class AbstractIntegrationTest { + + protected static final String TEST_CONTAINTER = "test-container"; + private static final String END_POINT_FORMAT = "http://127.0.0.1:%s/%s"; + + protected static final String EXTENSION_TEST = ".test"; + protected static final String CONTENT_TEST = "testContent.txt"; + private static final int BLOB_PORT = 10000; + + public static final GenericContainer azure = new GenericContainer<>( + DockerImageName.parse("mcr.microsoft.com/azure-storage/azurite")).withExposedPorts(BLOB_PORT); + + static { + azure.start(); + } + + protected static BlobServiceClient getBlobServiceClient() throws IOException { + String account = System.getProperty("azureBlobAccount"); + String key = System.getProperty("azureBlobKey"); + + if (StringUtils.isEmpty(account)) { + try (var in = AbstractIntegrationTest.class.getResourceAsStream("credentials.properties")) { + if (in != null) { + Properties props = new Properties(); + props.load(in); + account = (String) props.get("account"); + key = (String) props.get("key"); + } + } + } + + final String endpoint = String.format(END_POINT_FORMAT, azure.getMappedPort(BLOB_PORT),account); + return BlobServiceClientHelper.getBlobServiceClient(account, key, endpoint); + } + + protected static BlockBlobClient getBlockBlobClient(BlobServiceClient blobServiceClient, String blobName) { + BlobContainerClient blobContainerClient = getBlobContainerClient(blobServiceClient); + BlockBlobClient blobClient = blobContainerClient.getBlobClient(blobName).getBlockBlobClient(); + return blobClient; + } + + protected static BlobContainerClient getBlobContainerClient(BlobServiceClient blobServiceClient) { + BlobContainerClient blobContainerClient = blobServiceClient.createBlobContainerIfNotExists(TEST_CONTAINTER); + return blobContainerClient; + } + + protected boolean isUUIDFomart(String value, String expectedExtension) { + String uuid = value.split("\\.")[0]; + + return UUID.fromString(uuid) != null && value.endsWith("." + expectedExtension); + } +} diff --git a/azure-blob-connector-test/src_test/com/axonivy/connector/azure/blob/test/integration/AzureBlobStorageServiceTest.java b/azure-blob-connector-test/src_test/com/axonivy/connector/azure/blob/test/integration/AzureBlobStorageServiceTest.java new file mode 100644 index 0000000..d613652 --- /dev/null +++ b/azure-blob-connector-test/src_test/com/axonivy/connector/azure/blob/test/integration/AzureBlobStorageServiceTest.java @@ -0,0 +1,131 @@ +package com.axonivy.connector.azure.blob.test.integration; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.axonivy.connector.azure.blob.StorageService; +import com.axonivy.connector.azure.blob.internal.AzureBlobStorageService; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.specialized.BlockBlobClient; + +@Testcontainers +@TestMethodOrder(OrderAnnotation.class) +public class AzureBlobStorageServiceTest extends AbstractIntegrationTest { + + private static BlobServiceClient blobServiceClient = null; + private static StorageService storageService = null; + private static String blobNameOfFileUpload = null; + + @BeforeAll + public static void setup() throws IOException { + blobServiceClient = getBlobServiceClient(); + storageService = new AzureBlobStorageService(blobServiceClient, TEST_CONTAINTER); + } + + @Test + void shouldUploadContent() throws Exception { + final String content = "Test Content"; + storageService.upload(content, CONTENT_TEST); + BlockBlobClient blobClient = getBlockBlobClient(blobServiceClient, CONTENT_TEST); + String downloadContent = blobClient.downloadContent().toString(); + + assertEquals(content, downloadContent); + } + + @Test + @Order(1) + void shouldUploadFromFile() { + String path = new File("resource_test/picture/singapore.png").getAbsolutePath(); + String blobName = storageService.uploadFromFile(path, StringUtils.EMPTY, true); + blobNameOfFileUpload = blobName; + assertNotNull(blobName); + } + + @Test + void shouldUploadByteArray() throws Exception { + String sampleData = "Sample data for blob"; + String blobName = storageService.upload(sampleData.getBytes(), CONTENT_TEST, StringUtils.EMPTY, true); + assertNotNull(blobName); + } + + @Test + void shouldUploadByteArrayWithException() throws Exception { + var exception = assertThrows( + Exception.class, + () -> storageService.upload(null, "nullContent.txt", StringUtils.EMPTY, false)); + + assertEquals("Upload file error. Exception message: Cannot read the array length because \"buf\" is null", exception.getMessage()); + } + + @Test + @Order(2) + void shouldDownloadContent() { + byte[] result = storageService.downloadContent(blobNameOfFileUpload); + assertNotNull(result); + } + + @Test + @Order(3) + void shouldDownloadToFile() throws IOException { + File templeLocalFile = new File("resource_test/picture/local-file.png"); + storageService.downloadToFile(blobNameOfFileUpload, templeLocalFile.getAbsolutePath()); + boolean fileDeleted = Files.deleteIfExists(templeLocalFile.toPath()); + assertTrue(fileDeleted); + } + + @Test + @Order(4) + void shouldDownloadStream() throws IOException { + ByteArrayOutputStream baos = storageService.downloadStream(blobNameOfFileUpload); + assertNotNull(baos); + } + + @Test + @Order(5) + void shouldDeletedWithBlobIsExists() { + boolean result = storageService.delete(blobNameOfFileUpload); + assertTrue(result); + } + + @Test + @Order(6) + void shouldDeletedWithBlobIsNotExists() { + boolean result = storageService.delete(blobNameOfFileUpload); + assertFalse(result); + } + + @Test + @Order(7) + void shouldRestoreBlobIsDeleted() { + // Azurite: Current API is not implemented yet. + } + + + + @Disabled + void shouldGetDownloadLink() throws Exception { + // Azurite: Only authentication scheme Bearer is supported + } + + @Disabled + void shouldUploadFromUrl() throws Exception { + // Azurite: Current API is not implemented yet. + } +} diff --git a/azure-blob-connector/.settings/ch.ivyteam.ivy.designer.prefs b/azure-blob-connector/.settings/ch.ivyteam.ivy.designer.prefs index 4948f7b..3bfd371 100644 --- a/azure-blob-connector/.settings/ch.ivyteam.ivy.designer.prefs +++ b/azure-blob-connector/.settings/ch.ivyteam.ivy.designer.prefs @@ -1,5 +1,5 @@ -ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_DATA_CLASS=com.axonivy.cloud.storage.azure.blob.connector.Data -ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_NAMESPACE=com.axonivy.cloud.storage.azure.blob.connector +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_DATA_CLASS=com.axonivy.connector.azure.blob.Data +ch.ivyteam.ivy.designer.preferences.DataClassPreferencePage\:DEFAULT_NAMESPACE=com.axonivy.connector.azure.blob ch.ivyteam.ivy.project.preferences\:PRIMEFACES_VERSION=11 ch.ivyteam.ivy.project.preferences\:PROJECT_VERSION=100000 eclipse.preferences.version=1 diff --git a/azure-blob-connector/dataclasses/com/axonivy/connector/azure/blob/BlobStorageData.ivyClass b/azure-blob-connector/dataclasses/com/axonivy/connector/azure/blob/BlobStorageData.ivyClass new file mode 100644 index 0000000..62ff5a2 --- /dev/null +++ b/azure-blob-connector/dataclasses/com/axonivy/connector/azure/blob/BlobStorageData.ivyClass @@ -0,0 +1,15 @@ +BlobStorageData #class +com.axonivy.connector.azure.blob #namespace +date java.util.Date #field +azureBlobStorageBean com.axonivy.connector.azure.blob.internal.bean.AzureBlobStorageBean #field +isSuccess Boolean #field +blobItems java.util.List #field +blobName String #field +linkDownload String #field +localPath String #field +uploadToFolder String #field +isOverwriteFile Boolean #field +url String #field +content java.io.InputStream #field +functionName String #field +isExist Boolean #field diff --git a/azure-blob-connector/pom.xml b/azure-blob-connector/pom.xml index 07458cc..22cd148 100644 --- a/azure-blob-connector/pom.xml +++ b/azure-blob-connector/pom.xml @@ -1,9 +1,9 @@ 4.0.0 - com.axonivy.cloud.storage + com.axonivy.connector.azure.blob azure-blob-connector - 10.0.21-SNAPSHOT + 10.0.22-SNAPSHOT iar 10.0.16 diff --git a/azure-blob-connector/processes/BlobStorage.p.json b/azure-blob-connector/processes/BlobStorage.p.json index 1553ba7..4d8f9b5 100644 --- a/azure-blob-connector/processes/BlobStorage.p.json +++ b/azure-blob-connector/processes/BlobStorage.p.json @@ -3,7 +3,7 @@ "id" : "1905E51E1156B82C", "kind" : "CALLABLE_SUB", "config" : { - "data" : "com.axonivy.cloud.storage.azure.blob.connector.BlobStorageData" + "data" : "com.axonivy.connector.azure.blob.BlobStorageData" }, "elements" : [ { "id" : "f0", @@ -500,7 +500,7 @@ "config" : { "output" : { "code" : [ - "import com.axonivy.cloud.storage.azure.blob.connector.internal.bean.AzureBlobStorageBean;", + "import com.axonivy.connector.azure.blob.internal.bean.AzureBlobStorageBean;", "", "in.azureBlobStorageBean = new AzureBlobStorageBean();" ] diff --git a/azure-blob-connector/src/com/axonivy/connector/azure/blob/BlobServiceClientHelper.java b/azure-blob-connector/src/com/axonivy/connector/azure/blob/BlobServiceClientHelper.java new file mode 100644 index 0000000..47c4b12 --- /dev/null +++ b/azure-blob-connector/src/com/axonivy/connector/azure/blob/BlobServiceClientHelper.java @@ -0,0 +1,66 @@ +package com.axonivy.connector.azure.blob; + +import com.azure.identity.ClientSecretCredential; +import com.azure.identity.ClientSecretCredentialBuilder; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; +import com.azure.storage.common.StorageSharedKeyCredential; + +public class BlobServiceClientHelper { + + /** + * Create client to a storage account. + * @param clientId - the client ID of the application + * @param clientSecret - the secret value of the Microsoft Entra application. + * @param tenantId - the tenant ID of the application. + * @param endpoint - URL of the service + * @return a {@link BlobServiceClient} created from the configurations in this builder + */ + public static BlobServiceClient getBlobServiceClient(String clientId, String clientSecret, String tenantId, String endpoint) { + + ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder() + .clientId(clientId) + .clientSecret(clientSecret) + .tenantId(tenantId) + .build(); + + BlobServiceClient blobServiceClient = new BlobServiceClientBuilder() + .endpoint(endpoint) + .credential(clientSecretCredential) + .buildClient(); + + return blobServiceClient; + } + + /** + * Create client to a storage account. + * @param connectionString - onnection string of the storage account + * @param endpoint - URL of the service + * @return a {@link BlobServiceClient} created from the configurations in this builder + */ + public static BlobServiceClient getBlobServiceClient(String connectionString, String endpoint) { + BlobServiceClient blobServiceClient = new BlobServiceClientBuilder() + .endpoint(endpoint) + .connectionString(connectionString) + .buildClient(); + + return blobServiceClient; + } + + /** + * * Create client to a storage account. + * @param accountName The account name associated with the request. + * @param accountKey The account access key used to authenticate the request. + * @param endpoint - URL of the service + * @return a {@link BlobServiceClient} created from the configurations in this builder + */ + public static BlobServiceClient getBlobServiceClient(String accountName, String accountKey, String endpoint) { + StorageSharedKeyCredential credential = new StorageSharedKeyCredential(accountName, accountKey); + BlobServiceClient blobServiceClient = new BlobServiceClientBuilder() + .endpoint(endpoint) + .credential(credential) + .buildClient(); + + return blobServiceClient; + } +} diff --git a/azure-blob-connector/src/com/axonivy/connector/azure/blob/StorageService.java b/azure-blob-connector/src/com/axonivy/connector/azure/blob/StorageService.java new file mode 100644 index 0000000..926528f --- /dev/null +++ b/azure-blob-connector/src/com/axonivy/connector/azure/blob/StorageService.java @@ -0,0 +1,128 @@ +package com.axonivy.connector.azure.blob; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Date; +import java.util.List; + +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.models.BlobItem; + +public interface StorageService { + + /** + * The API to create a blob string with content + * @param content - The string content + * @param fileName - The file name + * @return - the blob name + */ + public String upload(String content, String fileName); + + /** + * The API to create a blob from local machine with specific path file + * @param path - path file + * @return - The blob name + * */ + public String uploadFromFile(String path); + + /** + * The API to create a blob from local machine with specific path file + * @param path - path file + * @param uploadToFolder - The name of folder + * @param isOverwrite - boolean + * @return - The blob name + * */ + public String uploadFromFile(String path, String uploadToFolder, boolean isOverwite); + + /** + * The API to create a blob from GUI with upload function + * @param content - byte[] + * @param fileName - file name + * @return - The blob name + * */ + public String upload(byte[] content, String fileName) throws Exception; + + /** + * The API to create a blob from GUI with upload function + * @param content - byte[] + * @param fileName - file name + * @param uploadToFolder - The name of folder + * @param isOverwrite - boolean + * @return - The blob name + * */ + public String upload(byte[] content, String fileName, String uploadToFolder, boolean isOverwite) throws Exception; + + /** + * The API to copy operation from a source object URL + * @param url - The source URL to upload from + * @return - The blob name + */ + public String uploadFromUrl(String url); + + /** + * The API to copy operation from a source object URL + * @param url - The source URL to upload from + * @param uploadToFolder - The name of folder + * @param isOverwrite - boolean + * @return - The blob name + */ + public String uploadFromUrl(String url, String uploadToFolder, boolean isOverwite); + + /** + * The API to delete blob + * @param blob name + * @return - boolean + * */ + public boolean delete(String blobName); + + /** + * The API to delete blob + * @param specific date to delete all blobs + * */ + public void delete(Date date); + + /** + * The API to restore an deleted blob if soft deleted for blobs is enable. + * @param blob name + * */ + public void restore(String blobName); + + /** + * The API to download content + * @param blob name + * @return bye[] + * */ + public byte[] downloadContent(String blobName); + + /** + * The API to download from file + * @param blob name + * @param filePath + * */ + public void downloadToFile(String blobName, String filePath); + + /** + * The API to download to a stream + * @param blob name + * */ + public ByteArrayOutputStream downloadStream(String blobName) throws IOException; + + /** + * The API to get the list blob + * */ + public List getBlobs(); + + /** + * The API to create a temporary download link with expired time + * @param blobName - The blob name + * @return - The url for download + */ + public String getDownloadLink(String blobName); + + /** + * The API to get blob client + * @param blobName - The blob name + * @return BlobClient + * */ + public BlobClient getBlobClient(String blobName); +} diff --git a/azure-blob-connector/src/com/axonivy/connector/azure/blob/internal/AzureBlobStorageService.java b/azure-blob-connector/src/com/axonivy/connector/azure/blob/internal/AzureBlobStorageService.java new file mode 100644 index 0000000..7134b1e --- /dev/null +++ b/azure-blob-connector/src/com/axonivy/connector/azure/blob/internal/AzureBlobStorageService.java @@ -0,0 +1,252 @@ +package com.axonivy.connector.azure.blob.internal; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpStatus; + +import com.axonivy.connector.azure.blob.StorageService; +import com.axonivy.connector.azure.blob.internal.helper.BlobSASHelper; +import com.azure.core.http.rest.PagedResponse; +import com.azure.core.http.rest.Response; +import com.azure.core.util.BinaryData; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.models.BlobItem; +import com.azure.storage.blob.models.BlobListDetails; +import com.azure.storage.blob.models.DeleteSnapshotsOptionType; +import com.azure.storage.blob.models.ListBlobsOptions; +import com.azure.storage.blob.options.BlobDownloadToFileOptions; +import com.azure.storage.blob.specialized.BlockBlobClient; +import com.azure.storage.common.ParallelTransferOptions; + +import ch.ivyteam.ivy.environment.Ivy; + +/** + * For first version, only upload file to demo-container. In next, maybe create + * virtual directory inside container. + */ +public class AzureBlobStorageService implements StorageService { + private static final String DATE_PATTERN = "yyyy-MM-dd"; + private BlobContainerClient destinationContainer = null; + private BlobServiceClient blobServiceClient = null; + private Duration downloadLinkLiveTime = Duration.ofHours(8); + private static final long FIVE_MB = (5 * 1024 * 1024); + private static final long FOUR_MB = (4 * 1024 * 1024); + private static final int MAX_CONCURRENCY = 4; + + /** + * @param blobServiceClient - A client to interact with the Blob Service at the account level + * @param container - The container name + */ + public AzureBlobStorageService(BlobServiceClient blobServiceClient, String container) { + this.blobServiceClient = blobServiceClient; + this.destinationContainer = getBlobContainerClient(this.blobServiceClient, container); + } + + public AzureBlobStorageService(BlobServiceClient blobServiceClient, String container, + Duration downloadLinkLiveTime) { + this.blobServiceClient = blobServiceClient; + this.destinationContainer = getBlobContainerClient(this.blobServiceClient, container); + this.downloadLinkLiveTime = downloadLinkLiveTime; + } + + @Override + public String upload(String content, String fileName) { + BlockBlobClient blockBlobClient = getBlobClient(fileName).getBlockBlobClient(); + blockBlobClient.upload(BinaryData.fromString(content)); + return blockBlobClient.getBlobName(); + } + + @Override + public String uploadFromUrl(String sourceURL, String uploadToFolder, boolean isOverwite) { + String fileName = getFileNameFromUrl(sourceURL); + String blobName = createBlobPath(uploadToFolder, fileName); + BlobClient destination = getBlobClient(blobName); + + destination.getBlockBlobClient().uploadFromUrl(sourceURL, isOverwite); + return destination.getBlockBlobClient().getBlobName(); + } + + @Override + public String uploadFromFile(String path, String uploadToFolder, boolean isOverwite) { + String fileName = FilenameUtils.getName(path); + String blobName = createBlobPath(uploadToFolder, fileName); + BlobClient blobClient = getBlobClient(blobName); + blobClient.uploadFromFile(path, isOverwite); + return blobClient.getBlockBlobClient().getBlobName(); + } + + @Override + public String getDownloadLink(String blobName) { + BlobClient blobClient = getBlobClient(blobName); + String sasToken = BlobSASHelper.createServiceSASBlob(blobClient, this.downloadLinkLiveTime); + String downloadLink = blobClient.getBlobUrl() + "?" + sasToken; + return downloadLink; + } + + @Override + public String upload(byte[] content, String fileName, String uploadToFolder, boolean isOverwite) throws Exception { + String blobName = createBlobPath(uploadToFolder, fileName); + BlockBlobClient blockBlobClient = getBlobClient(blobName).getBlockBlobClient(); + + try (ByteArrayInputStream dataStream = new ByteArrayInputStream(content)) { + blockBlobClient.upload(dataStream, content.length, isOverwite); + return blockBlobClient.getBlobName(); + } catch (Exception ex) { + throw new Exception("Upload file error. Exception message: " + ex.getMessage(), ex); + } + } + + @Override + public boolean delete(String blobName) { + BlobClient blobClient = getBlobClient(blobName); + + Response response = blobClient.deleteIfExistsWithResponse(DeleteSnapshotsOptionType.INCLUDE, null, null, null); + return response.getStatusCode() != HttpStatus.SC_NOT_FOUND; + } + + @Override + public void restore(String blobName) { + BlobClient blobClient = getBlobClient(blobName); + blobClient.undelete(); + } + + @Override + public byte[] downloadContent(String blobName) { + BlockBlobClient blockBlobClient = getBlobClient(blobName).getBlockBlobClient(); + BinaryData data = blockBlobClient.downloadContent(); + return data.toBytes(); + } + + @Override + public void downloadToFile(String blobName, String filePath) { + BlockBlobClient blockBlobClient = getBlobClient(blobName).getBlockBlobClient(); + long blogSize = blockBlobClient.getProperties().getBlobSize(); + if (blogSize >= FIVE_MB) { + downloadFileWithLargeSize(blobName, filePath); + } else { + blockBlobClient.downloadToFile(filePath); + } + } + + @Override + public ByteArrayOutputStream downloadStream(String blobName) throws IOException { + BlockBlobClient blockBlobClient = getBlobClient(blobName).getBlockBlobClient(); + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + blockBlobClient.downloadStream(outputStream); + return outputStream; + } catch (IOException e) { + throw new IOException ("download file error. Exception message: " + e.getMessage(), e); + } + } + + @Override + public BlobClient getBlobClient(String blobName) { + return this.destinationContainer.getBlobClient(blobName); + } + + private void downloadFileWithLargeSize(String blobName, String filePath) { + BlockBlobClient blockBlobClient = getBlobClient(blobName).getBlockBlobClient(); + ParallelTransferOptions parallelTransferOptions = new ParallelTransferOptions() + .setBlockSizeLong(FOUR_MB) + .setMaxConcurrency(MAX_CONCURRENCY); + + BlobDownloadToFileOptions options = new BlobDownloadToFileOptions(filePath); + options.setParallelTransferOptions(parallelTransferOptions); + + blockBlobClient.downloadToFileWithResponse(options, null, null); + } + + private BlobContainerClient getBlobContainerClient(BlobServiceClient blobServiceClient, String container) { + if (ObjectUtils.anyNull(container)) { + throw new NullPointerException("The container are not allow null."); + } + + BlobContainerClient blobContainerClient = blobServiceClient.createBlobContainerIfNotExists(container); + return blobContainerClient; + } + + private static String getFileNameFromUrl(String url) { + try { + return Paths.get(new URI(url).getPath()).getFileName().toString(); + } catch (URISyntaxException e) { + Ivy.log().warn("Can not get file name from " + url); + } + return StringUtils.EMPTY; + } + + private String createBlobPath(String folderName, String fileName) { + String path = StringUtils.isNotBlank(folderName) ? folderName + "/" : StringUtils.EMPTY; + return path + fileName; + } + + @Override + public String uploadFromUrl(String url) { + String blobName = getFileNameFromUrl(url); + BlobClient destination = getBlobClient(blobName); + destination.getBlockBlobClient().uploadFromUrl(url, false); + return destination.getBlockBlobClient().getBlobName(); + } + + @Override + public String uploadFromFile(String path) { + String blobName = FilenameUtils.getName(path); + BlobClient blobClient = getBlobClient(blobName); + blobClient.uploadFromFile(path); + return blobClient.getBlockBlobClient().getBlobName(); + } + + @Override + public String upload(byte[] content, String fileName) throws Exception { + BlockBlobClient blockBlobClient = getBlobClient(fileName).getBlockBlobClient(); + + try (ByteArrayInputStream dataStream = new ByteArrayInputStream(content)) { + blockBlobClient.upload(dataStream, content.length); + return blockBlobClient.getBlobName(); + } catch (Exception ex) { + throw new Exception("Upload file error. Exception message: " + ex.getMessage(), ex); + } + } + + @Override + public List getBlobs() { + List blobItems = new ArrayList<>(); + ListBlobsOptions options = new ListBlobsOptions() + .setDetails(new BlobListDetails().setRetrieveDeletedBlobs(true)); + Iterable> blobPages = destinationContainer.listBlobs(options, null).iterableByPage(); + for (PagedResponse page : blobPages) { + page.getElements().forEach(blob -> blobItems.add(blob)); + } + return blobItems; + } + + @Override + public void delete(Date date) { + List bi = destinationContainer.listBlobs().stream() + .filter(blobItem -> isSameDate(blobItem, date)) + .collect(Collectors.toList()); + bi.forEach(blob -> delete(blob.getName())); + } + + private boolean isSameDate(BlobItem blobItem, Date date) { + String creationTime2String = blobItem.getProperties().getCreationTime().format(DateTimeFormatter.ofPattern(DATE_PATTERN)); + String date2String = new SimpleDateFormat(DATE_PATTERN).format(date); + return creationTime2String.equals(date2String); + } +} diff --git a/azure-blob-connector/src/com/axonivy/connector/azure/blob/internal/bean/AzureBlobStorageBean.java b/azure-blob-connector/src/com/axonivy/connector/azure/blob/internal/bean/AzureBlobStorageBean.java new file mode 100644 index 0000000..a97a5bc --- /dev/null +++ b/azure-blob-connector/src/com/axonivy/connector/azure/blob/internal/bean/AzureBlobStorageBean.java @@ -0,0 +1,36 @@ +package com.axonivy.connector.azure.blob.internal.bean; + +import com.axonivy.connector.azure.blob.BlobServiceClientHelper; +import com.axonivy.connector.azure.blob.StorageService; +import com.axonivy.connector.azure.blob.internal.AzureBlobStorageService; +import com.azure.storage.blob.BlobServiceClient; + +import ch.ivyteam.ivy.environment.Ivy; + +public class AzureBlobStorageBean { + + private StorageService azureBlobStorageService; + + public AzureBlobStorageBean() { + String clientId = Ivy.var().get("AzureBlob.ClientId"); + String clientSecret = Ivy.var().get("AzureBlob.ClientSecret"); + String tenantId = Ivy.var().get("AzureBlob.TenantId"); + String endPoint = Ivy.var().get("AzureBlob.EndPoint"); + String containerName = Ivy.var().get("AzureBlob.ContainterName"); + + BlobServiceClient blobServiceClient = BlobServiceClientHelper.getBlobServiceClient(clientId, clientSecret, tenantId, endPoint); + azureBlobStorageService = new AzureBlobStorageService(blobServiceClient, containerName); + } + + public StorageService getAzureBlobStorageService() { + return azureBlobStorageService; + } + + public void setAzureBlobStorageService(StorageService azureBlobStorageService) { + this.azureBlobStorageService = azureBlobStorageService; + } + + public boolean isBlobExist(String blobName) { + return azureBlobStorageService.getBlobClient(blobName).exists(); + } +} diff --git a/azure-blob-connector/src/com/axonivy/connector/azure/blob/internal/helper/BlobSASHelper.java b/azure-blob-connector/src/com/axonivy/connector/azure/blob/internal/helper/BlobSASHelper.java new file mode 100644 index 0000000..cbf1adf --- /dev/null +++ b/azure-blob-connector/src/com/axonivy/connector/azure/blob/internal/helper/BlobSASHelper.java @@ -0,0 +1,32 @@ +package com.axonivy.connector.azure.blob.internal.helper; + +import java.time.Duration; +import java.time.OffsetDateTime; + +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.models.UserDelegationKey; +import com.azure.storage.blob.sas.BlobSasPermission; +import com.azure.storage.blob.sas.BlobServiceSasSignatureValues; + +public class BlobSASHelper { + + public static String createServiceSASBlob(BlobClient blobClient, Duration liveTime) { + + OffsetDateTime now = OffsetDateTime.now(); + UserDelegationKey key = blobClient.getBlockBlobClient() + .getContainerClient() + .getServiceClient() + .getUserDelegationKey(now.minusMinutes(1), now.plusSeconds(liveTime.getSeconds())); + + OffsetDateTime expiryTime = OffsetDateTime.now().plusSeconds(liveTime.toSeconds()); + + // Assign read permissions to the SAS token + BlobSasPermission sasPermission = new BlobSasPermission().setReadPermission(true); + + var sasSignatureValues = new BlobServiceSasSignatureValues(expiryTime, sasPermission) + .setStartTime(now.minusMinutes(1)); + + String sasToken = blobClient.generateUserDelegationSas(sasSignatureValues, key); + return sasToken; + } +} diff --git a/azure-blob-connector/webContent/icon/azure-blob-icon.png b/azure-blob-connector/webContent/icon/azure-blob-icon.png index d94371448c610492afc354a236ae1754c36f48b9..0130ca2c37b58dc83b4f8ccba6d7298668bb78fa 100644 GIT binary patch literal 2040 zcmb_cX*8T!8opI1v2T%(#1guR?WF9c7{oAWRTN5u5Hv-q>R39K*y|H%Nvv@Qf&`&j ziBY1et7vOYg6`0gPS7DL*0ID=9bzd_6Bb#e3p009pX;1~)v0oUl^un~>8fV%)dOM%ouh#2f^hPrs+07%vW0J;gl z9vp&}0EkBeupA5krVN0SF_rC}Ht+=MYd2>{AUyo4yXuPo5SMdxyg*2NyY}u~PC$g3 z_=l<3@B|%J1@>a3>s3p}c~6SjooP4GrhEEV!d%@*IDT=q(D9sCv}l*&rbF{_L#^d+ zaW|&(=ovSfP#pPlC`kuJW(&??_C+@_Jf8}+4`W|HSghPljbc@(JxqIXJ$-8YqvGM6 z{V?~aVQS(N1is{b?BdIAm4*ZNY{Dh-PiCT81I4o`&FSk6C$(uaA`(#OV$f$l$K=Tk z2cPdU1Va=Lo3!O_k?FDB(yAPn9(Enxm?X&a)u1lWT12K<)V)uiN0x-zPH{~-1coe6 z)lt|Y6_mXFR=8ijwx9Y+$jQ{u#{kE;9On8^q}+O>Fq5Fp&c)BmyJ%=w1FhE~ZpV~m zA%d;NXHk5A6zqIHQ=RL~4@H7yEP&u#0?niJ0($_EK>q&`LUI*<|1s3?3y-hi@ta*D zscfLtNQtls&S$q)ow5YBrmZ`*dNgMvZGyL7i7x?6PwGNU!zTHWG6@({HIj}9M#2O1 zkTO>wr*nu-9~?!hk$^1)xsqBZjP2|+nl2h-KfUV^FN4Y)CUgW@JiZxm1X|S>%vsR^Bmvr{u2Yp@vaxDQLVYe(RT`Bb$u?7wCP1DR z78a|=$)vQjKrxm1g`tX;_I7eB-oP@7cl%0nJ+f1&^n6hM`1trz941Q1eg4PKTU%Rq zaGcuwnQ(?wD~EG}LZKwcW4*S#FZrJi{g9H`@r`bP zIyQbSTkFK>CNhd!7qA@N>yh$iZfgD)*+(1UT79t(cKVCf7!tw7qcfW6ZS9JmJ|Mo> zim@CJjPB(L@U_Z0d7PJk^YO?~IL&M6mj?sPT;Cc#|`-Aw% z2siTYA=$)4Vi$n)76`#(XCF$79)tPATbB)-`aPwPB)X<5;@|uMm_K!)54;ALGy`>6 z$x%j|K0r^3PK*2rjR5BN0>SYtS@Pq;8KVqlBS0>iL!fO>HJ^6md^KIprZM zukS;B>kNgDfBiXr24+==s@(gjMzH); z90zK7Z+W+?0uEB7Q-LdPb1>v!<8`0aSlY{c50QHvy3>?5%@{!g&G#ZBY^R(l1+`lI z3QOgE5T%Rk1G(C$grPV29%+rx#0?Qxp-Eyf`smeF7sjC|tC99i{ZtPW@~E8Duz@l# zcRB!#I6@xggU~T%($z+i4tx>tO~S4qa_8{BWmAwq$=;Ualhu%!{MAf4(2kGrhlM&4 z$!4YC#>Kd^{4Uu2zOADXnY;0Pcc-s(=X`IKY3D6W?#hRQzpVFmf4i!0Us8bD-xIez zQAHIL3Ko|iv~{sVcJO|zq5FwKe$*MBY1RA+WuZMrd||sc0OS}mTq?H`N0^!6L z>N2P@0I6xh5>SawJt{Z2gEdV^Mc%$WLG-gF->)toV0Nb2DREay-`11HbUV7;2;>_>?qBoc|ZDxs?lz&tWP}gb!;5E8EU{ZO1{pCkdazllEyR_mTcJC!QsuOe?VbGnr<#T#r&Zv1$;Q;4N-r{@rf zo)bT@G^o-D8D? zT}-TOD@~q?(wAKGN;R`0SMq%K+F7)v2=-(QgqttDzj)J&3C6fZaChoIKrjpbC~7}Om0>LUlyoRM_`j#^v9C3 z2}i)`Qy|Y@*^wk00D8}A0?$jpx|1(OBF{m`65wi$94;=i7K_(p`WYrC$|yQqEMHl_Q?DSM__dc8d2@5qEb?;GBGjPLQ4jD;ZcB-Er^nq;766q&gQ2?J-ZIm!rFTBin;A>U9FZIkW-Ya=pU zR<;(RL8Y(}1fSFFEwf3u2S0ri-uQ)TrZb0}5llu_P;fUI6szXx@DXK4`JL|&2$0lo z$S`U+N|CB}sYbFiM9be}z8Ffc({r**Kv~;$WQTlmGq=si_hqJFvaID=IcuspU%gB7 zrL8xjym8H{fYS;??a_0&njxPoe*7@=k)b{r2_r44G#G!x(fm_4E%G4Z!Ux5m4x}P> zdQE{NY1ni~z%Qn>4uO9K;0^DxWU7{gyQP(HwD;wg-%?8_N@>UMt)7X83%I72u$F82 zyosKq(XnVfp2{#RtF=+xDt3xy6b^f!UX$@$v@-0Qg6diNqXpSGog=r=k1YM{-$DKI zrLJq@;!829RAgKxxFz@&IUkoCT4B|lkMnFQJ zTheRu=I{%A!m=A4Wqlu)7`GaymC`p#Bfe>1S}AXKJHE!53OCq8S{D=xR1Y%18_I&KlQP?Jp5)?Vb_2xR79jw_9| z?(c2K$21v?tmf|K#;v_VVFxt0!uf=XOahzrW_dDh{q_1iMyU8v@Q;IJz9aLicZ4v7 SVbm`Ua<;#0S7qz><9`9f9HPqr diff --git a/pom.xml b/pom.xml index 3b4e465..cc3cfbd 100644 --- a/pom.xml +++ b/pom.xml @@ -1,9 +1,9 @@ 4.0.0 - com.axonivy.cloud.storage + com.axonivy.connector.azure.blob azure-blob-connector azure-blob-connector-modules - 10.0.21-SNAPSHOT + 10.0.22-SNAPSHOT pom @@ -14,7 +14,7 @@ - scm:git:https://github.com/axonivy-professional-services/market-${project.name}.git + scm:git:https://github.com/axonivy-market/${project.name}.git HEAD