diff --git a/.gitignore b/.gitignore index 5749b82ae..303aba5c4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ target/ .idea/ *.iml +#VSCode +.vscode/ + # Mac OS .DS_Store diff --git a/gxexternalproviders/pom.xml b/gxexternalproviders/pom.xml index f1a5a41b8..ba677d5e5 100644 --- a/gxexternalproviders/pom.xml +++ b/gxexternalproviders/pom.xml @@ -1,62 +1,67 @@ - - - 4.0.0 - - - com.genexus - parent - 1.2-SNAPSHOT - - - gxexternalproviders - GeneXus External Providers - - - - ${project.groupId} - gxclassR - ${project.version} - - - com.microsoft.azure - azure-storage - 4.2.0 - - - com.google.api-client - google-api-client - 1.22.0 - - - org.pacesys - openstack4j - 3.0.3 - - - org.pacesys - openstack4j-core - 3.0.0 - - - com.amazonaws - aws-java-sdk-s3 - 1.11.570 - - - com.google.apis - google-api-services-storage - v1-rev82-1.22.0 - - - com.google.cloud - google-cloud-storage - 1.0.0 - - - - - gxexternalproviders - - + + + 4.0.0 + + + com.genexus + parent + 1.2-SNAPSHOT + + + gxexternalproviders + GeneXus External Providers + + + + ${project.groupId} + gxclassR + ${project.version} + + + com.microsoft.azure + azure-storage + 4.2.0 + + + com.google.api-client + google-api-client + 1.22.0 + + + org.pacesys + openstack4j + 3.0.3 + + + org.pacesys + openstack4j-core + 3.0.0 + + + com.amazonaws + aws-java-sdk-s3 + 1.11.570 + + + com.google.apis + google-api-services-storage + v1-rev82-1.22.0 + + + com.google.cloud + google-cloud-storage + 1.0.0 + + + com.box + box-java-sdk + 2.32.0 + + + + + gxexternalproviders + + diff --git a/gxexternalproviders/src/main/java/com/genexus/db/driver/ExternalProviderBox.java b/gxexternalproviders/src/main/java/com/genexus/db/driver/ExternalProviderBox.java new file mode 100644 index 000000000..72e5ebab1 --- /dev/null +++ b/gxexternalproviders/src/main/java/com/genexus/db/driver/ExternalProviderBox.java @@ -0,0 +1,458 @@ +package com.genexus.db.driver; + +import com.box.sdk.BoxAPIConnection; +import com.box.sdk.BoxAPIRequest; +import com.box.sdk.BoxAPIResponse; +import com.box.sdk.BoxAPIResponseException; +import com.box.sdk.BoxConfig; +import com.box.sdk.BoxDeveloperEditionAPIConnection; +import com.box.sdk.BoxFile; +import com.box.sdk.BoxFolder; +import com.box.sdk.BoxItem; +import com.box.sdk.BoxResource; +import com.box.sdk.BoxSearch; +import com.box.sdk.BoxSearchParameters; +import com.box.sdk.BoxSharedLink; +import com.box.sdk.BoxSharedLink.Access; +import com.box.sdk.BoxSharedLink.Permissions; +import com.genexus.Application; +import com.genexus.StructSdtMessages_Message; +import com.genexus.util.GXService; +import com.genexus.util.GXServices; +import com.genexus.util.StorageUtils; + +import org.apache.logging.log4j.LogManager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.net.URL; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ExternalProviderBox implements ExternalProvider { + private static final String CONFIG_FILE = "CONFIG_FILE"; + + private BoxAPIConnection api; + + private static org.apache.logging.log4j.Logger logger = LogManager.getLogger(ExternalProviderBox.class); + + public ExternalProviderBox() { + GXServices services = Application.getGXServices(); + GXService providerService = services.get(GXServices.STORAGE_SERVICE); + + String configFile = providerService.getProperties().get(CONFIG_FILE); + File file = new File(services.configBaseDirectory(), configFile); + debugTrace("configFile = \"%s\"", file.getAbsolutePath()); + + BoxConfig config; + try (Reader reader = new FileReader(file)) { + config = BoxConfig.readFrom(reader); + } catch (IOException ex) { + throw handleException("read config", ex); + } + + api = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(config); + } + + @Override + public void download(String externalFileName, String localFile, boolean isPrivate) { + debugTrace("download(\"%s\", \"%s\", %s)", externalFileName, localFile, isPrivate); + + try { + BoxFile.Info file = getBoxFileFromPath(externalFileName, isPrivate); + try (OutputStream out = new FileOutputStream(localFile)) { + file.getResource().download(out); + } + } catch (IOException ex) { + throw handleException("download", ex); + } + } + + @Override + public String upload(String localFile, String externalFileName, boolean isPrivate) { + try { + BoxFolder folder = getOrCreateBoxFolderFromPath(externalFileName, true); + String fileName = getFileNameFromPath(externalFileName); + + BoxFile.Info fileInfo = null; + File file = new File(localFile); + try (FileInputStream fileStream = new FileInputStream(file)) { + fileInfo = folder.uploadFile(fileStream, fileName, file.length(), null); + } + String link = getBoxFileLink(fileInfo.getResource(), isPrivate); + debugTrace("upload(\"%s\", \"%s\", %s) = %s", localFile, externalFileName, isPrivate, link); + return link; + } catch (IOException ex) { + throw handleException("upload", ex); + } + } + + @Override + public String upload(String externalFileName, InputStream input, boolean isPrivate) { + try { + BoxFolder folder = getOrCreateBoxFolderFromPath(externalFileName, true); + String fileName = getFileNameFromPath(externalFileName); + + BoxFile.Info fileInfo = folder.uploadFile(input, fileName); + String link = getBoxFileLink(fileInfo.getResource(), isPrivate); + debugTrace("upload_Input(\"%s\", %s) = %s", externalFileName, isPrivate, link); + return link; + } catch (Exception ex) { + throw handleException("upload_input", ex); + } + } + + private String getBoxFileLink(BoxFile file, boolean isPrivate) { + return getBoxFileLink(file, isPrivate, null); + } + + private String getBoxFileLink(BoxFile file, boolean isPrivate, Date expiration) { + boolean visible = !isPrivate; + Permissions permissions = new Permissions(); + permissions.setCanPreview(visible); + permissions.setCanDownload(visible); + BoxSharedLink link = file.createSharedLink(visible ? Access.OPEN : Access.COMPANY, expiration, permissions); + return visible ? link.getDownloadURL() : null; + } + + private String getFileNameFromPath(String path) { + String[] names = path.split(StorageUtils.DELIMITER); + return names != null ? names[names.length - 1] : null; + } + + private BoxFolder getOrCreateBoxFolderFromPath(String path, boolean isFilePath) { + return getBoxFolderFromPath(path, isFilePath, true); + } + + private BoxFolder getBoxFolderFromPath(String path, boolean isFilePath, boolean createIfMissing) { + BoxFolder root = BoxFolder.getRootFolder(api); + BoxFolder.Info folder = null; + String folderID = root.getID(); + + String lastToken = null; + + for (String token : path.split(StorageUtils.DELIMITER)) { + String dirName = lastToken; + if (dirName != null) { + folder = getBoxFolder(folderID, dirName, createIfMissing); + if (folder == null) + return null; + else + folderID = folder.getID(); + } + lastToken = token; + } + if (!isFilePath && lastToken != null) { + folder = getBoxFolder(folderID, lastToken, createIfMissing); + if (folder == null) + return null; + else + folderID = folder.getID(); + } + return folder.getResource(); + } + + private BoxFolder getBoxFolderFromPath(String path, boolean isFilePath) { + return getBoxFolderFromPath(path, isFilePath, false); + } + + private BoxFolder.Info getOrCreateBoxFolder(String parentID, String folderName) { + return getBoxFolder(parentID, folderName, true); + } + + private BoxFolder.Info getBoxFolder(String parentID, String folderName, boolean createIfMissing) { + BoxFolder.Info childFolder = getBoxFolder(parentID, folderName); + if (childFolder == null && createIfMissing) { + childFolder = new BoxFolder(api, parentID).createFolder(folderName); + } + return childFolder; + } + + private BoxFolder.Info getBoxFolder(String parentID, String folderName) { + BoxSearch find = new BoxSearch(api); + BoxSearchParameters query = new BoxSearchParameters(); + query.setQuery(folderName); + query.setType(BoxResource.getResourceType(BoxFolder.class)); + query.setAncestorFolderIds(Arrays.asList(parentID)); + + for (BoxItem.Info info : find.searchRange(0, 200, query)) { + String foundItemParentID = info.getParent().getID(); + String foundItemName = info.getName(); + + if (parentID.equals(foundItemParentID) && folderName.equals(foundItemName)) { + if (BoxFolder.Info.class.isInstance(info)) { + return BoxFolder.Info.class.cast(info); + } + } + } + + return null; + } + + private BoxFile.Info getBoxFile(String parentID, String fileName, String... fields) { + BoxFolder folder = new BoxFolder(api, parentID); + String fileID = null; + try { + folder.canUpload(fileName, 1024); + } catch (BoxAPIResponseException bex) { + if (bex.getResponseCode() == 409) { + fileID = getIdFromConflict(bex.getResponse()); + } + } + if (fileID != null) { + BoxFile file = new BoxFile(api, fileID); + if (fields != null && fields.length > 0) + return file.getInfo(fields); + else + return file.getInfo(); + } else { + return null; + } + } + + private String getIdFromConflict(String message) { + String id = ""; + Pattern p = Pattern.compile("\"id\":\"[0-9]+\""); + Matcher m = p.matcher(message); + if (m.find()) { + String sub = m.group(); + id = sub.substring("\"id\":".length() + 1, sub.length() - 1); + } + return id; + } + + private BoxFile.Info getBoxFileFromPath(String filePath, boolean isPrivate, String... fields) { + BoxFolder folder = getBoxFolderFromPath(filePath, true); + String fileName = getFileNameFromPath(filePath); + return folder != null && fileName != null ? getBoxFile(folder.getID(), fileName, fields) : null; + } + + @Override + public String get(String externalFileName, boolean isPrivate, int expirationMinutes) { + Date expiration = Date.from(Instant.now().plusSeconds(expirationMinutes * 60)); + BoxFile.Info fileInfo = getBoxFileFromPath(externalFileName, isPrivate); + String link = getBoxFileLink(fileInfo.getResource(), isPrivate, expiration); + debugTrace("get(\"%s\", %s, %d) = %s", externalFileName, isPrivate, expirationMinutes, link); + return link; + } + + @Override + public void delete(String objectName, boolean isPrivate) { + try { + debugTrace("delete(\"%s\", %s)", objectName, isPrivate); + BoxFile.Info file = getBoxFileFromPath(objectName, isPrivate); + + file.getResource().delete(); + } catch (Exception ex) { + throw handleException("delete", ex); + } + } + + @Override + public String rename(String objectName, String newName, boolean isPrivate) { + try { + debugTrace("rename(\"%s\", \"%s\", %s)", objectName, newName, isPrivate); + BoxFile.Info fileInfo = getBoxFileFromPath(objectName, isPrivate); + if (fileInfo == null) + return null; + + BoxFile file = fileInfo.getResource(); + file.rename(newName); + + String newUrl = getBoxFileLink(file, isPrivate); + debugTrace("rename(\"%s\", \"%s\", %s) = \"%s\"", objectName, newName, isPrivate, newUrl); + return newUrl; + } catch (Exception ex) { + throw handleException("rename", ex); + } + } + + @Override + public String copy(String objectName, String newName, boolean isPrivate) { + try { + BoxFile.Info file = getBoxFileFromPath(objectName, isPrivate); + + String newUrl = internalCopy(file.getID(), file.getParent().getID(), newName, isPrivate); + debugTrace("copy(\"%s\", \"%s\", %s) = \"%s\"", objectName, newName, isPrivate, newUrl); + return newUrl; + } catch (Exception ex) { + throw handleException("copy", ex); + } + } + + @Override + public String copy(String objectUrl, String newName, String tableName, String fieldName, boolean isPrivate) { + try { + BoxFolder root = BoxFolder.getRootFolder(api); + BoxFolder.Info table = getOrCreateBoxFolder(root.getID(), tableName); + BoxFolder.Info field = getOrCreateBoxFolder(table.getID(), fieldName); + BoxFile.Info file = getBoxFileFromPath(objectUrl, isPrivate); + + String newUrl = internalCopy(file.getID(), field.getID(), newName, isPrivate); + debugTrace("copy(\"%s\", \"%s\", \"%s\", \"%s\", %s) = \"%s\"", objectUrl, newName, tableName, fieldName, + isPrivate, newUrl); + return newUrl; + } catch (Exception ex) { + throw handleException("copy", ex); + } + } + + private String internalCopy(String fileID, String destinationFolderID, String newName, boolean isPrivate) { + BoxFile file = new BoxFile(api, fileID); + BoxFolder destination = new BoxFolder(api, destinationFolderID); + BoxFile.Info copiedFile = file.copy(destination, newName); + debugTrace("copy(source: %s) = %s", fileID, copiedFile.getID()); + return getBoxFileLink(copiedFile.getResource(), isPrivate); + } + + @Override + public long getLength(String objectName, boolean isPrivate) { + try { + BoxFile.Info fileInfo = getBoxFileFromPath(objectName, isPrivate, "size"); + long size = fileInfo.getSize(); + debugTrace("getLength(name: \"%s\", isPrivate: %s) = %s", objectName, isPrivate, size); + return size; + } catch (Exception ex) { + throw handleException("getLength", ex); + } + } + + @Override + public Date getLastModified(String objectName, boolean isPrivate) { + try { + BoxFile.Info fileInfo = getBoxFileFromPath(objectName, isPrivate, "modified_at"); + Date modifiedAt = fileInfo.getModifiedAt(); + debugTrace("getLastModified(name: \"%s\", isPrivate: %s) = %s", objectName, isPrivate, modifiedAt); + return modifiedAt; + } catch (Exception ex) { + throw handleException("getLastModified", ex); + } + } + + @Override + public boolean exists(String objectName, boolean isPrivate) { + try { + boolean exists = getBoxFileFromPath(objectName, isPrivate) != null; + debugTrace("exists(\"%s\", %s) = %s", objectName, isPrivate, exists); + return exists; + } catch (Exception ex) { + throw handleException("exists", ex); + } + } + + @Override + public String getDirectory(String directoryName) { + // What should return? + return existsDirectory(directoryName) ? directoryName : null; + } + + @Override + public boolean existsDirectory(String directoryName) { + boolean exists = getBoxFolderFromPath(directoryName, false) != null; + debugTrace("existsDirectory(\"%s\") = %s", directoryName, exists); + return exists; + } + + @Override + public void createDirectory(String directoryName) { + debugTrace("createDirectory(\"%s\")", directoryName); + getOrCreateBoxFolderFromPath(directoryName, false); + } + + @Override + public void deleteDirectory(String directoryName) { + debugTrace("deleteDirectory(\"%s\")", directoryName); + BoxFolder folder = getBoxFolderFromPath(directoryName, false); + if (folder != null) + folder.delete(true); + } + + @Override + public void renameDirectory(String directoryName, String newDirectoryName) { + debugTrace("renameDirectory(\"%s\", \"%s\")", directoryName, newDirectoryName); + BoxFolder folder = getBoxFolderFromPath(directoryName, false); + if (folder != null) + folder.rename(newDirectoryName); + } + + @Override + public List getFiles(String directoryName, String filter) { + debugTrace("getFiles(\"%s\", \"%s\")", directoryName, filter); + List files = new ArrayList<>(); + try { + BoxFolder folder = getBoxFolderFromPath(directoryName, false); + for (BoxItem.Info info : folder.getChildren("name")) { + if (info instanceof BoxFile.Info) + files.add(info.getName()); + } + return files; + } catch (Exception ex) { + throw handleException("getFiles", ex); + } + } + + @Override + public List getFiles(String directoryName) { + return getFiles(directoryName, ""); + } + + @Override + public List getSubDirectories(String directoryName) { + debugTrace("getSubDirectories(\"%s\")", directoryName); + List directories = new ArrayList<>(); + try { + BoxFolder folder = getBoxFolderFromPath(directoryName, false); + for (BoxItem.Info info : folder.getChildren("name")) { + if (info instanceof BoxFolder.Info) + directories.add(info.getName()); + } + return directories; + } catch (Exception ex) { + throw handleException("getDirectories", ex); + } + } + + @Override + public InputStream getStream(String objectName, boolean isPrivate) { + debugTrace("getStream(\"%s\", %s)", objectName, isPrivate); + + try { + BoxFile.Info fileInfo = getBoxFileFromPath(objectName, isPrivate); + if (fileInfo == null) + return null; + + /// Copied from BoxFile.download() + URL url = BoxFile.CONTENT_URL_TEMPLATE.build(api.getBaseURL(), fileInfo.getID()); + BoxAPIRequest request = new BoxAPIRequest(api, url, "GET"); + BoxAPIResponse response = request.send(); + return response.getBody(); + } catch (Exception ex) { + throw handleException("getStream", ex); + } + } + + @Override + public boolean getMessageFromException(Exception ex, StructSdtMessages_Message msg) { + return false; + } + + private RuntimeException handleException(String action, Exception ex) { + logger.error(String.format("Failed to %s", action), ex); + return new RuntimeException(ex); + } + + private void debugTrace(String message, Object... args) { + // logger.debug(String.format(message, args)); + } +} diff --git a/java/src/main/java/com/genexus/util/GXServices.java b/java/src/main/java/com/genexus/util/GXServices.java index 713d1d565..2506a6da9 100644 --- a/java/src/main/java/com/genexus/util/GXServices.java +++ b/java/src/main/java/com/genexus/util/GXServices.java @@ -70,7 +70,7 @@ public static void loadFromFile(String basePath, String fileName, GXServices ser } } - private String configBaseDirectory() { + public String configBaseDirectory() { String baseDir = ""; String envVariable = System.getenv("LAMBDA_TASK_ROOT"); if (envVariable != null && envVariable.length() > 0)