From 6d0f3b516d45bc2d891e3e4bef4cb2a559d84e91 Mon Sep 17 00:00:00 2001
From: juanf <juanf@ethz.ch>
Date: Wed, 8 Mar 2023 11:27:15 +0100
Subject: [PATCH] SSDM-13422: Zip support for master data imports

---
 .../v1/impl/MasterDataRegistrationHelper.java | 54 ++++++++++++++++++-
 .../1/as/services/xls-import/xls-import.py    | 46 +++++++++++-----
 2 files changed, 85 insertions(+), 15 deletions(-)

diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/jython/api/v1/impl/MasterDataRegistrationHelper.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/jython/api/v1/impl/MasterDataRegistrationHelper.java
index aa023a84384..29278ae699e 100644
--- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/jython/api/v1/impl/MasterDataRegistrationHelper.java
+++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/jython/api/v1/impl/MasterDataRegistrationHelper.java
@@ -17,8 +17,13 @@ package ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 
 import org.apache.log4j.Logger;
 
@@ -100,7 +105,7 @@ public class MasterDataRegistrationHelper {
         return result;
     }
 
-    private void gatherScripts(Map<String, String> scripts, File rootFolder, File file) {
+    private static void gatherScripts(Map<String, String> scripts, File rootFolder, File file) {
         if (file.isFile()) {
             String scriptPath = FileUtilities.getRelativeFilePath(rootFolder, file);
             scripts.put(scriptPath, FileUtilities.loadToString(file));
@@ -114,4 +119,51 @@ public class MasterDataRegistrationHelper {
         }
     }
 
+    public static Map<String, String> getAllScripts(Path path) {
+        Map<String, String> result = new TreeMap<>();
+        File scriptsFolder = new File(path.toFile(), "scripts");
+        if (scriptsFolder.isDirectory()) {
+            gatherScripts(result, scriptsFolder, scriptsFolder);
+        }
+        return result;
+    }
+
+    public static List<byte[]> getByteArrays(Path path, String findName) {
+        List<byte[]> byteArrays = new ArrayList<>();
+        for (File file : path.toFile().listFiles()) {
+            String name = file.getName();
+            if (name.contains(findName)) {
+                operationLog.info("load master data " + file.getName());
+                byteArrays.add(FileUtilities.loadToByteArray(file));
+            }
+        }
+        return byteArrays;
+    }
+
+    public static void extractToDestination(byte[] zip, String tempPathAsString) throws IOException
+    {
+        // Write temp file
+        Path tempZipPath = Paths.get(tempPathAsString, "temp.zip");
+        Files.write(tempZipPath, zip);
+
+        try (ZipFile zipFile = new ZipFile(tempZipPath.toFile())) {
+            Enumeration<? extends ZipEntry> entries = zipFile.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = entries.nextElement();
+                File entryDestination = new File(tempPathAsString,  entry.getName());
+                if (entry.isDirectory()) {
+                    entryDestination.mkdirs();
+                } else {
+                    entryDestination.getParentFile().mkdirs();
+                    try (InputStream in = zipFile.getInputStream(entry)) {
+                        Files.copy(in, entryDestination.toPath());
+                    }
+                }
+            }
+        }
+
+        // Delete temp file leaving on the folder only the uncompressed content
+        Files.delete(tempZipPath);
+    }
+
 }
diff --git a/ui-admin/src/core-plugins/admin/1/as/services/xls-import/xls-import.py b/ui-admin/src/core-plugins/admin/1/as/services/xls-import/xls-import.py
index 17d8c6267fd..ef0e4dbedeb 100644
--- a/ui-admin/src/core-plugins/admin/1/as/services/xls-import/xls-import.py
+++ b/ui-admin/src/core-plugins/admin/1/as/services/xls-import/xls-import.py
@@ -1,10 +1,15 @@
 import base64
+from ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl import MasterDataRegistrationHelper
 from ch.ethz.sis.openbis.generic.server.xls.importer import ImportOptions
 from ch.ethz.sis.openbis.generic.server.xls.importer import XLSImport
 from ch.ethz.sis.openbis.generic.server.xls.importer.enums import ImportModes
 from ch.systemsx.cisd.common.exceptions import UserFailureException
 from java.util import ArrayList
-
+from org.apache.commons.io import FileUtils
+from java.io import File
+from java.lang import Long
+from java.lang import System
+from java.nio.file import Path
 
 def get_update_mode(parameters):
     update_mode = parameters.get('update_mode', 'FAIL_IF_EXISTS')
@@ -41,8 +46,29 @@ def process(context, parameters):
     result = None
 
     if method == "import":
+        zip = parameters.get('zip', False)
+        temp = None
+
+
+        if zip: # Zip mode uses xls_base64 for all multiple XLS + script files
+            zip_bytes = base64.b64decode(parameters.get('xls_base64'))
+            temp = File.createTempFile("temp", Long.toString(System.nanoTime()))
+            temp.delete()
+            temp.mkdir()
+            tempPath = temp.getAbsolutePath()
+            MasterDataRegistrationHelper.extractToDestination(zip_bytes, tempPath)
+            byteArrays = MasterDataRegistrationHelper.getByteArrays(Path.of(tempPath), ".xls")
+            parameters.put('xls', byteArrays)
+            allScripts = MasterDataRegistrationHelper.getAllScripts(Path.of(tempPath))
+            parameters.put('scripts', allScripts)
+        else:
+            # Check if xls_base64 is used for a single XLS
+            xls_base64_string = parameters.get('xls_base64', None)
+            if xls_base64_string is not None:
+                parameters.set('xls', [ base64.b64decode(xls_base64_string) ])
         result = _import(context, parameters)
-
+        if temp is not None:
+            FileUtils.deleteDirectory(temp)
     return result
 
 
@@ -73,25 +99,17 @@ def _import(context, parameters):
             }
         :return: Openbis's execute operations result string. It should contain report on what was created.
     """
-    api, session_token = context.applicationService, context.sessionToken
-
-    xls_byte_arrays = parameters.get('xls', None)
-    xls_base64_string = parameters.get('xls_base64', None)
-    xls_name = parameters.get('xls_name', None)
-    zip = parameters.get('zip', False)
+    session_token = context.sessionToken
+    api = context.applicationService
     scripts = parameters.get('scripts', {})
     mode = get_update_mode(parameters)
     options = get_import_options(parameters)
-
-    if zip:
-        raise UserFailureException('Zip imports not yet supported');
-
-    if xls_byte_arrays is None and xls_base64_string is not None:
-        xls_byte_arrays = [ base64.b64decode(xls_base64_string) ]
+    xls_name = parameters.get('xls_name', None)
 
     importXls = XLSImport(session_token, api, scripts, mode, options, xls_name)
 
     ids = ArrayList()
+    xls_byte_arrays = parameters.get('xls', None)
     for xls_byte_array in xls_byte_arrays:
         ids.addAll(importXls.importXLS(xls_byte_array))
 
-- 
GitLab