From 2ded4be4582aac3372e56e9008957e25eec9c087 Mon Sep 17 00:00:00 2001
From: alaskowski <alaskowski@ethz.ch>
Date: Thu, 9 Nov 2023 17:09:11 +0100
Subject: [PATCH] BIS-716: Added simple data model and validator for
 imaging_data_set

---
 .../imaging/1/as/initialize-master-data.py    |  73 ++++++++
 .../1/as/master-data/imaging-data-model.xls   | Bin 0 -> 23552 bytes
 .../imaging_dataset_config_validation.py      | 163 +++++++++++++++++-
 3 files changed, 231 insertions(+), 5 deletions(-)
 create mode 100644 core-plugin-openbis/dist/core-plugins/imaging/1/as/initialize-master-data.py
 create mode 100644 core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/imaging-data-model.xls

diff --git a/core-plugin-openbis/dist/core-plugins/imaging/1/as/initialize-master-data.py b/core-plugin-openbis/dist/core-plugins/imaging/1/as/initialize-master-data.py
new file mode 100644
index 00000000000..e1ed0ce1fa3
--- /dev/null
+++ b/core-plugin-openbis/dist/core-plugins/imaging/1/as/initialize-master-data.py
@@ -0,0 +1,73 @@
+#
+# Copyright 2014 ETH Zuerich, Scientific IT Services
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# MasterDataRegistrationTransaction Class
+from ch.ethz.sis.openbis.generic.server.asapi.v3 import ApplicationServerApi
+from ch.systemsx.cisd.openbis.generic.server import CommonServiceProvider
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.service.id import CustomASServiceCode
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.service import CustomASServiceExecutionOptions
+from ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl import MasterDataRegistrationHelper
+import sys
+
+helper = MasterDataRegistrationHelper(sys.path)
+api = CommonServiceProvider.getApplicationContext().getBean(ApplicationServerApi.INTERNAL_SERVICE_NAME)
+sessionToken = api.loginAsSystem()
+props = CustomASServiceExecutionOptions().withParameter('xls', helper.listXlsByteArrays()) \
+    .withParameter('method', 'import').withParameter('zip', False).withParameter('xls_name', 'IMAGING').withParameter('update_mode', 'UPDATE_IF_EXISTS') \
+    .withParameter('scripts', helper.getAllScripts())
+result = api.executeCustomASService(sessionToken, CustomASServiceCode("xls-import"), props)
+api.logout(sessionToken)
+print("======================== imaging-master-data xls ingestion result ========================")
+print(result)
+print("======================== imaging-data xls ingestion result ========================")
+
+
+
+# from ch.ethz.sis.openbis.generic.server.asapi.v3 import ApplicationServerApi
+# from ch.systemsx.cisd.openbis.generic.server import CommonServiceProvider
+# from ch.ethz.sis.openbis.generic.asapi.v3.dto.service.id import CustomASServiceCode
+# from ch.ethz.sis.openbis.generic.asapi.v3.dto.service import CustomASServiceExecutionOptions
+# from ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl import MasterDataRegistrationHelper
+# import sys
+#
+# from ch.systemsx.cisd.openbis.generic.server.hotfix import ELNFixes
+# from ch.systemsx.cisd.openbis.generic.server.hotfix import ELNAnnotationsMigration
+# from ch.systemsx.cisd.openbis.generic.server.hotfix import ELNCollectionTypeMigration
+#
+# api = CommonServiceProvider.getApplicationContext().getBean(ApplicationServerApi.INTERNAL_SERVICE_NAME)
+# sessionToken = api.loginAsSystem()
+#
+# if ELNFixes.isELNInstalled():
+#     ELNFixes.beforeUpgrade(sessionToken)
+#     ELNAnnotationsMigration.beforeUpgrade(sessionToken)
+#     ELNCollectionTypeMigration.beforeUpgrade(sessionToken)
+#
+# helper = MasterDataRegistrationHelper(sys.path)
+# props = CustomASServiceExecutionOptions().withParameter('xls', helper.getByteArray("common-data-model.xls"))\
+#     .withParameter('method', 'import').withParameter('zip', False).withParameter('xls_name', 'ELN-LIMS').withParameter('update_mode', 'UPDATE_IF_EXISTS')\
+#     .withParameter('scripts', helper.getAllScripts())
+# result = api.executeCustomASService(sessionToken, CustomASServiceCode("xls-import"), props)
+#
+# if not ELNFixes.isMultiGroup():
+#     props = CustomASServiceExecutionOptions().withParameter('xls', helper.getByteArray("single-group-data-model.xls"))\
+#         .withParameter('method', 'import').withParameter('zip', False).withParameter('xls_name', 'ELN-LIMS').withParameter('update_mode', 'UPDATE_IF_EXISTS')\
+#         .withParameter('scripts', helper.getAllScripts())
+#     result = api.executeCustomASService(sessionToken, CustomASServiceCode("xls-import"), props)
+#
+# ELNCollectionTypeMigration.afterUpgrade()
+# api.logout(sessionToken)
+# print("======================== master-data xls ingestion result ========================")
+# print(result)
+# print("======================== master-data xls ingestion result ========================")
diff --git a/core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/imaging-data-model.xls b/core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/imaging-data-model.xls
new file mode 100644
index 0000000000000000000000000000000000000000..8f88765f4618a3462bf4118c977d56eb9f621c5d
GIT binary patch
literal 23552
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
L{QuSe>-PT#nLi{N

literal 0
HcmV?d00001

diff --git a/core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/scripts/imaging_dataset_config_validation.py b/core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/scripts/imaging_dataset_config_validation.py
index 8f06d3e3744..6766c2ff211 100644
--- a/core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/scripts/imaging_dataset_config_validation.py
+++ b/core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/scripts/imaging_dataset_config_validation.py
@@ -13,17 +13,170 @@
 #   limitations under the License.
 #
 
+import json
+
+
+def assert_control(control_config):
+    if not control_config:
+        return True, ''
+    obligatory_tags = ['@type', 'label', 'type']
+    for tag in obligatory_tags:
+        if tag not in control_config:
+            return False, tag+': is missing!'
+        if control_config[tag] is None:
+            return False, tag+': can not be empty!'
+
+    list_tags = ['values', 'range', 'speeds', 'visibility']
+    for list_tag in list_tags:
+        if list_tag in control_config and control_config[list_tag] is not None and not isinstance(control_config[list_tag], list):
+            return False, list_tag+': must be a list or null!'
+
+    boolean_tags = ['playable', 'multiselect']
+    for boolean_tag in boolean_tags:
+        if boolean_tag in control_config and control_config[boolean_tag] is not None and not isinstance(control_config[boolean_tag], bool):
+            return False, boolean_tag+': must be a boolean or empty!'
+
+    if 'visibility' in control_config and control_config['visibility'] is not None:
+        visibility = control_config['visibility']
+        for vis in visibility:
+            tags = ['label', 'values']
+            all_tags = tags + ['range', 'unit']
+            for tag in ['label', 'values']:
+                if tag not in vis:
+                    return False, 'visibility->'+tag+': is missing!'
+                if vis[tag] is None:
+                    return False, 'visibility->'+tag+': can not be empty!'
+
+            for tag in ['values', 'range']:
+                if tag in vis and vis[tag] is not None and not isinstance(vis[tag], list):
+                    return False, 'visibility->'+tag+': must be a list!'
+
+    if 'metadata' in control_config and control_config['metadata'] is not None and not isinstance(control_config['metadata'], dict):
+        return False, '->metadata: must be a dictionary or null!'
+
+    return True, ''
+
+
+def assert_config(json_config):
+    if 'config' in json_config:
+        config = json_config['config']
+        obligatory_tags = ['@type', 'adaptor', 'version', 'playable', 'exports', 'inputs']
+        for tag in obligatory_tags:
+            if tag not in config:
+                return False, 'config->' + tag + ': is missing!'
+            if config[tag] is None:
+                return False, 'config->' + tag + ': can not be empty!'
+
+        if config['adaptor'].strip() == '':
+            return False, 'config->adaptor: can not be blank!'
+
+        list_tags = ['speeds', 'resolutions', 'exports', 'inputs']
+        for list_tag in list_tags:
+            if list_tag in config and config[list_tag] is not None and not isinstance(config[list_tag], list):
+                return False, '\''+list_tag+'\' must be a list or null!'
+
+        if not isinstance(config['playable'], bool):
+            return False, 'config->playable: must be a boolean!'
+
+        for control in config['exports']:
+            result, err = assert_control(control)
+            if not result:
+                return result, 'config->exports->' + err
+
+        for control in config['inputs']:
+            result, err = assert_control(control)
+            if not result:
+                return result, 'config->inputs->' + err
+
+        if 'metadata' in config and config['metadata'] is not None and not isinstance(config['metadata'], dict):
+            return False, 'config->metadata: must be a dictionary or null!'
+    else:
+        return False, 'Missing \'config\' tag in configuration!'
+    return True, ''
+
+
+def assert_preview(preview_config):
+    obligatory_tags = ['@type', 'format', 'show']
+    for tag in obligatory_tags:
+        if tag not in config:
+            return False, tag + ': is missing!'
+        if config[tag] is None:
+            return False, tag + ': can not be empty!'
+
+    if not isinstance(preview_config['show'], bool):
+        return False, 'show: must be boolean!'
+
+    if not isinstance(preview_config['format'], str):
+        return False, 'format: must be string!'
+
+    if 'bytes' in preview_config and preview_config['bytes'] is not None and not isinstance(preview_config['bytes'], str):
+        return False, 'bytes: must be a base64 encoded string or null!'
+
+    if 'metadata' in preview_config and preview_config['metadata'] is not None and not isinstance(preview_config['metadata'], dict):
+        return False, 'metadata: must be a dictionary or null!'
+
+    if 'config' in preview_config and preview_config['config'] is not None and not isinstance(preview_config['config'], dict):
+        return False, 'config: must be a dictionary or null!'
+
+    return True, ''
+
+
+def assert_images(json_config):
+    if 'images' in json_config:
+        images = json_config['images']
+        if images is None:
+            return False, '\'images\' tag can not be null!'
+        if not isinstance(images, list):
+            return False, '\'images\' tag must be a list!'
+
+        for image in images:
+            obligatory_tags = ['@type']
+            for tag in obligatory_tags:
+                if tag not in image:
+                    return False, 'images->' + tag + ': missing tag!'
+                if image[tag] is None:
+                    return False, 'images->' + tag + ': can not be empty!'
+
+            if 'metadata' in image and image['metadata'] is not None and not isinstance(image['metadata'], dict):
+                return False, 'images->metadata: must be a dictionary or null!'
+
+            if 'previews' in image and image['previews'] is not None and not isinstance(image['previews'], list):
+                return False, 'images->previews: must be a list or null!'
+
+            for preview in image['previews']:
+                res, err = assert_preview(preview)
+                if not res:
+                    return result, 'images->previews->' + err
+
+    else:
+        return False, 'Missing \'images\' tag in configuration!'
+    return True, ''
+
 
 def get_rendered_property(entity, property):
-    value = entity.property(property)
-    if value is not None:
-        return value.renderedValue()
+    properties = entity.externalDataPE().getProperties()
+    for prop in properties:
+        etpt = prop.getEntityTypePropertyType()
+        pt = etpt.getPropertyType()
+        code = pt.getCode()
+        if code == property:
+            return prop.tryGetUntypedValue()
+    return None
 
 
 def validate(entity, is_new):
     imaging_dataset_config = get_rendered_property(entity, "$IMAGING_DATA_CONFIG")
     if imaging_dataset_config is None or imaging_dataset_config == "":
         return "Imaging dataset config can not be empty!"
+    elif "test_validation_failure" in imaging_dataset_config:
+        return "Imaging dataset config validation failure!"
     else:
-        # TODO add deserialization and validation of particular fields
-        pass
+
+        try:
+            config = json.loads(imaging_dataset_config)
+        except Exception as e:
+            return "Could not parse JSON: " + e
+
+        result, err = assert_config(config)
+        if not result:
+            return err
-- 
GitLab