From ffcd036da49f43e36b1cb308d9df3cdf0a887a3f Mon Sep 17 00:00:00 2001 From: Juan Fuentes <juanf@ethz.ch> Date: Tue, 28 May 2024 16:38:32 +0200 Subject: [PATCH] BIS-1059: Large file imports support --- .../ch/ethz/sis/openbis/generic/OpenBIS.java | 47 +- .../asapi/v3/IApplicationServerApi.java | 5 +- .../v3/dto/importer/ImportOperation.java | 10 +- .../dto/importer/ImportOperationResult.java | 26 ++ .../ImportScript.java => ImportResult.java} | 42 +- .../v3/dto/importer/data/IImportData.java | 26 -- .../{ZipImportData.java => ImportData.java} | 22 +- .../v3/dto/importer/data/ImportFormat.java | 2 +- .../v3/dto/importer/data/ImportValue.java | 91 ---- .../importer/data/UncompressedImportData.java | 113 ----- .../generic/sharedapi/v3/dictionary.txt | 6 +- .../src/v3/as/dto/importer/ImportOperation.js | 2 +- .../as/dto/importer/ImportOperationResult.js | 16 +- .../{data/ImportScript.js => ImportResult.js} | 38 +- .../v3/as/dto/importer/data/IImportData.js | 23 - .../data/{ZipImportData.js => ImportData.js} | 29 +- .../v3/as/dto/importer/data/ImportFormat.js | 2 +- .../v3/as/dto/importer/data/ImportValue.js | 54 --- .../importer/data/UncompressedImportData.js | 83 ---- api-openbis-javascript/src/v3/openbis.js | 28 ++ .../dto/OpenBISJavaScriptFacade.java | 12 +- .../.dynamic_property_evaluator_queue | Bin 12 -> 0 bytes .../excelimport/AbstractImportTest.java | 37 ++ .../excelimport/ImportDatasetTypesTest.java | 32 +- .../ImportExperimentTypesTest.java | 27 +- .../excelimport/ImportExperimentsTest.java | 224 +++++++--- .../excelimport/ImportFromExcelTest.java | 10 +- .../excelimport/ImportProjectsTest.java | 43 +- .../excelimport/ImportPropertyTypesTest.java | 83 ++-- .../excelimport/ImportSampleTypesTest.java | 71 ++- .../plugin/excelimport/ImportSamplesTest.java | 206 ++++++--- .../plugin/excelimport/ImportSpacesTest.java | 18 +- .../ImportVocabularyTypesTest.java | 61 ++- .../plugin/excelimport/TestUtils.java | 133 +----- .../test_files/experiments/update.xls | Bin 32768 -> 7168 bytes .../property_types/normal_property_type.xls | Bin 40960 -> 15360 bytes .../sample_types/with_dynamic_script.xls | Bin 40960 -> 14848 bytes .../space_project_experiment_elsewhere.xls | Bin 46080 -> 19968 bytes .../server/asapi/v3/ApplicationServerApi.java | 9 +- .../asapi/v3/ApplicationServerApiLogger.java | 9 +- ...iPersonalAccessTokenInvocationHandler.java | 7 +- .../v3/executor/importer/IImportExecutor.java | 3 +- .../v3/executor/importer/ImportExecutor.java | 150 +------ .../importer/ImportOperationExecutor.java | 5 +- .../helper/AbstractXLSExportHelper.java | 3 +- .../xls/importer/MainImportXlsTest.java | 89 ---- .../server/xls/importer/XLSImport.java | 403 +++++++++++++----- .../delay/DelayedExecutionDecorator.java | 21 +- .../importer/helper/BasicImportHelper.java | 5 +- .../helper/DatasetTypeImportHelper.java | 15 +- .../helper/ExperimentImportHelper.java | 5 +- .../helper/ExperimentTypeImportHelper.java | 15 +- .../PropertyAssignmentImportHelper.java | 23 +- .../helper/PropertyTypeImportHelper.java | 27 +- .../importer/helper/SampleImportHelper.java | 4 +- .../helper/SampleTypeImportHelper.java | 21 +- .../importer/helper/ScriptImportHelper.java | 57 +-- .../helper/VocabularyImportHelper.java | 12 +- .../helper/VocabularyTermImportHelper.java | 12 +- .../xls/importer/utils/FileServerUtils.java | 26 +- .../web/server/UploadServiceServlet.java | 2 +- .../shared/ISessionWorkspaceProvider.java | 4 + .../shared/SessionWorkspaceProvider.java | 16 + .../asapi/v3/AbstractImportTest.java | 33 +- .../systemtest/asapi/v3/AbstractTest.java | 5 + .../asapi/v3/UncompressedImportTest.java | 74 ++-- .../systemtest/asapi/v3/ZipImportTest.java | 70 +-- .../v3/test_files/import/scripts/dynamic.py | 2 +- .../v3/test_files/import/scripts/valid.py | 1 + .../openbis-v3-api-test/html/test/common.js | 20 + .../openbis-v3-api-test/html/test/dtos.js | 7 +- .../html/test/openbis-execute-operations.js | 2 +- .../html/test/test-import-export.ts | 90 +--- .../html/test/types/common.d.ts | 1 + .../html/test/types/openbis.esm.d.ts | 171 +++----- .../IntegrationSessionWorkspaceTest.java | 90 ++++ .../1/as/services/xls-import/xls-import.py | 110 +---- .../form/import/all/ImportAllFormFacade.js | 34 +- ui-admin/src/js/services/openbis/api.js | 4 + .../1/as/services/as-eln-lims-api/script.py | 43 -- .../eln-lims/html/js/server/ServerFacade.js | 35 +- .../SampleTable/SampleTableController.js | 22 +- 82 files changed, 1664 insertions(+), 1715 deletions(-) rename api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/{data/ImportScript.java => ImportResult.java} (59%) delete mode 100644 api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/IImportData.java rename api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/{ZipImportData.java => ImportData.java} (74%) delete mode 100644 api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportValue.java delete mode 100644 api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/UncompressedImportData.java rename api-openbis-javascript/src/v3/as/dto/importer/{data/ImportScript.js => ImportResult.js} (58%) delete mode 100644 api-openbis-javascript/src/v3/as/dto/importer/data/IImportData.js rename api-openbis-javascript/src/v3/as/dto/importer/data/{ZipImportData.js => ImportData.js} (61%) delete mode 100644 api-openbis-javascript/src/v3/as/dto/importer/data/ImportValue.js delete mode 100644 api-openbis-javascript/src/v3/as/dto/importer/data/UncompressedImportData.js delete mode 100644 core-plugin-openbis/.dynamic_property_evaluator_queue delete mode 100644 server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/MainImportXlsTest.java create mode 100644 server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/test_files/import/scripts/valid.py create mode 100644 test-integration/sourceTest/java/ch/ethz/sis/openbis/systemtests/IntegrationSessionWorkspaceTest.java diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/OpenBIS.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/OpenBIS.java index ede5ae601e1..76ba0dae624 100644 --- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/OpenBIS.java +++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/OpenBIS.java @@ -34,9 +34,13 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; +import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.MultiPartContentProvider; +import org.eclipse.jetty.client.util.PathContentProvider; +import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.http.HttpMethod; import ch.ethz.sis.afsapi.api.OperationsAPI; @@ -116,7 +120,7 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.update.ExternalDmsUp import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.GlobalSearchObject; import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.fetchoptions.GlobalSearchObjectFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.search.GlobalSearchCriteria; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.IImportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportData; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.Material; import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.MaterialType; @@ -1241,7 +1245,7 @@ public class OpenBIS return asFacadeWithTransactions.createCodes(sessionToken, prefix, entityKind, count); } - public void executeImport(IImportData importData, ImportOptions importOptions) { + public void executeImport(ImportData importData, ImportOptions importOptions) { asFacadeWithTransactions.executeImport(sessionToken, importData, importOptions); } @@ -1249,6 +1253,45 @@ public class OpenBIS return asFacadeWithTransactions.executeExport(sessionToken, exportData, exportOptions); } + public String uploadToSessionWorkspace(final Path fileOrFolder) + { + if (transactionId != null) + { + throw new IllegalStateException("AS session workspace SHOULD NOT be used during transactions."); + } + + String uploadId = UUID.randomUUID().toString() + "/" + fileOrFolder.getFileName().toString(); + + try + { + HttpClient httpClient = JettyHttpClientFactory.getHttpClient(); + + MultiPartContentProvider multiPart = new MultiPartContentProvider(); + multiPart.addFieldPart("sessionKeysNumber", new StringContentProvider("1"), null); + multiPart.addFieldPart("sessionKey_0", new StringContentProvider("openbis-file-upload"), null); + multiPart.addFilePart("openbis-file-upload", uploadId, new PathContentProvider(fileOrFolder), null); + multiPart.addFieldPart("keepOriginalFileName", new StringContentProvider("True"), null); + multiPart.addFieldPart("sessionID", new StringContentProvider(this.sessionToken), null); + multiPart.close(); + + ContentResponse response = httpClient.newRequest(this.asURL + "/upload") + .method(HttpMethod.POST) + .content(multiPart) + .send(); + + final int status = response.getStatus(); + if (status != 200) + { + throw new IOException(response.getContentAsString()); + } + } catch (final IOException | TimeoutException | InterruptedException | ExecutionException e) + { + throw new RuntimeException(e); + } + + return uploadId; + } + // // DSS Facade methods // diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java index bd1ec54d8ce..c58a8be081a 100644 --- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java +++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/IApplicationServerApi.java @@ -89,7 +89,8 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.update.ExternalDmsUp import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.GlobalSearchObject; import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.fetchoptions.GlobalSearchObjectFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.search.GlobalSearchCriteria; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.IImportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportData; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.Material; import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.MaterialType; @@ -2262,7 +2263,7 @@ public interface IApplicationServerApi extends IRpcService */ public List<String> createCodes(String sessionToken, String prefix, EntityKind entityKind, int count); - public void executeImport(String sessionToken, IImportData importData, ImportOptions importOptions); + public ImportResult executeImport(String sessionToken, ImportData importData, ImportOptions importOptions); public ExportResult executeExport(String sessionToken, ExportData exportData, ExportOptions exportOptions); diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/ImportOperation.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/ImportOperation.java index 509ee42d978..479452c9b98 100644 --- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/ImportOperation.java +++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/ImportOperation.java @@ -24,7 +24,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.ObjectToString; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperation; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.IImportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportData; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportOptions; import ch.systemsx.cisd.base.annotation.JsonObject; @@ -35,7 +35,7 @@ public class ImportOperation implements Serializable, IOperation private static final long serialVersionUID = 1L; @JsonProperty - private IImportData importData; + private ImportData importData; @JsonProperty private ImportOptions importOptions; @@ -45,7 +45,7 @@ public class ImportOperation implements Serializable, IOperation { } - public ImportOperation(final IImportData importData, final ImportOptions importOptions) + public ImportOperation(final ImportData importData, final ImportOptions importOptions) { this.importData = importData; this.importOptions = importOptions; @@ -58,13 +58,13 @@ public class ImportOperation implements Serializable, IOperation } @JsonIgnore - public IImportData getImportData() + public ImportData getImportData() { return importData; } @JsonIgnore - public void setImportData(final IImportData importData) + public void setImportData(final ImportData importData) { this.importData = importData; } diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/ImportOperationResult.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/ImportOperationResult.java index a28b89ee5f3..54e9619942e 100644 --- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/ImportOperationResult.java +++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/ImportOperationResult.java @@ -19,6 +19,9 @@ package ch.ethz.sis.openbis.generic.asapi.v3.dto.importer; import java.io.Serializable; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperationResult; import ch.systemsx.cisd.base.annotation.JsonObject; @@ -28,12 +31,35 @@ public class ImportOperationResult implements Serializable, IOperationResult private static final long serialVersionUID = 1L; + @JsonProperty + private ImportResult importResult; + + public ImportOperationResult() + { + } + + public ImportOperationResult(final ImportResult importResult) + { + this.importResult = importResult; + } + @Override public String getMessage() { return toString(); } + @JsonIgnore + public ImportResult getImportResult() + { + return importResult; + } + + public void setImportResult(final ImportResult importResult) + { + this.importResult = importResult; + } + @Override public String toString() { diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportScript.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/ImportResult.java similarity index 59% rename from api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportScript.java rename to api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/ImportResult.java index 4307d2ff86c..29ca23329d8 100644 --- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportScript.java +++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/ImportResult.java @@ -14,67 +14,53 @@ * limitations under the License. */ -package ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data; +package ch.ethz.sis.openbis.generic.asapi.v3.dto.importer; import java.io.Serializable; +import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.ObjectToString; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; import ch.systemsx.cisd.base.annotation.JsonObject; -@JsonObject("as.dto.importer.data.ImportScript") -public class ImportScript implements Serializable +@JsonObject("as.dto.importer.ImportResult") +public class ImportResult implements Serializable { private static final long serialVersionUID = 1L; @JsonProperty - private String name; - - @JsonProperty - private String source; + private List<IObjectId> objectIds; @SuppressWarnings("unused") - public ImportScript() - { - } - - public ImportScript(final String name, final String source) + public ImportResult() { - this.name = name; - this.source = source; } - @JsonIgnore - public String getName() - { - return name; - } - - @JsonIgnore - public void setName(final String name) + public ImportResult(final List<IObjectId> objectIds) { - this.name = name; + this.objectIds = objectIds; } @JsonIgnore - public String getSource() + public List<IObjectId> getObjectIds() { - return source; + return objectIds; } @JsonIgnore - public void setSource(final String source) + public void setObjectIds(final List<IObjectId> objectIds) { - this.source = source; + this.objectIds = objectIds; } @Override public String toString() { - return new ObjectToString(this).append("name", name).append("source", source).toString(); + return new ObjectToString(this).append("objectIds", objectIds).toString(); } } diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/IImportData.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/IImportData.java deleted file mode 100644 index 4f47167bdca..00000000000 --- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/IImportData.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright ETH 2023 Zürich, 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. - */ - -package ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data; - -import java.io.Serializable; - -import ch.systemsx.cisd.base.annotation.JsonObject; - -@JsonObject("as.dto.importer.data.IImportData") -public interface IImportData extends Serializable -{ -} diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ZipImportData.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportData.java similarity index 74% rename from api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ZipImportData.java rename to api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportData.java index d3a95e6bbd3..02137385311 100644 --- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ZipImportData.java +++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportData.java @@ -24,8 +24,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.ObjectToString; import ch.systemsx.cisd.base.annotation.JsonObject; -@JsonObject("as.dto.importer.data.ZipImportData") -public class ZipImportData implements Serializable, IImportData +@JsonObject("as.dto.importer.data.ImportData") +public class ImportData implements Serializable { private static final long serialVersionUID = 1L; @@ -33,17 +33,17 @@ public class ZipImportData implements Serializable, IImportData private ImportFormat format; @JsonProperty - private byte[] file; + private String[] sessionWorkspaceFiles; @SuppressWarnings("unused") - public ZipImportData() + public ImportData() { } - public ZipImportData(final ImportFormat format, final byte[] file) + public ImportData(final ImportFormat format, final String... sessionWorkspaceFiles) { this.format = format; - this.file = file; + this.sessionWorkspaceFiles = sessionWorkspaceFiles; } @JsonIgnore @@ -58,16 +58,14 @@ public class ZipImportData implements Serializable, IImportData this.format = format; } - @JsonIgnore - public byte[] getFile() + public String[] getSessionWorkspaceFiles() { - return file; + return sessionWorkspaceFiles; } - @JsonIgnore - public void setFile(final byte[] file) + public void setSessionWorkspaceFiles(String[] sessionWorkspaceFiles) { - this.file = file; + this.sessionWorkspaceFiles = sessionWorkspaceFiles; } @Override diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportFormat.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportFormat.java index 3bd858e1271..415b5c62950 100644 --- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportFormat.java +++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportFormat.java @@ -22,6 +22,6 @@ import ch.systemsx.cisd.base.annotation.JsonObject; public enum ImportFormat { - XLS + EXCEL } diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportValue.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportValue.java deleted file mode 100644 index 005b3e1059a..00000000000 --- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/ImportValue.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright ETH 2023 Zürich, 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. - */ - -package ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data; - -import java.io.Serializable; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; - -import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.ObjectToString; -import ch.systemsx.cisd.base.annotation.JsonObject; - -@JsonObject("as.dto.importer.data.ImportValue") -public class ImportValue implements Serializable -{ - - private static final long serialVersionUID = 1L; - - @JsonProperty - private String name; - - @JsonProperty - private String value; - - @SuppressWarnings("unused") - public ImportValue() - { - } - - public ImportValue(final String name, final String value) - { - this.name = name; - this.value = value; - } - - @JsonIgnore - public String getName() - { - return name; - } - - @JsonIgnore - public void setName(final String name) - { - this.name = name; - } - - @JsonIgnore - public String getValue() - { - return value; - } - - @JsonIgnore - public void setValue(final String value) - { - this.value = value; - } - - @Override - public String toString() - { - final ObjectToString builder = new ObjectToString(this).append("name", name); - if (value == null) - { - builder.append("value", null); - } else if (value.length() <= 255) - { - builder.append("value", value); - } else - { - builder.append("value", value.substring(0, 255) + "..."); - } - return builder.toString(); - } - -} diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/UncompressedImportData.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/UncompressedImportData.java deleted file mode 100644 index 44113b16817..00000000000 --- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/importer/data/UncompressedImportData.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright ETH 2023 Zürich, 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. - */ - -package ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data; - -import java.io.Serializable; -import java.util.Collection; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; - -import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.ObjectToString; -import ch.systemsx.cisd.base.annotation.JsonObject; - -@JsonObject("as.dto.importer.data.UncompressedImportData") -public class UncompressedImportData implements Serializable, IImportData -{ - private static final long serialVersionUID = 1L; - - @JsonProperty - private ImportFormat format; - - @JsonProperty - private byte[] file; - - @JsonProperty - private Collection<ImportScript> scripts; - - @JsonProperty - private Collection<ImportValue> importValues; - - @SuppressWarnings("unused") - public UncompressedImportData() - { - } - - public UncompressedImportData(final ImportFormat format, final byte[] file, final Collection<ImportScript> scripts, - final Collection<ImportValue> importValues) - { - this.format = format; - this.file = file; - this.scripts = scripts; - this.importValues = importValues; - } - - @JsonIgnore - public ImportFormat getFormat() - { - return format; - } - - @JsonIgnore - public void setFormat(final ImportFormat format) - { - this.format = format; - } - - @JsonIgnore - public byte[] getFile() - { - return file; - } - - @JsonIgnore - public void setFile(final byte[] file) - { - this.file = file; - } - - @JsonIgnore - public Collection<ImportScript> getScripts() - { - return scripts; - } - - @JsonIgnore - public void setScripts(final Collection<ImportScript> scripts) - { - this.scripts = scripts; - } - - @JsonIgnore - public Collection<ImportValue> getImportValues() - { - return importValues; - } - - @JsonIgnore - public void setImportValues(final Collection<ImportValue> importValues) - { - this.importValues = importValues; - } - - @Override - public String toString() - { - return new ObjectToString(this).append("format", format).append("scripts", scripts).toString(); - } - -} diff --git a/api-openbis-java/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt b/api-openbis-java/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt index af8a6580cf7..21daba1c397 100644 --- a/api-openbis-java/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt +++ b/api-openbis-java/sourceTest/java/ch/ethz/sis/openbis/generic/sharedapi/v3/dictionary.txt @@ -464,6 +464,7 @@ Event Sort Options Event Tech Id Event Type Event Type Search Criteria +EXCEL exec execute Aggregation Service Execute Aggregation Service Operation @@ -1282,7 +1283,6 @@ IExternalDmsId IFileFormatTypeId IGNORE_EXISTING IIdentifierHolder -IImportData ILocatorTypeId ImagingDataSetConfig ImagingDataSetControl @@ -1300,8 +1300,6 @@ ImportModes ImportOperation ImportOperationResult ImportOptions -ImportScript -ImportValue indent indexOf INSTANCE @@ -2495,7 +2493,6 @@ UNARCHIVE_PENDING UnarchiveDataSetsOperation UnarchiveDataSetsOperationResult UnauthorizedObjectAccessException -UncompressedImportData UNDEFINED UNIQUE_SUBCODES UNKNOWN @@ -2903,7 +2900,6 @@ XlsTextFormat XLSX XML YES -ZipImportData I Transaction Coordinator Api I Transaction Participant Api APPLICATION_SERVER_PARTICIPANT_ID diff --git a/api-openbis-javascript/src/v3/as/dto/importer/ImportOperation.js b/api-openbis-javascript/src/v3/as/dto/importer/ImportOperation.js index 717aa8112bf..aeb00d41c2d 100644 --- a/api-openbis-javascript/src/v3/as/dto/importer/ImportOperation.js +++ b/api-openbis-javascript/src/v3/as/dto/importer/ImportOperation.js @@ -46,7 +46,7 @@ define(["stjs", "as/dto/common/operation/IOperation"], }; }, { - importData: "IImportData", + importData: "ImportData", importOptions: "ImportOptions" } ); diff --git a/api-openbis-javascript/src/v3/as/dto/importer/ImportOperationResult.js b/api-openbis-javascript/src/v3/as/dto/importer/ImportOperationResult.js index 8644008309f..9bdfca0689c 100644 --- a/api-openbis-javascript/src/v3/as/dto/importer/ImportOperationResult.js +++ b/api-openbis-javascript/src/v3/as/dto/importer/ImportOperationResult.js @@ -17,7 +17,8 @@ define(["stjs", "as/dto/common/operation/IOperationResult"], function (stjs, IOperationResult) { - var ImportOperationResult = function() { + var ImportOperationResult = function(importResult) { + this.importResult = importResult; } stjs.extend( @@ -28,12 +29,23 @@ define(["stjs", "as/dto/common/operation/IOperationResult"], prototype["@type"] = "as.dto.importer.ImportOperationResult"; constructor.serialVersionUID = 1; + prototype.importResult = null; prototype.getMessage = function() { return "ImportOperationResult"; }; + + prototype.getImportResult = function() { + return this.importResult; + }; + + prototype.setImportResult = function(importResult) { + this.importResult = importResult; + }; }, - {} + { + importResult: "ImportResult" + } ); return ImportOperationResult; diff --git a/api-openbis-javascript/src/v3/as/dto/importer/data/ImportScript.js b/api-openbis-javascript/src/v3/as/dto/importer/ImportResult.js similarity index 58% rename from api-openbis-javascript/src/v3/as/dto/importer/data/ImportScript.js rename to api-openbis-javascript/src/v3/as/dto/importer/ImportResult.js index 64545851a62..29daa115a98 100644 --- a/api-openbis-javascript/src/v3/as/dto/importer/data/ImportScript.js +++ b/api-openbis-javascript/src/v3/as/dto/importer/ImportResult.js @@ -15,40 +15,36 @@ * */ -define(["stjs"], - function (stjs) { - var ImportScript = function() { +define(["stjs"], function (stjs) { + var ImportResult = function(objectIds) { + this.objectIds = objectIds; } stjs.extend( - ImportScript, + ImportResult, null, [], function (constructor, prototype) { - prototype["@type"] = "as.dto.importer.data.ImportScript"; + prototype["@type"] = "as.dto.importer.ImportResult"; constructor.serialVersionUID = 1; - prototype.name = null; - prototype.source = null; + prototype.objectIds = null; - prototype.getName = function() { - return this.name; + prototype.getObjectIds = function() { + return this.objectIds; }; - prototype.setName = function(name) { - this.name = name; - }; - - prototype.getSource = function() { - return this.source; - }; - - prototype.setSource = function(source) { - this.source = source; + prototype.setObjectIds = function(objectIds) { + this.objectIds = objectIds; }; }, - {} + { + objectIds: { + name: "List", + arguments: ["IObjectId"] + } + } ); - return ImportScript; + return ImportResult; }); \ No newline at end of file diff --git a/api-openbis-javascript/src/v3/as/dto/importer/data/IImportData.js b/api-openbis-javascript/src/v3/as/dto/importer/data/IImportData.js deleted file mode 100644 index 1289c8e8e4d..00000000000 --- a/api-openbis-javascript/src/v3/as/dto/importer/data/IImportData.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright ETH 2023 Zürich, 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. - * - */ - -define(["stjs"], function (stjs) { - var IImportData = function () { - }; - stjs.extend(IImportData, null, [], null, {}); - return IImportData; -}); \ No newline at end of file diff --git a/api-openbis-javascript/src/v3/as/dto/importer/data/ZipImportData.js b/api-openbis-javascript/src/v3/as/dto/importer/data/ImportData.js similarity index 61% rename from api-openbis-javascript/src/v3/as/dto/importer/data/ZipImportData.js rename to api-openbis-javascript/src/v3/as/dto/importer/data/ImportData.js index 918cc534cd6..9786a7ef566 100644 --- a/api-openbis-javascript/src/v3/as/dto/importer/data/ZipImportData.js +++ b/api-openbis-javascript/src/v3/as/dto/importer/data/ImportData.js @@ -15,21 +15,22 @@ * */ -define(["stjs", "as/dto/importer/data/IImportData"], - function (stjs, IImportData) { - var ZipImportData = function() { +define(["stjs"], function (stjs) { + var ImportData = function(format, sessionWorkspaceFiles) { + this.format = format; + this.sessionWorkspaceFiles = sessionWorkspaceFiles; } stjs.extend( - ZipImportData, - IImportData, - [IImportData], + ImportData, + null, + [], function (constructor, prototype) { - prototype["@type"] = "as.dto.importer.data.ZipImportData"; + prototype["@type"] = "as.dto.importer.data.ImportData"; constructor.serialVersionUID = 1; prototype.format = null; - prototype.file = null; + prototype.sessionWorkspaceFiles = null; prototype.getFormat = function() { return this.format; @@ -39,19 +40,19 @@ define(["stjs", "as/dto/importer/data/IImportData"], this.format = format; }; - prototype.getFile = function() { - return this.file; + prototype.getSessionWorkspaceFiles = function() { + return this.sessionWorkspaceFiles; }; - prototype.setFile = function(file) { - this.file = file; + prototype.setSessionWorkspaceFiles = function(sessionWorkspaceFiles) { + this.sessionWorkspaceFiles = sessionWorkspaceFiles; }; }, { format: "ImportFormat", - file: "byte[]" + sessionWorkspaceFiles: "String[]" } ); - return ZipImportData; + return ImportData; }); \ No newline at end of file diff --git a/api-openbis-javascript/src/v3/as/dto/importer/data/ImportFormat.js b/api-openbis-javascript/src/v3/as/dto/importer/data/ImportFormat.js index 982b7ff16bf..4ffb6dd2454 100644 --- a/api-openbis-javascript/src/v3/as/dto/importer/data/ImportFormat.js +++ b/api-openbis-javascript/src/v3/as/dto/importer/data/ImportFormat.js @@ -17,7 +17,7 @@ define(["stjs", "as/dto/common/Enum"], function (stjs, Enum) { var ImportFormat = function() { - Enum.call(this, ["XLS"]); + Enum.call(this, ["EXCEL"]); } stjs.extend( diff --git a/api-openbis-javascript/src/v3/as/dto/importer/data/ImportValue.js b/api-openbis-javascript/src/v3/as/dto/importer/data/ImportValue.js deleted file mode 100644 index e8bddc19ddb..00000000000 --- a/api-openbis-javascript/src/v3/as/dto/importer/data/ImportValue.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright ETH 2024 Zürich, 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. - * - */ - -define(["stjs"], - function (stjs) { - var ImportValue = function() { - } - - stjs.extend( - ImportValue, - null, - [], - function (constructor, prototype) { - prototype["@type"] = "as.dto.importer.data.ImportValue"; - - constructor.serialVersionUID = 1; - prototype.name = null; - prototype.value = null; - - prototype.getName = function() { - return this.name; - }; - - prototype.setName = function(name) { - this.name = name; - }; - - prototype.getValue = function() { - return this.value; - }; - - prototype.setValue = function(value) { - this.value = value; - }; - }, - {} - ); - - return ImportValue; - }); \ No newline at end of file diff --git a/api-openbis-javascript/src/v3/as/dto/importer/data/UncompressedImportData.js b/api-openbis-javascript/src/v3/as/dto/importer/data/UncompressedImportData.js deleted file mode 100644 index 49084d1b71c..00000000000 --- a/api-openbis-javascript/src/v3/as/dto/importer/data/UncompressedImportData.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright ETH 2023 Zürich, 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. - * - */ - -define(["stjs", "as/dto/importer/data/IImportData"], - function (stjs, IImportData) { - var UncompressedImportData = function() { - } - - stjs.extend( - UncompressedImportData, - IImportData, - [IImportData], - function (constructor, prototype) { - prototype["@type"] = "as.dto.importer.data.UncompressedImportData"; - - constructor.serialVersionUID = 1; - prototype.format = null; - prototype.file = null; - prototype.scripts = null; - prototype.importValues = null; - - prototype.getFormat = function() { - return this.format; - }; - - prototype.setFormat = function(format) { - this.format = format; - }; - - prototype.getFile = function() { - return this.file; - }; - - prototype.setFile = function(file) { - this.file = file; - }; - - prototype.getScripts = function() { - return this.scripts; - }; - - prototype.setScripts = function(scripts) { - this.scripts = scripts; - }; - - prototype.getImportValues = function() { - return this.importValues; - }; - - prototype.setImportValues = function(importValues) { - this.importValues = importValues; - }; - }, - { - format: "ImportFormat", - file: "byte[]", - scripts: { - name: "Collection", - arguments: ["ImportScript"] - }, - importValues: { - name: "Collection", - arguments: ["ImportValue"] - } - } - ); - - return UncompressedImportData; - }); \ No newline at end of file diff --git a/api-openbis-javascript/src/v3/openbis.js b/api-openbis-javascript/src/v3/openbis.js index f7a8d669586..a5c1a6be3fd 100644 --- a/api-openbis-javascript/src/v3/openbis.js +++ b/api-openbis-javascript/src/v3/openbis.js @@ -2728,6 +2728,34 @@ define([ 'jquery', 'util/Json', 'as/dto/datastore/search/DataStoreSearchCriteria }) } + this.uploadToSessionWorkspace = function(file) { + //Building Form Data Object for Multipart File Upload + var formData = new FormData(); + formData.append("sessionKeysNumber", "1"); + formData.append("sessionKey_0", "openbis-file-upload"); + formData.append("openbis-file-upload", file); + formData.append("keepOriginalFileName", "True"); + formData.append("sessionID", this._private.sessionToken); + + var dfd = jquery.Deferred(); + + jquery.ajax({ + type: "POST", + url: "/openbis/openbis/upload", + contentType: false, + processData: false, + data: formData, + success: function() { + dfd.resolve(); + }, + error: function() { + dfd.reject(); + } + }); + + return dfd.promise(); + } + /** * ======================= * OpenBIS webapp context diff --git a/api-openbis-typescript/source/java/ch/ethz/sis/openbis/generic/typescript/dto/OpenBISJavaScriptFacade.java b/api-openbis-typescript/source/java/ch/ethz/sis/openbis/generic/typescript/dto/OpenBISJavaScriptFacade.java index cb1cb283777..c55846a1f9f 100644 --- a/api-openbis-typescript/source/java/ch/ethz/sis/openbis/generic/typescript/dto/OpenBISJavaScriptFacade.java +++ b/api-openbis-typescript/source/java/ch/ethz/sis/openbis/generic/typescript/dto/OpenBISJavaScriptFacade.java @@ -75,7 +75,8 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.update.ExternalDmsUp import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.GlobalSearchObject; import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.fetchoptions.GlobalSearchObjectFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.search.GlobalSearchCriteria; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.IImportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportData; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.Material; import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.MaterialType; @@ -1322,9 +1323,9 @@ public class OpenBISJavaScriptFacade implements IApplicationServerApi } @TypeScriptMethod - @Override public void executeImport(final String sessionToken, final IImportData importData, final ImportOptions importOptions) + @Override public ImportResult executeImport(final String sessionToken, final ImportData importData, final ImportOptions importOptions) { - + return null; } @TypeScriptMethod @@ -1333,6 +1334,11 @@ public class OpenBISJavaScriptFacade implements IApplicationServerApi return null; } + @TypeScriptMethod(sessionToken = false) + public void uploadToSessionWorkspace(final Object file) + { + } + @TypeScriptMethod(sessionToken = false) @Override public int getMajorVersion() { diff --git a/core-plugin-openbis/.dynamic_property_evaluator_queue b/core-plugin-openbis/.dynamic_property_evaluator_queue deleted file mode 100644 index 2e08dce276e43c677d121d627a250d1710fed824..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12 Rcmeyd#OO5x0|O5T0{|U20@45g diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java index 69708ddf23d..42c27477f7a 100644 --- a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java +++ b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/AbstractImportTest.java @@ -16,6 +16,9 @@ package ch.ethz.sis.openbis.systemtest.plugin.excelimport; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests; @@ -24,8 +27,10 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeSuite; import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi; +import ch.systemsx.cisd.openbis.generic.server.CommonServiceProvider; import ch.systemsx.cisd.openbis.generic.server.util.TestInitializer; import ch.systemsx.cisd.openbis.generic.shared.Constants; +import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider; import ch.systemsx.cisd.openbis.generic.shared.coreplugin.CorePluginsUtils; public class AbstractImportTest extends AbstractTransactionalTestNGSpringContextTests @@ -41,6 +46,10 @@ public class AbstractImportTest extends AbstractTransactionalTestNGSpringContext protected static final String PASSWORD = "password"; + protected static final String VALIDATION_SCRIPT = "full/scripts/valid.py"; + + protected static final String DYNAMIC_SCRIPT = "full/scripts/dynamic/dynamic.py"; + @Autowired protected IApplicationServerInternalApi v3api; @@ -54,6 +63,7 @@ public class AbstractImportTest extends AbstractTransactionalTestNGSpringContext System.setProperty(XLS_VERSIONING_DIR, VERSIONING_JSON); System.setProperty(CorePluginsUtils.CORE_PLUGINS_FOLDER_KEY, "dist/core-plugins"); System.setProperty(Constants.ENABLED_MODULES_KEY, "xls-import"); + System.setProperty(Constants.PROJECT_SAMPLES_ENABLED_KEY, "false"); TestInitializer.initEmptyDbNoIndex(); } @@ -71,4 +81,31 @@ public class AbstractImportTest extends AbstractTransactionalTestNGSpringContext v3api.logout(sessionToken); } + protected static String uploadToAsSessionWorkspace(final String sessionToken, final String filePath) throws IOException + { + final ISessionWorkspaceProvider sessionWorkspaceProvider = CommonServiceProvider.getSessionWorkspaceProvider(); + final String destination = UUID.randomUUID() + "/" + new File(filePath).getName(); + + sessionWorkspaceProvider.write(sessionToken, destination, new FileInputStream("sourceTest/java/" + filePath)); + + return destination; + } + + protected static String[] uploadToAsSessionWorkspace(final String sessionToken, final String... filePaths) throws IOException + { + final ISessionWorkspaceProvider sessionWorkspaceProvider = CommonServiceProvider.getSessionWorkspaceProvider(); + final UUID uploadId = UUID.randomUUID(); + + final String[] destinations = new String[filePaths.length]; + for (int i = 0; i < filePaths.length; i++) + { + destinations[i] = uploadId + + (filePaths[i].toLowerCase().endsWith(".xls") || filePaths[i].toLowerCase().endsWith(".xlsx") ? "/" : "/scripts/") + + new File(filePaths[i]).getName(); + sessionWorkspaceProvider.write(sessionToken, destinations[i], new FileInputStream("sourceTest/java/" + filePaths[i])); + } + + return destinations; + } + } diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportDatasetTypesTest.java b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportDatasetTypesTest.java index 1a13a1028e6..bf607e47456 100644 --- a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportDatasetTypesTest.java +++ b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportDatasetTypesTest.java @@ -70,13 +70,16 @@ public class ImportDatasetTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, DATASET_TYPES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, DATASET_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN DataSetType rawData = TestUtils.getDatasetType(v3api, sessionToken, "RAW_DATA"); List<String> propertyNames = Arrays.asList("$NAME", "NOTES"); List<PropertyAssignment> propertyAssignments = TestUtils.extractAndSortPropertyAssignmentsPerGivenPropertyName(rawData, propertyNames); PropertyAssignment nameProperty = propertyAssignments.get(0); PropertyAssignment notesProperty = propertyAssignments.get(1); + // THEN assertEquals(rawData.getCode(), "RAW_DATA"); assertEquals(rawData.getPropertyAssignments().size(), 2); @@ -101,9 +104,12 @@ public class ImportDatasetTypesTest extends AbstractImportTest public void testDatasetTypesWithoutPropertiesTypesAreCreated() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, DATASET_WITHOUT_PROPERTIES))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, DATASET_WITHOUT_PROPERTIES)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN DataSetType rawData = TestUtils.getDatasetType(v3api, sessionToken, "RAW_DATA"); + // THEN assertEquals(rawData.getCode(), "RAW_DATA"); assertEquals(rawData.getPropertyAssignments().size(), 0); @@ -114,10 +120,13 @@ public class ImportDatasetTypesTest extends AbstractImportTest public void testDatasetTypesWithValidationScript() throws Exception { // GIVEN - TestUtils.createFrom(v3api, sessionToken, TestUtils.getValidationPluginMap(), - Paths.get(FilenameUtils.concat(FILES_DIR, DATASET_WITH_VALIDATION_SCRIPT))); + final String[] sessionWorkspaceFilePaths = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, DATASET_WITH_VALIDATION_SCRIPT), FilenameUtils.concat(FILES_DIR, VALIDATION_SCRIPT)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePaths[0])); + // WHEN DataSetType rawData = TestUtils.getDatasetType(v3api, sessionToken, "RAW_DATA"); + // THEN assertEquals(rawData.getValidationPlugin().getName().toUpperCase(), "RAW_DATA.VALID"); } @@ -130,15 +139,19 @@ public class ImportDatasetTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, DATASET_TYPES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, DATASET_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN - TestUtils.createFrom(v3api, sessionToken, TestUtils.getDynamicPluginMap(), UpdateMode.UPDATE_IF_EXISTS, - Paths.get(FilenameUtils.concat(FILES_DIR, DATASET_TYPES_UPDATE))); + final String[] sessionWorkspaceFilePaths = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, DATASET_TYPES_UPDATE), FilenameUtils.concat(FILES_DIR, DYNAMIC_SCRIPT)); + TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(sessionWorkspaceFilePaths[0])); DataSetType rawData = TestUtils.getDatasetType(v3api, sessionToken, "RAW_DATA"); List<String> propertyNames = Arrays.asList("$NAME", "NOTES"); List<PropertyAssignment> propertyAssignments = TestUtils.extractAndSortPropertyAssignmentsPerGivenPropertyName(rawData, propertyNames); PropertyAssignment nameProperty = propertyAssignments.get(0); PropertyAssignment notesProperty = propertyAssignments.get(1); + // THEN // Property Assignment updates are not supported, no change here between updates. assertTrue(nameProperty.isMandatory()); @@ -157,10 +170,11 @@ public class ImportDatasetTypesTest extends AbstractImportTest assertEquals(notesProperty.getPlugin(), null); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = "(?s).*Header should contain 'Code'.*") public void shouldThrowExceptionIfNoSampleCode() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, DATASET_NO_CODE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, DATASET_NO_CODE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } } diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentTypesTest.java b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentTypesTest.java index f8f56716ddd..843e6c02e72 100644 --- a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentTypesTest.java +++ b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentTypesTest.java @@ -73,13 +73,16 @@ public class ImportExperimentTypesTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN ExperimentType collection = TestUtils.getExperimentType(v3api, sessionToken, "COLLECTION"); List<String> propertyNames = Arrays.asList("$NAME", "DEFAULT_OBJECT_TYPE"); List<PropertyAssignment> propertyAssignments = TestUtils.extractAndSortPropertyAssignmentsPerGivenPropertyName(collection, propertyNames); PropertyAssignment nameProperty = propertyAssignments.get(0); PropertyAssignment defaultObjectTypeProperty = propertyAssignments.get(1); + // THEN assertEquals(collection.getCode(), "COLLECTION"); assertEquals(collection.getPropertyAssignments().size(), 2); @@ -108,15 +111,19 @@ public class ImportExperimentTypesTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN - TestUtils.createFrom(v3api, sessionToken, TestUtils.getDynamicPluginMap(), UpdateMode.UPDATE_IF_EXISTS, - Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPES_UPDATE))); + final String[] updateSessionWorkspaceFilePaths = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPES_UPDATE), FilenameUtils.concat(FILES_DIR, DYNAMIC_SCRIPT)); + TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(updateSessionWorkspaceFilePaths[0])); ExperimentType collection = TestUtils.getExperimentType(v3api, sessionToken, "COLLECTION"); List<String> propertyNames = Arrays.asList("$NAME", "DEFAULT_OBJECT_TYPE"); List<PropertyAssignment> propertyAssignments = TestUtils.extractAndSortPropertyAssignmentsPerGivenPropertyName(collection, propertyNames); PropertyAssignment nameProperty = propertyAssignments.get(0); PropertyAssignment defaultObjectTypeProperty = propertyAssignments.get(1); + // THEN // Property Assignment updates are not supported, no change here between updates. assertTrue(nameProperty.isMandatory()); @@ -141,18 +148,22 @@ public class ImportExperimentTypesTest extends AbstractImportTest public void testExperimentTypesWithValidationScript() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, TestUtils.getValidationPluginMap(), - Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_WITH_VALIDATION_SCRIPT))); + final String[] sessionWorkspaceFilePaths = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENT_WITH_VALIDATION_SCRIPT), FilenameUtils.concat(FILES_DIR, VALIDATION_SCRIPT)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePaths[0])); + // WHEN ExperimentType collection = TestUtils.getExperimentType(v3api, sessionToken, "COLLECTION"); + // THEN assertEquals(collection.getValidationPlugin().getName().toUpperCase(), "COLLECTION.VALID"); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = "(?s).*Mandatory field is missing or empty: Code.*") public void shouldThrowExceptionIfNoSampleCode() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_NO_CODE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, EXPERIMENT_NO_CODE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } } diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentsTest.java b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentsTest.java index 742ed9ffbe5..ec529f6dcff 100644 --- a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentsTest.java +++ b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportExperimentsTest.java @@ -96,9 +96,12 @@ public class ImportExperimentsTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, EXPERIMENT_XLS)); + TestUtils.createFrom(v3api, sessionToken, UpdateMode.FAIL_IF_EXISTS, Paths.get(sessionWorkspaceFilePath)); + // WHEN Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); + // THEN assertEquals(experiment.getCode(), "TEST_EXPERIMENT"); assertEquals(experiment.getProject().getCode(), "TEST_PROJECT"); @@ -114,9 +117,12 @@ public class ImportExperimentsTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, EXPERIMENT_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT2", "TEST_PROJECT", "TEST_SPACE"); + // THEN assertEquals(experiment.getCode(), "TEST_EXPERIMENT2"); assertEquals(experiment.getProject().getCode(), "TEST_PROJECT"); @@ -132,12 +138,23 @@ public class ImportExperimentsTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECT))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_ALL_ELSEWHERE))); + final String experimentTypeSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(experimentTypeSessionWorkspaceFilePath)); + + final String spaceSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(spaceSessionWorkspaceFilePath)); + + final String projectSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROJECT)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(projectSessionWorkspaceFilePath)); + + final String experimentsSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_ALL_ELSEWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(experimentsSessionWorkspaceFilePath)); + // WHEN Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); + // THEN assertEquals(experiment.getCode(), "TEST_EXPERIMENT"); assertEquals(experiment.getProject().getCode(), "TEST_PROJECT"); @@ -153,12 +170,22 @@ public class ImportExperimentsTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECT))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_XLS))); + final String experimentTypeSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(experimentTypeSessionWorkspaceFilePath)); + + final String spaceSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(spaceSessionWorkspaceFilePath)); + + final String projectSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROJECT)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(projectSessionWorkspaceFilePath)); + + final String experimentsSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, EXPERIMENT_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(experimentsSessionWorkspaceFilePath)); + // WHEN Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); + // THEN assertEquals(experiment.getCode(), "TEST_EXPERIMENT"); assertEquals(experiment.getProject().getCode(), "TEST_PROJECT"); @@ -166,20 +193,36 @@ public class ImportExperimentsTest extends AbstractImportTest assertEquals(experiment.getProperties().get("DEFAULT_OBJECT_TYPE"), "OBJECT_TYPE"); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = "(?s).*Experiment type COLLECTION not found.*") public void shouldThrowExceptionIfExperimentTypeDoesntExist() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECT))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_ALL_ELSEWHERE))); + final String spaceSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(spaceSessionWorkspaceFilePath)); + + final String projectSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROJECT)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(projectSessionWorkspaceFilePath)); + + final String experimentsSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_ALL_ELSEWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(experimentsSessionWorkspaceFilePath)); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = "(?s).*Entity \\[/TEST_SPACE/TEST_PROJECT\\] could not be found\\..*") public void shouldThrowExceptionIfProjectDoesntExist() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_ALL_ELSEWHERE))); + // the Excel contains internally property types which can be only manipulated by the system user + sessionToken = v3api.loginAsSystem(); + + final String experimentTypeSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(experimentTypeSessionWorkspaceFilePath)); + + final String spaceSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(spaceSessionWorkspaceFilePath)); + + final String experimentsSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_ALL_ELSEWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(experimentsSessionWorkspaceFilePath)); } @Test @@ -190,10 +233,17 @@ public class ImportExperimentsTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_TYPE_ELSEWHERE))); + final String experimentTypeSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(experimentTypeSessionWorkspaceFilePath)); + + final String experimentsSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_TYPE_ELSEWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(experimentsSessionWorkspaceFilePath)); + // WHEN Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); + // THEN assertEquals(experiment.getCode(), "TEST_EXPERIMENT"); assertEquals(experiment.getProject().getCode(), "TEST_PROJECT"); @@ -209,12 +259,23 @@ public class ImportExperimentsTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECT))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_TYPE_ELSEWHERE))); + final String experimentTypeSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(experimentTypeSessionWorkspaceFilePath)); + + final String spaceSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(spaceSessionWorkspaceFilePath)); + + final String projectSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROJECT)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(projectSessionWorkspaceFilePath)); + + final String experimentsSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_TYPE_ELSEWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(experimentsSessionWorkspaceFilePath)); + // WHEN Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); + // THEN assertEquals(experiment.getCode(), "TEST_EXPERIMENT"); assertEquals(experiment.getProject().getCode(), "TEST_PROJECT"); @@ -225,7 +286,8 @@ public class ImportExperimentsTest extends AbstractImportTest @Test(expectedExceptions = UserFailureException.class) public void shouldThrowExceptionIfExperimentNoCode() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_NO_CODE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, EXPERIMENTS_NO_CODE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } @Test @@ -236,9 +298,13 @@ public class ImportExperimentsTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_NON_MANDATORY_PROPERTY_MISSING))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_NON_MANDATORY_PROPERTY_MISSING)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); + // THEN assertEquals(experiment.getCode(), "TEST_EXPERIMENT"); assertEquals(experiment.getProject().getCode(), "TEST_PROJECT"); @@ -249,7 +315,9 @@ public class ImportExperimentsTest extends AbstractImportTest @Test(expectedExceptions = RuntimeException.class) public void shouldThrowExceptionIfExperimentNoProject() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_NO_PROJECT_ATTRIBUTE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_NO_PROJECT_ATTRIBUTE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } @Test @@ -260,11 +328,19 @@ public class ImportExperimentsTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECT))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_SPACE_AND_PROJECT_ELSEWHERE))); + final String spaceSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(spaceSessionWorkspaceFilePath)); + + final String projectSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROJECT)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(projectSessionWorkspaceFilePath)); + + final String elsewhereWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_SPACE_AND_PROJECT_ELSEWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(elsewhereWorkspaceFilePath)); + // WHEN Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); + // THEN assertEquals(experiment.getCode(), "TEST_EXPERIMENT"); assertEquals(experiment.getProject().getCode(), "TEST_PROJECT"); @@ -280,10 +356,16 @@ public class ImportExperimentsTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_SPACE_ELSEWHERE))); + final String spaceSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(spaceSessionWorkspaceFilePath)); + + final String spaceElsewhereSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_SPACE_ELSEWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(spaceElsewhereSessionWorkspaceFilePath)); + // WHEN Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); + // THEN assertEquals(experiment.getCode(), "TEST_EXPERIMENT"); assertEquals(experiment.getProject().getCode(), "TEST_PROJECT"); @@ -299,11 +381,20 @@ public class ImportExperimentsTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_TYPE_AND_SPACE_ELSEWHERE))); + final String experimentTypeSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(experimentTypeSessionWorkspaceFilePath)); + + final String spaceSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(spaceSessionWorkspaceFilePath)); + + final String elsewhereSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_TYPE_AND_SPACE_ELSEWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(elsewhereSessionWorkspaceFilePath)); + // WHEN Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); + // THEN assertEquals(experiment.getCode(), "TEST_EXPERIMENT"); assertEquals(experiment.getProject().getCode(), "TEST_PROJECT"); @@ -314,7 +405,9 @@ public class ImportExperimentsTest extends AbstractImportTest @Test(expectedExceptions = UserFailureException.class) public void shouldThrowExceptionIfMandatoryPropertyMissing() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_MANDATORY_PROPERTY_MISSING))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_MANDATORY_PROPERTY_MISSING)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } @Test @@ -325,9 +418,13 @@ public class ImportExperimentsTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_MANDATORY_PROPERTY_PRESENT))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_WITH_MANDATORY_PROPERTY_PRESENT)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); + // THEN assertEquals(experiment.getCode(), "TEST_EXPERIMENT"); assertEquals(experiment.getProject().getCode(), "TEST_PROJECT"); @@ -343,9 +440,13 @@ public class ImportExperimentsTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_PROPERTIES_COLUMNS_AS_LABELS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_PROPERTIES_COLUMNS_AS_LABELS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); + // THEN assertEquals(experiment.getCode(), "TEST_EXPERIMENT"); assertEquals(experiment.getProject().getCode(), "TEST_PROJECT"); @@ -361,11 +462,17 @@ public class ImportExperimentsTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE))); - TestUtils.createFrom(v3api, sessionToken, - Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENTS_PROPERTIES_COLUMNS_AS_LABELS_TYPE_ON_SERVER))); + final String experimentTypeSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENT_TYPE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(experimentTypeSessionWorkspaceFilePath)); + + final String elsewhereSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENTS_PROPERTIES_COLUMNS_AS_LABELS_TYPE_ON_SERVER)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(elsewhereSessionWorkspaceFilePath)); + // WHEN Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); + // THEN assertEquals(experiment.getCode(), "TEST_EXPERIMENT"); assertEquals(experiment.getProject().getCode(), "TEST_PROJECT"); @@ -373,19 +480,26 @@ public class ImportExperimentsTest extends AbstractImportTest assertEquals(experiment.getProperties().get("DEFAULT_OBJECT_TYPE"), "OBJECT_TYPE"); } -// @Test -// @DirtiesContext -// public void testExperimentsUpdate() throws Exception -// { -// // GIVEN -// TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_XLS))); -// // WHEN -// TestUtils.createFrom(v3api, sessionToken, TestUtils.getDynamicPluginMap(), UpdateMode.UPDATE_IF_EXISTS, -// Paths.get(FilenameUtils.concat(FILES_DIR, EXPERIMENT_UPDATE))); -// Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); -// // THEN -// assertEquals(experiment.getProperties().get("$NAME"), "NameUpdate"); -// assertEquals(experiment.getProperties().get("DEFAULT_OBJECT_TYPE"), "DefaultObjectTypeUpdate"); -// } + @Test + @DirtiesContext + public void testExperimentsUpdate() throws Exception + { + // the Excel contains internally managed property types which can be only manipulated by the system user + String sessionToken = v3api.loginAsSystem(); + + // GIVEN + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, EXPERIMENT_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + + // WHEN + final String[] updateSessionWorkspaceFilePaths = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, EXPERIMENT_UPDATE), FilenameUtils.concat(FILES_DIR, DYNAMIC_SCRIPT)); + TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(updateSessionWorkspaceFilePaths[0])); + Experiment experiment = TestUtils.getExperiment(v3api, sessionToken, "TEST_EXPERIMENT", "TEST_PROJECT", "TEST_SPACE"); + + // THEN + assertEquals(experiment.getProperties().get("$NAME"), "NameUpdate"); + assertEquals(experiment.getProperties().get("DEFAULT_OBJECT_TYPE"), "DefaultObjectTypeUpdate"); + } } diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportFromExcelTest.java b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportFromExcelTest.java index 46549e079ef..758adcb28d7 100644 --- a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportFromExcelTest.java +++ b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportFromExcelTest.java @@ -21,7 +21,6 @@ import static org.testng.Assert.assertNotNull; import java.io.IOException; import java.nio.file.Paths; -import ch.systemsx.cisd.common.exceptions.UserFailureException; import org.apache.commons.io.FilenameUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; @@ -34,6 +33,7 @@ import org.testng.annotations.Test; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.ExperimentType; import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample; import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi; +import ch.systemsx.cisd.common.exceptions.UserFailureException; @ContextConfiguration(locations = "classpath:applicationContext.xml") @Transactional(transactionManager = "transaction-manager") @@ -53,21 +53,23 @@ public class ImportFromExcelTest extends AbstractImportTest FILES_DIR = f.substring(0, f.length() - ImportExperimentTypesTest.class.getSimpleName().length()) + "/test_files/"; } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, + expectedExceptionsMessageRegExp = "(?s).*Content found after a double blank row that should mark the end of a page\\..*") @DirtiesContext public void testFileWithManyBlankRowsWasParsed() throws Exception { // the Excel contains internally managed property types which can be only manipulated by the system user sessionToken = v3api.loginAsSystem(); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, WITH_BLANKS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, WITH_BLANKS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); ExperimentType propertyType = TestUtils.getExperimentType(v3api, sessionToken, "COLLECTION"); assertNotNull(propertyType); // the last line on the first sheet was read assertEquals(propertyType.getPropertyAssignments().size(), 2); assertEquals(propertyType.getPropertyAssignments().get(1).getPropertyType().getCode(), "LAST_ROW_EXP_TYPE"); - Sample sample = TestUtils.getSample(v3api, sessionToken, "LAST_SAMPLE", "TEST_SPACE"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "TEST_SPACE", "LAST_SAMPLE"); assertNotNull(sample); // the last line on the second sheet was read } } \ No newline at end of file diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportProjectsTest.java b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportProjectsTest.java index 0c827f35dd5..01aa8bc5462 100644 --- a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportProjectsTest.java +++ b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportProjectsTest.java @@ -69,9 +69,12 @@ public class ImportProjectsTest extends AbstractImportTest public void testProjectsAreCreated() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROJECTS_XLS)); + TestUtils.createFrom(v3api, sessionToken, UpdateMode.FAIL_IF_EXISTS, Paths.get(sessionWorkspaceFilePath)); + // WHEN Project project = TestUtils.getProject(v3api, sessionToken, "TEST_PROJECT"); + // THEN assertEquals(project.getCode(), "TEST_PROJECT"); assertEquals(project.getDescription(), "TEST"); @@ -83,10 +86,14 @@ public class ImportProjectsTest extends AbstractImportTest public void testExistProjectIsUpdated() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROJECTS_XLS)); + TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(sessionWorkspaceFilePath)); + // WHEN - TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_UPDATE))); + final String updateSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROJECTS_UPDATE)); + TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(updateSessionWorkspaceFilePath)); Project project = TestUtils.getProject(v3api, sessionToken, "TEST_PROJECT", "TEST_SPACE2"); + // THEN assertEquals(project.getCode(), "TEST_PROJECT"); assertEquals(project.getDescription(), "UPDATE"); @@ -98,19 +105,23 @@ public class ImportProjectsTest extends AbstractImportTest public void testProjectsAreCreatedSecondProject() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROJECTS_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN Project project = TestUtils.getProject(v3api, sessionToken, "TEST_PROJECT2"); + // THEN assertEquals(project.getCode(), "TEST_PROJECT2"); assertEquals(project.getDescription(), "description of another project"); assertEquals(project.getSpace().getCode(), "TEST_SPACE2"); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = "(?s).*Header 'Code' is missing.*") public void shouldThrowExceptionIfNoProjectCode() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_NO_CODE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROJECTS_NO_CODE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } @Test @@ -118,19 +129,23 @@ public class ImportProjectsTest extends AbstractImportTest public void testProjectsAreCreatedNoDescription() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_NO_DESCRIPTION))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROJECTS_NO_DESCRIPTION)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN Project project = TestUtils.getProject(v3api, sessionToken, "TEST_PROJECT"); + // THEN assertEquals(project.getCode(), "TEST_PROJECT"); assertEquals(project.getDescription(), null); assertEquals(project.getSpace().getCode(), "TEST_SPACE"); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = "(?s).*Header 'Space' is missing.*") public void shouldThrowExceptionIfNoProjectSpace() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_NO_SPACE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROJECTS_NO_SPACE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } @Test @@ -138,10 +153,16 @@ public class ImportProjectsTest extends AbstractImportTest public void testProjectsAreCreatedSpaceOnServer() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACES))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROJECTS_WITH_SPACES_ON_SERVER))); + final String spaceSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACES)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(spaceSessionWorkspaceFilePath)); + + final String projectsSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, PROJECTS_WITH_SPACES_ON_SERVER)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(projectsSessionWorkspaceFilePath)); + // WHEN Project project = TestUtils.getProject(v3api, sessionToken, "TEST_PROJECT"); + // THEN assertEquals(project.getCode(), "TEST_PROJECT"); assertEquals(project.getDescription(), "TEST"); diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportPropertyTypesTest.java b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportPropertyTypesTest.java index b7963f77f1f..fc35b51fa4b 100644 --- a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportPropertyTypesTest.java +++ b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportPropertyTypesTest.java @@ -17,6 +17,7 @@ package ch.ethz.sis.openbis.systemtest.plugin.excelimport; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; @@ -25,7 +26,6 @@ import java.nio.file.Paths; import java.util.Arrays; import org.apache.commons.io.FilenameUtils; -import org.python27.core.PyException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.Rollback; @@ -50,6 +50,7 @@ public class ImportPropertyTypesTest extends AbstractImportTest private IApplicationServerInternalApi v3api; private static final String PROPERTY_TYPES_XLS = "property_types/normal_property_type.xls"; + private static final String PROPERTY_TYPES_WITH_PATTERN_XLS = "property_types/normal_property_type_with_pattern.xls"; private static final String PROPERTY_NO_CODE = "property_types/no_code.xls"; @@ -74,7 +75,7 @@ public class ImportPropertyTypesTest extends AbstractImportTest public void setupClass() throws IOException { String f = ImportPropertyTypesTest.class.getName().replace(".", "/"); - FILES_DIR = f.substring(0, f.length() - ImportPropertyTypesTest.class.getSimpleName().length()) + "/test_files/"; + FILES_DIR = f.substring(0, f.length() - ImportPropertyTypesTest.class.getSimpleName().length()) + "test_files/"; } @Test @@ -85,9 +86,12 @@ public class ImportPropertyTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN PropertyType notes = TestUtils.getPropertyType(v3api, sessionToken, "NOTES"); + // THEN assertEquals(notes.getCode(), "NOTES"); assertEquals(notes.getLabel(), "Notes"); @@ -105,9 +109,13 @@ public class ImportPropertyTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_WITH_PATTERN_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_WITH_PATTERN_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN PropertyType notes = TestUtils.getPropertyType(v3api, sessionToken, "PATTERN_PATTERN"); + // THEN assertEquals(notes.getCode(), "PATTERN_PATTERN"); assertEquals(notes.getLabel(), "Pattern"); @@ -127,9 +135,12 @@ public class ImportPropertyTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN PropertyType notes = TestUtils.getPropertyType(v3api, sessionToken, "$INTERNAL_PROP"); + // THEN assertEquals(notes.getCode(), "$INTERNAL_PROP"); assertEquals(notes.getLabel(), "Name"); @@ -144,9 +155,13 @@ public class ImportPropertyTypesTest extends AbstractImportTest public void testDuplicatesPropertiesAreAllowedIfTheyAreTheSame() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_DUPLICATES_SAME))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_DUPLICATES_SAME)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN PropertyType notes = TestUtils.getPropertyType(v3api, sessionToken, "NOTES"); + // THEN assertEquals(notes.getCode(), "NOTES"); assertEquals(notes.getLabel(), "Notes"); @@ -157,76 +172,96 @@ public class ImportPropertyTypesTest extends AbstractImportTest assertNull(notes.getVocabulary()); } - @Test(expectedExceptions = RuntimeException.class) + @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "(?s).*Mandatory field is missing or empty: Code.*") public void testPropertyTypeNoCode() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_NO_CODE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROPERTY_NO_CODE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = "(?s).*Header 'Property label' is missing.*") public void testPropertyTypeNoLabel() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_NO_LABEL))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROPERTY_NO_LABEL)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, + expectedExceptionsMessageRegExp = "(?s).*Ambiguous property NOTES found, it has been declared before with different attributes.*") public void testPropertyTypesDuplicatesAreDifferent() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_DUPLICATES_DIFFERENT))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, PROPERTY_DUPLICATES_DIFFERENT)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } - @Test(expectedExceptions = RuntimeException.class) + @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "(?s).*Header 'Vocabulary code' is missing.*") public void testPropertyTypeNoVocabularyCodeWhenVocabularyType() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_VOCAB_TYPE_NO_VOCABULARY_CODE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, PROPERTY_VOCAB_TYPE_NO_VOCABULARY_CODE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } - @Test(expectedExceptions = RuntimeException.class) + @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "(?s).*Header 'Data type' is missing.*") public void testPropertyTypeNoDataType() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_NO_DATA_TYPE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROPERTY_NO_DATA_TYPE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = "(?s).*Header 'Description' is missing.*") public void testPropertyTypeNoDescription() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_NO_DESCRIPTION))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROPERTY_NO_DESCRIPTION)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = "(?s).*Entity \\[DETECTION\\] could not be found..*") public void testPropertyTypeVocabularyCodeToNonVocabularyType() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_NON_VOCAB_TYPE_VOCABULARY_CODE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, PROPERTY_NON_VOCAB_TYPE_VOCABULARY_CODE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } - @Test(expectedExceptions = Exception.class) + @Test/*(expectedExceptions = Exception.class)*/ @DirtiesContext public void deleteProjectFromDBButNotFromJSON() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS))); + // the Excel contains internally property types which can be only manipulated by the system user + sessionToken = v3api.loginAsSystem(); + + final String sessionWorkspaceFilePath1 = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, UpdateMode.FAIL_IF_EXISTS, Paths.get(sessionWorkspaceFilePath1)); PropertyType type = TestUtils.getPropertyType(v3api, sessionToken, "$INTERNAL_PROP"); + assertNotNull(type); PropertyTypeDeletionOptions deletionOptions = new PropertyTypeDeletionOptions(); deletionOptions.setReason("test"); v3api.deletePropertyTypes(sessionToken, Arrays.asList(type.getPermId()), deletionOptions); // After deleting one property, the exception is not thrown. // Because it can be deleted by the user an DB is fine. - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS))); + final String sessionWorkspaceFilePath2 = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath2)); // remove all data from DB type = TestUtils.getPropertyType(v3api, sessionToken, "$INTERNAL_PROP"); + assertNotNull(type); deletionOptions = new PropertyTypeDeletionOptions(); deletionOptions.setReason("test"); v3api.deletePropertyTypes(sessionToken, Arrays.asList(type.getPermId()), deletionOptions); type = TestUtils.getPropertyType(v3api, sessionToken, "NOTES"); + assertNotNull(type); deletionOptions = new PropertyTypeDeletionOptions(); deletionOptions.setReason("test"); v3api.deletePropertyTypes(sessionToken, Arrays.asList(type.getPermId()), deletionOptions); // exception should be thrown because DB is empty. - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS))); + final String sessionWorkspaceFilePath3 = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PROPERTY_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath3)); } } diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSampleTypesTest.java b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSampleTypesTest.java index 4f37c6bd3f3..e2a4cf0ee6a 100644 --- a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSampleTypesTest.java +++ b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSampleTypesTest.java @@ -72,7 +72,7 @@ public class ImportSampleTypesTest extends AbstractImportTest public void setupClass() throws IOException { String f = ImportSampleTypesTest.class.getName().replace(".", "/"); - FILES_DIR = f.substring(0, f.length() - ImportSampleTypesTest.class.getSimpleName().length()) + "/test_files/"; + FILES_DIR = f.substring(0, f.length() - ImportSampleTypesTest.class.getSimpleName().length()) + "test_files/"; } @Test @@ -83,9 +83,12 @@ public class ImportSampleTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN SampleType antibody = TestUtils.getSampleType(v3api, sessionToken, "ANTIBODY"); + // THEN assertFalse(antibody.isAutoGeneratedCode()); } @@ -98,9 +101,12 @@ public class ImportSampleTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN SampleType antibody = TestUtils.getSampleType(v3api, sessionToken, "ANTIBODY"); + // THEN boolean allMandatory = antibody.getPropertyAssignments().stream().allMatch(propAssignment -> propAssignment.isMandatory() == true); boolean allShownInEditView = @@ -125,9 +131,13 @@ public class ImportSampleTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_XLS_DIFFERENT_PROPERTY_ASSIGN))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_XLS_DIFFERENT_PROPERTY_ASSIGN)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN SampleType antibody = TestUtils.getSampleType(v3api, sessionToken, "ANTIBODY"); + // THEN boolean allNotMandatory = antibody.getPropertyAssignments().stream().allMatch(propAssignment -> propAssignment.isMandatory() == false); boolean allNotShownInEditView = @@ -146,9 +156,12 @@ public class ImportSampleTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN SampleType antibody = TestUtils.getSampleType(v3api, sessionToken, "ANTIBODY"); + // THEN boolean namePropertyExists = antibody.getPropertyAssignments().stream().anyMatch(propAssignment -> propAssignment.getPropertyType().getCode().equals("$NAME")); @@ -171,15 +184,18 @@ public class ImportSampleTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, TestUtils.getDynamicPluginMap(), - Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_DYNAMIC_SCRIPT))); + final String[] sessionWorkspaceFilePaths = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_DYNAMIC_SCRIPT), FilenameUtils.concat(FILES_DIR, DYNAMIC_SCRIPT)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePaths[0])); + // WHEN SampleType antibody = TestUtils.getSampleType(v3api, sessionToken, "ANTIBODY"); + // THEN - Plugin dynamicScript = antibody.getPropertyAssignments().get(0).getPlugin(); + final Plugin dynamicScript = antibody.getPropertyAssignments().get(0).getPlugin(); assertNotNull(dynamicScript); assertEquals(dynamicScript.getName().toUpperCase(), "$NAME.DYNAMIC"); - assertEquals(dynamicScript.getScript(), TestUtils.getDynamicScript()); + assertEquals(dynamicScript.getScript(), "def calculate():\n return 1"); assertEquals(dynamicScript.getPluginType(), PluginType.DYNAMIC_PROPERTY); } @@ -188,15 +204,18 @@ public class ImportSampleTypesTest extends AbstractImportTest public void testSampleTypesWithPropertyHavingValidationScript() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, TestUtils.getValidationPluginMap(), - Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_VALIDATION_SCRIPT))); + final String[] sessionWorkspaceFilePaths = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_VALIDATION_SCRIPT), FilenameUtils.concat(FILES_DIR, VALIDATION_SCRIPT)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePaths[0])); + // WHEN SampleType antibody = TestUtils.getSampleType(v3api, sessionToken, "ANTIBODY"); + // THEN Plugin validationScript = antibody.getValidationPlugin(); assertNotNull(validationScript); assertEquals(validationScript.getName().toUpperCase(), "ANTIBODY.VALID"); - assertEquals(validationScript.getScript(), TestUtils.getValidationScript()); + assertEquals(validationScript.getScript(), "def validate(entity, isNew):\n if isNew:\n return"); assertEquals(validationScript.getPluginType(), PluginType.ENTITY_VALIDATION); } @@ -205,9 +224,13 @@ public class ImportSampleTypesTest extends AbstractImportTest public void testSampleTypesWithVocabularyInXls() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_VOCABULARY))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_VOCABULARY)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN SampleType antibody = TestUtils.getSampleType(v3api, sessionToken, "ANTIBODY"); + // THEN PropertyAssignment propertyAssignment = antibody.getPropertyAssignments().get(0); assertNotNull(propertyAssignment); @@ -219,10 +242,17 @@ public class ImportSampleTypesTest extends AbstractImportTest public void testSampleTypesWithVocabularyOnServer() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARY_DETECTION))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_VOCABULARY_ON_SERVER))); + final String vocabularySessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, VOCABULARY_DETECTION)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(vocabularySessionWorkspaceFilePath)); + + final String sampleTypesSessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_VOCABULARY_ON_SERVER)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sampleTypesSessionWorkspaceFilePath)); + // WHEN SampleType antibody = TestUtils.getSampleType(v3api, sessionToken, "ANTIBODY"); + // THEN PropertyAssignment propertyAssignment = antibody.getPropertyAssignments().get(0); assertNotNull(propertyAssignment); @@ -234,17 +264,22 @@ public class ImportSampleTypesTest extends AbstractImportTest public void testSampleTypesWithAutoGeneratedCodeAttribute() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_AUTO_GENERATED_CODES))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLE_TYPES_WITH_AUTO_GENERATED_CODES)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN SampleType antibody = TestUtils.getSampleType(v3api, sessionToken, "SECONDBODY"); + // THEN assertTrue(antibody.isAutoGeneratedCode()); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = "(s?).*Mandatory field is missing or empty: Code.*") public void shouldThrowExceptionIfNoSampleCode() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPE_NO_CODE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SAMPLE_TYPE_NO_CODE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } } diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSamplesTest.java b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSamplesTest.java index f92702dfa16..55567f48535 100644 --- a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSamplesTest.java +++ b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSamplesTest.java @@ -15,15 +15,15 @@ */ package ch.ethz.sis.openbis.systemtest.plugin.excelimport; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + import java.io.IOException; import java.nio.file.Paths; import java.util.HashSet; import java.util.List; import java.util.Set; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.ISampleId; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SampleIdentifier; import org.apache.commons.io.FilenameUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; @@ -33,15 +33,16 @@ import org.springframework.transaction.annotation.Transactional; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.Experiment; import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.Project; import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.ISampleId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SampleIdentifier; import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.Space; import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi; import ch.systemsx.cisd.common.exceptions.UserFailureException; -import static org.testng.Assert.*; - @ContextConfiguration(locations = "classpath:applicationContext.xml") @Transactional(transactionManager = "transaction-manager") @Rollback @@ -109,9 +110,12 @@ public class ImportSamplesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_XLS))); + final String sessionWorkspaceFile = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SAMPLES_XLS)); + TestUtils.createFrom(v3api, sessionToken, UpdateMode.FAIL_IF_EXISTS, Paths.get(sessionWorkspaceFile)); + // WHEN - Sample sample = TestUtils.getSample(v3api, sessionToken, "AAA", "TEST_SPACE"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "TEST_SPACE", "AAA"); + // THEN assertEquals(sample.getCode(), "AAA"); assertEquals(sample.getProject(), null); @@ -127,9 +131,10 @@ public class ImportSamplesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SAMPLES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); // WHEN - Sample sample = TestUtils.getSample(v3api, sessionToken, "VVV", "TEST_SPACE"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "TEST_SPACE", "VVV"); // THEN assertEquals(sample.getCode(), "VVV"); assertEquals(sample.getProject(), null); @@ -145,9 +150,10 @@ public class ImportSamplesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SAMPLES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); // WHEN - Sample sample = TestUtils.getSample(v3api, sessionToken, "S1", "TEST_SPACE"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "TEST_SPACE", "S1"); // THEN assertEquals(sample.getCode(), "S1"); assertEquals(sample.getProject(), null); @@ -163,10 +169,13 @@ public class ImportSamplesTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SPACE_ELSEWHERE))); + final String sessionWorkspaceFilePathForSpace = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePathForSpace)); + final String sessionWorkspaceFilePathForSamplesSpaceElsewhere = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLES_SPACE_ELSEWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePathForSamplesSpaceElsewhere)); // WHEN - Sample sample = TestUtils.getSample(v3api, sessionToken, "VVV", "TEST_SPACE"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "TEST_SPACE", "VVV"); // THEN assertNotNull(sample); } @@ -179,19 +188,28 @@ public class ImportSamplesTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, - Paths.get(FilenameUtils.concat(FILES_DIR, SPACE)), - Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SPACE_ELSEWHERE))); + final String sessionWorkspaceFilePathForSpace = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACE)); + final String sessionWorkspaceFilePathForSamplesSpaceElsewhere = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLES_SPACE_ELSEWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePathForSpace), + Paths.get(sessionWorkspaceFilePathForSamplesSpaceElsewhere)); // WHEN - Sample sample = TestUtils.getSample(v3api, sessionToken, "VVV", "TEST_SPACE"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "TEST_SPACE", "VVV"); // THEN assertNotNull(sample); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, + expectedExceptionsMessageRegExp = "(s?).*Entity \\[TEST_SPACE\\] could not be found. " + + "Either you forgot to register it or mistyped the identifier.*") public void shouldThrowExceptionIfSpaceDoesntExist() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SPACE_ELSEWHERE))); + // the Excel contains internally property types which can be only manipulated by the system user + sessionToken = v3api.loginAsSystem(); + + final String sessionWorkspaceFilePathForSamplesSpaceElsewhere = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLES_SPACE_ELSEWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePathForSamplesSpaceElsewhere)); } @Test @@ -202,10 +220,17 @@ public class ImportSamplesTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, - Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARY_TYPE)), - Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPE_CYCLIC))); - List<IObjectId> ids = TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SAMPLE_TYPE_ELSWHERE_CYCLIC))); + final String sessionWorkspaceFilePathForVocabularyType = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, VOCABULARY_TYPE)); + final String sessionWorkspaceFilePathForSampleType = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLE_TYPE_CYCLIC)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePathForVocabularyType), + Paths.get(sessionWorkspaceFilePathForSampleType)); + + final String sessionWorkspaceFilePathForSampleTypeElsewhere = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLES_SAMPLE_TYPE_ELSWHERE_CYCLIC)); + List<IObjectId> ids = TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, + Paths.get(sessionWorkspaceFilePathForSampleTypeElsewhere)); List<ISampleId> sampleIds = List.of((SampleIdentifier)ids.get(9), (SampleIdentifier)ids.get(10), (SampleIdentifier)ids.get(11)); // WHEN List<Sample> samples = (List<Sample>) TestUtils.getSamplesById(v3api, sessionToken, sampleIds); @@ -226,13 +251,22 @@ public class ImportSamplesTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN + final String sessionWorkspaceFilePathForVocabularyType = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, VOCABULARY_TYPE)); + final String sessionWorkspaceFilePathForSampleType = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLE_TYPE_CYCLIC_FIX_TYPE)); + TestUtils.createFrom(v3api, sessionToken, - Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARY_TYPE)), - Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPE_CYCLIC_FIX_TYPE))); - List<IObjectId> ids = TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SAMPLE_TYPE_ELSWHERE_CYCLIC))); + Paths.get(sessionWorkspaceFilePathForVocabularyType), + Paths.get(sessionWorkspaceFilePathForSampleType)); + + final String sessionWorkspaceFilePathForSampleTypeElsewhere = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLES_SAMPLE_TYPE_ELSWHERE_CYCLIC)); + List<IObjectId> ids = TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, + Paths.get(sessionWorkspaceFilePathForSampleTypeElsewhere)); List<ISampleId> sampleIds = List.of((SampleIdentifier)ids.get(9), (SampleIdentifier)ids.get(10), (SampleIdentifier)ids.get(11)); // WHEN - List<Sample> samples = (List<Sample>) TestUtils.getSamplesById(v3api, sessionToken, sampleIds); + List<Sample> samples = TestUtils.getSamplesById(v3api, sessionToken, sampleIds); Set<String> differentCyclicAssignments = new HashSet<>(); for (Sample sample:samples) { differentCyclicAssignments.add((String)sample.getProperty("CYCLIC_SAMPLE_PROPERTY")); @@ -242,7 +276,8 @@ public class ImportSamplesTest extends AbstractImportTest assertEquals(differentCyclicAssignments.size(), 2); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, + expectedExceptionsMessageRegExp = "(?s).*Property CYCLIC_SAMPLE_PROPERTY is not a sample of type ANTIBODY but of type TEST_TYPE.*") @DirtiesContext public void testSamplesAreCreatedWhenSampleTypeCyclicOnServerFixTypeWrongType() throws IOException { @@ -250,10 +285,18 @@ public class ImportSamplesTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN + final String sessionWorkspaceFilePathForVocabularyType = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, VOCABULARY_TYPE)); + final String sessionWorkspaceFilePathForSampleType = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLE_TYPE_CYCLIC_FIX_TYPE)); + TestUtils.createFrom(v3api, sessionToken, - Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARY_TYPE)), - Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPE_CYCLIC_FIX_TYPE))); - List<IObjectId> ids = TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SAMPLE_TYPE_ELSWHERE_CYCLIC_WRONG_TYPE))); + Paths.get(sessionWorkspaceFilePathForVocabularyType), + Paths.get(sessionWorkspaceFilePathForSampleType)); + + final String sessionWorkspaceFilePathForSampleTypeElsewhere = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLES_SAMPLE_TYPE_ELSWHERE_CYCLIC_WRONG_TYPE)); + TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(sessionWorkspaceFilePathForSampleTypeElsewhere)); } @Test @@ -264,12 +307,19 @@ public class ImportSamplesTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN + final String sessionWorkspaceFilePathForVocabularyType = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, VOCABULARY_TYPE)); + final String sessionWorkspaceFilePathForSampleType = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLE_TYPE)); TestUtils.createFrom(v3api, sessionToken, - Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARY_TYPE)), - Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPE))); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SAMPLE_TYPE_ELSWHERE))); + Paths.get(sessionWorkspaceFilePathForVocabularyType), + Paths.get(sessionWorkspaceFilePathForSampleType)); + + final String sessionWorkspaceFilePathForSampleTypeElsewhere = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLES_SAMPLE_TYPE_ELSWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePathForSampleTypeElsewhere)); // WHEN - Sample sample = TestUtils.getSample(v3api, sessionToken, "VVV", "TEST_SPACE"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "TEST_SPACE", "VVV"); // THEN assertNotNull(sample); } @@ -282,11 +332,14 @@ public class ImportSamplesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, - Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLE_TYPE)), - Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SAMPLE_TYPE_ELSWHERE))); + final String sessionWorkspaceFilePathForSampleType = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLE_TYPE)); + final String sessionWorkspaceFilePathForSampleTypeElsewhere = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLES_SAMPLE_TYPE_ELSWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePathForSampleType), + Paths.get(sessionWorkspaceFilePathForSampleTypeElsewhere)); // WHEN - Sample sample = TestUtils.getSample(v3api, sessionToken, "VVV", "TEST_SPACE"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "TEST_SPACE", "VVV"); // THEN assertNotNull(sample); } @@ -299,10 +352,10 @@ public class ImportSamplesTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, - Paths.get(FilenameUtils.concat(FILES_DIR, CHILD_AS_IDENTIFIER))); + final String sessionWorkspaceFile = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, CHILD_AS_IDENTIFIER)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFile)); // WHEN - Sample sample = TestUtils.getSample(v3api, sessionToken, "VVV", "TEST_SPACE"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "TEST_SPACE", "VVV"); // THEN assertNotNull(sample); assertEquals(sample.getChildren().size(), 1); @@ -319,10 +372,10 @@ public class ImportSamplesTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, - Paths.get(FilenameUtils.concat(FILES_DIR, PARENT_AS_IDENTIFIER))); + final String sessionWorkspaceFile = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PARENT_AS_IDENTIFIER)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFile)); // WHEN - Sample sample = TestUtils.getSample(v3api, sessionToken, "VVV", "TEST_SPACE"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "TEST_SPACE", "VVV"); // THEN assertNotNull(sample); assertEquals(sample.getParents().size(), 1); @@ -339,10 +392,10 @@ public class ImportSamplesTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, - Paths.get(FilenameUtils.concat(FILES_DIR, CHILD_AS_DOLLARTAG))); + final String sessionWorkspaceFile = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, CHILD_AS_DOLLARTAG)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFile)); // WHEN - Sample sample = TestUtils.getSample(v3api, sessionToken, "VVV", "TEST_SPACE"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "TEST_SPACE", "VVV"); // THEN assertNotNull(sample); assertEquals(sample.getChildren().size(), 1); @@ -359,10 +412,10 @@ public class ImportSamplesTest extends AbstractImportTest String sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, - Paths.get(FilenameUtils.concat(FILES_DIR, PARENT_AS_DOLLARTAG))); + final String sessionWorkspaceFile = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, PARENT_AS_DOLLARTAG)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFile)); // WHEN - Sample sample = TestUtils.getSample(v3api, sessionToken, "VVV", "TEST_SPACE"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "TEST_SPACE", "VVV"); // THEN assertNotNull(sample); assertEquals(sample.getParents().size(), 1); @@ -379,10 +432,10 @@ public class ImportSamplesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, - Paths.get(FilenameUtils.concat(FILES_DIR, NON_MANDATORY_FIELD_MISSING))); + final String sessionWorkspaceFile = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, NON_MANDATORY_FIELD_MISSING)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFile)); // WHEN - Sample sample = TestUtils.getSample(v3api, sessionToken, "AAA", "TEST_SPACE"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "TEST_SPACE", "AAA"); // THEN assertNotNull(sample); assertEquals(sample.getProperties().get("FOR_WHAT"), null); @@ -396,9 +449,9 @@ public class ImportSamplesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - - List<IObjectId> result = TestUtils.createFrom(v3api, sessionToken, UpdateMode.IGNORE_EXISTING, - Paths.get(FilenameUtils.concat(FILES_DIR, AUTO_GENERATED_SAMPLE_LEVEL))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, AUTO_GENERATED_SAMPLE_LEVEL)); + List<IObjectId> result = TestUtils.createFrom(v3api, sessionToken, UpdateMode.IGNORE_EXISTING, Paths.get(sessionWorkspaceFilePath)); String permId = result.get(result.size() - 1).toString(); // WHEN @@ -416,8 +469,9 @@ public class ImportSamplesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - List<IObjectId> result = TestUtils.createFrom(v3api, sessionToken, UpdateMode.IGNORE_EXISTING, - Paths.get(FilenameUtils.concat(FILES_DIR, AUTO_GENERATED_SAMPLE_TYPE_LEVEL))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, AUTO_GENERATED_SAMPLE_TYPE_LEVEL)); + List<IObjectId> result = TestUtils.createFrom(v3api, sessionToken, UpdateMode.IGNORE_EXISTING, Paths.get(sessionWorkspaceFilePath)); String permId = result.get(result.size() - 1).toString(); // WHEN @@ -434,10 +488,11 @@ public class ImportSamplesTest extends AbstractImportTest // the Excel contains internally managed property types which can be only manipulated by the system user sessionToken = v3api.loginAsSystem(); - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, GENERAL_ELN_SETTINGS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, GENERAL_ELN_SETTINGS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); // test sample before update - Sample sample = TestUtils.getSample(v3api, sessionToken, "GENERAL_ELN_SETTINGS", "ELN_SETTINGS"); + Sample sample = TestUtils.getSample(v3api, sessionToken, "ELN_SETTINGS", "GENERAL_ELN_SETTINGS"); assertNotNull(sample); // properties are empty assertEquals(sample.getProperties().size(), 0); @@ -456,11 +511,12 @@ public class ImportSamplesTest extends AbstractImportTest assertEquals(experiment.getProperties().containsKey("$NAME"), true); assertEquals(experiment.getProperties().get("$NAME"), "Default Experiment"); - TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, - Paths.get(FilenameUtils.concat(FILES_DIR, GENERAL_ELN_SETTINGS_UPDATE))); + final String sessionWorkspaceFilePathForUpdate = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, GENERAL_ELN_SETTINGS_UPDATE)); + TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(sessionWorkspaceFilePathForUpdate)); // test sample after update - sample = TestUtils.getSample(v3api, sessionToken, "GENERAL_ELN_SETTINGS", "ELN_SETTINGS"); + sample = TestUtils.getSample(v3api, sessionToken, "ELN_SETTINGS", "GENERAL_ELN_SETTINGS"); assertNotNull(sample); // properties have been updated assertEquals(sample.getProperties().size(), 1); @@ -483,16 +539,28 @@ public class ImportSamplesTest extends AbstractImportTest assertEquals(experiment.getProperties().get("$NAME"), "Default Experiment Updated"); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, + expectedExceptionsMessageRegExp = "(s?).*Entity \\[TEST_SPACE, /TEST_SPACE/TEST_PROJECT/TEST_EXPERIMENT\\] could not be found. " + + "Either you forgot to register it or mistyped the identifier.*") public void shouldThrowExceptionIfSamplesSpaceProjectDoesntExist() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SAMPLES_SPACE_PROJECT_EXPERIMENT_ELSEWHERE))); + // the Excel contains internally property types which can be only manipulated by the system user + sessionToken = v3api.loginAsSystem(); + + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, SAMPLES_SPACE_PROJECT_EXPERIMENT_ELSEWHERE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, + expectedExceptionsMessageRegExp = "(s?).*Header 'name' is neither an attribute, property code or property label.*") public void shouldThrowExceptionIfMandatoryPropertyIsMissing() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, MANDATORY_FIELD_MISSING))); + // the Excel contains internally property types which can be only manipulated by the system user + sessionToken = v3api.loginAsSystem(); + + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, MANDATORY_FIELD_MISSING)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } } diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSpacesTest.java b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSpacesTest.java index 75fb6f688e4..8024fecf89e 100644 --- a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSpacesTest.java +++ b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportSpacesTest.java @@ -53,7 +53,7 @@ public class ImportSpacesTest extends AbstractImportTest public void setupClass() throws IOException { String f = ImportSpacesTest.class.getName().replace(".", "/"); - FILES_DIR = f.substring(0, f.length() - ImportSpacesTest.class.getSimpleName().length()) + "/test_files/"; + FILES_DIR = f.substring(0, f.length() - ImportSpacesTest.class.getSimpleName().length()) + "test_files/"; } @Test @@ -61,7 +61,8 @@ public class ImportSpacesTest extends AbstractImportTest public void testNormalSpacesAreCreated() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); // WHEN Space rawData = TestUtils.getSpace(v3api, sessionToken, "TEST_SPACE"); // THEN @@ -74,7 +75,8 @@ public class ImportSpacesTest extends AbstractImportTest public void testNormalSpacesAreCreatedSecondSpace() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); // WHEN Space space = TestUtils.getSpace(v3api, sessionToken, "TEST_SPACE2"); // THEN @@ -82,17 +84,19 @@ public class ImportSpacesTest extends AbstractImportTest assertEquals(space.getDescription(), "TEST desc"); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = "(?s).*Header 'Code' is missing.*") public void shouldThrowExceptionIfNoSpaceCode() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACES_NO_CODE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACES_NO_CODE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } - @Test(expectedExceptions = UserFailureException.class) + @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = "(?s).*Header 'Description' is missing.*") @DirtiesContext public void shouldCreateSpaceWhenNoDescription() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, SPACES_NO_DESCRIPTION))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, SPACES_NO_DESCRIPTION)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } } diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportVocabularyTypesTest.java b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportVocabularyTypesTest.java index 3b10cf00fe9..707d1ab1ef3 100644 --- a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportVocabularyTypesTest.java +++ b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/ImportVocabularyTypesTest.java @@ -40,7 +40,6 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.create.VocabularyTerm import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.fetchoptions.VocabularyFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.VocabularyPermId; import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi; -import ch.systemsx.cisd.common.exceptions.UserFailureException; @ContextConfiguration(locations = "classpath:applicationContext.xml") @Transactional(transactionManager = "transaction-manager") @@ -75,7 +74,7 @@ public class ImportVocabularyTypesTest extends AbstractImportTest public void setupClass() throws IOException { String f = ImportVocabularyTypesTest.class.getName().replace(".", "/"); - FILES_DIR = f.substring(0, f.length() - ImportVocabularyTypesTest.class.getSimpleName().length()) + "/test_files/"; + FILES_DIR = f.substring(0, f.length() - ImportVocabularyTypesTest.class.getSimpleName().length()) + "test_files/"; } @Test @@ -86,7 +85,8 @@ public class ImportVocabularyTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_TYPES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, VOCABULARIES_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); // WHEN Vocabulary detection = TestUtils.getVocabulary(v3api, sessionToken, "DETECTION"); // THEN @@ -102,9 +102,12 @@ public class ImportVocabularyTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_TYPES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, VOCABULARIES_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN Vocabulary detection = TestUtils.getVocabulary(v3api, sessionToken, "DETECTION"); + // THEN VocabularyTerm term = detection.getTerms().get(0); assertEquals(term.getCode(), "HRP"); @@ -120,9 +123,12 @@ public class ImportVocabularyTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_TYPES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, VOCABULARIES_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN List<Vocabulary> vocabularies = TestUtils.getAllVocabularies(v3api, sessionToken); + // THEN assertEquals(vocabularies.size(), 3); // 2 created + 1 default } @@ -135,9 +141,12 @@ public class ImportVocabularyTypesTest extends AbstractImportTest sessionToken = v3api.loginAsSystem(); // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_TYPES_XLS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, VOCABULARIES_TYPES_XLS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN Vocabulary detection = TestUtils.getVocabulary(v3api, sessionToken, "DETECTION"); + // THEN VocabularyTerm term = detection.getTerms().get(1); assertEquals(term.getCode(), "TEST_VOC"); @@ -150,24 +159,30 @@ public class ImportVocabularyTypesTest extends AbstractImportTest public void testVocabularyWithNoTermDescriptionShouldBeCreated() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_TERM_DESCRIPTION))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_TERM_DESCRIPTION)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN Vocabulary detection = TestUtils.getVocabulary(v3api, sessionToken, "DETECTION"); + // THEN assertNotNull(detection); assertNull(detection.getTerms().get(0).getDescription()); } - @Test(expectedExceptions = RuntimeException.class) + @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "(s?).*Mandatory field is missing or empty: Code.*") public void shouldThrowExceptionIfNoVocabularyCode() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_CODE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_CODE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } - @Test(expectedExceptions = RuntimeException.class) + @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "(s?).*Mandatory field is missing or empty: Code.*") public void shouldThrowExceptionIfNoTermCode() throws IOException { - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_TERM_CODE))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_TERM_CODE)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); } @Test @@ -175,9 +190,13 @@ public class ImportVocabularyTypesTest extends AbstractImportTest public void shouldNotThrowExceptionIfNoVocabularyDescription() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_DESCRIPTION))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, + FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_DESCRIPTION)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN Vocabulary detection = TestUtils.getVocabulary(v3api, sessionToken, "DETECTION"); + // THEN assertNotNull(detection); assertNull(detection.getDescription()); @@ -188,9 +207,12 @@ public class ImportVocabularyTypesTest extends AbstractImportTest public void shouldNotThrowExceptionIfNoTermLabel() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_TERM_LABEL))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_TERM_LABEL)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN Vocabulary detection = TestUtils.getVocabulary(v3api, sessionToken, "DETECTION"); + // THEN assertNotNull(detection); assertNull(detection.getTerms().get(0).getLabel()); @@ -201,9 +223,12 @@ public class ImportVocabularyTypesTest extends AbstractImportTest public void shouldNotThrowExceptionIfNoTerms() throws IOException { // GIVEN - TestUtils.createFrom(v3api, sessionToken, Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_TERMS))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, VOCABULARIES_NO_TERMS)); + TestUtils.createFrom(v3api, sessionToken, Paths.get(sessionWorkspaceFilePath)); + // WHEN Vocabulary detection = TestUtils.getVocabulary(v3api, sessionToken, "DETECTION"); + // THEN assertNotNull(detection); } @@ -214,7 +239,8 @@ public class ImportVocabularyTypesTest extends AbstractImportTest { TestUtils.createVocabulary(v3api, sessionToken, "TEST_VOCABULARY_TYPE", "Test desc"); // there should be no exceptions - TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(FilenameUtils.concat(FILES_DIR, EXIST_VOCABULARIES))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(sessionToken, FilenameUtils.concat(FILES_DIR, EXIST_VOCABULARIES)); + TestUtils.createFrom(v3api, sessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(sessionWorkspaceFilePath)); Vocabulary test = TestUtils.getVocabulary(v3api, sessionToken, "TEST_VOCABULARY_TYPE"); assertNotNull(test); } @@ -250,8 +276,9 @@ public class ImportVocabularyTypesTest extends AbstractImportTest assertEquals(beforeTerm.getDescription(), "Original Description"); assertEquals(beforeTerm.getRegistrator().getUserId(), TEST_USER); - TestUtils.createFrom(v3api, systemSessionToken, UpdateMode.UPDATE_IF_EXISTS, - Paths.get(FilenameUtils.concat(FILES_DIR, VOCABULARY_WITH_TERM_TO_TAKE_OVER))); + final String sessionWorkspaceFilePath = uploadToAsSessionWorkspace(systemSessionToken, + FilenameUtils.concat(FILES_DIR, VOCABULARY_WITH_TERM_TO_TAKE_OVER)); + TestUtils.createFrom(v3api, systemSessionToken, UpdateMode.UPDATE_IF_EXISTS, Paths.get(sessionWorkspaceFilePath)); Vocabulary afterVocabulary = v3api.getVocabularies(instanceAdminSessionToken, Arrays.asList(vocabularyId), fetchOptions).get(vocabularyId); VocabularyTerm afterTerm = afterVocabulary.getTerms().get(0); diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/TestUtils.java b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/TestUtils.java index adbb3a91ba4..8681e8e9cb3 100644 --- a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/TestUtils.java +++ b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/TestUtils.java @@ -15,23 +15,15 @@ */ package ch.ethz.sis.openbis.systemtest.plugin.excelimport; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.create.VocabularyCreation; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; - import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IEntityType; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSetType; @@ -44,6 +36,11 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.fetchoptions.Experime import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id.ExperimentIdentifier; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id.IExperimentId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.search.ExperimentTypeSearchCriteria; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportFormat; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportMode; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.Project; import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.fetchoptions.ProjectFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.search.ProjectSearchCriteria; @@ -60,12 +57,11 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.ISampleId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SampleIdentifier; import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SamplePermId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.search.SampleTypeSearchCriteria; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.service.CustomASServiceExecutionOptions; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.service.id.CustomASServiceCode; import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.Space; import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.fetchoptions.SpaceFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.search.SpaceSearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.Vocabulary; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.create.VocabularyCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.fetchoptions.VocabularyFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.VocabularyPermId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.search.VocabularySearchCriteria; @@ -73,19 +69,6 @@ import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi public class TestUtils { - private static final String TEST_XLS = "TEST-XLS"; - - private static final String XLS_NAME = "xls_name"; - - private static final String UPDATE_MODE = "update_mode"; - - private static final String XLS_PARAM = "xls"; - - public static final String CSV_PARAM = "csv"; - - private static final String SCRIPTS_PARAM = "scripts"; - - private static final String XLS_IMPORT_API = "xls-import-api"; static Vocabulary getVocabulary(IApplicationServerInternalApi v3api, String sessionToken, String code) { @@ -276,7 +259,7 @@ public class TestUtils } } - static Sample getSample(IApplicationServerInternalApi v3api, String sessionToken, String sampleCode, String spaceCode) + static Sample getSample(IApplicationServerInternalApi v3api, String sessionToken, String spaceCode, String sampleCode) { List<ISampleId> ids = new ArrayList<>(); ids.add(new SampleIdentifier(spaceCode, null, null, sampleCode)); @@ -351,83 +334,11 @@ public class TestUtils static List<IObjectId> createFrom(IApplicationServerInternalApi v3api, String sessionToken, UpdateMode updateMode, Path... xls_paths) throws IOException { - List<byte[]> excels = new ArrayList<>(); - for (Path xls_path : xls_paths) - { - byte[] xls = readData(xls_path); - excels.add(xls); - } - CustomASServiceExecutionOptions options = new CustomASServiceExecutionOptions(); - options.withParameter(XLS_PARAM, excels); - options.withParameter(UPDATE_MODE, updateMode.name()); - options.withParameter(XLS_NAME, TEST_XLS); - return (List<IObjectId>) v3api.executeCustomASService(sessionToken, new CustomASServiceCode(XLS_IMPORT_API), options); - } - - static String createFromCsv(IApplicationServerInternalApi v3api, String sessionToken, Path... csv_paths) throws IOException - { - List<byte[]> csvs = new ArrayList<>(); - for (Path csv_path : csv_paths) - { - byte[] csv = readData(csv_path); - csvs.add(csv); - } - CustomASServiceExecutionOptions options = new CustomASServiceExecutionOptions(); - options.withParameter(CSV_PARAM, csvs); - options.withParameter(UPDATE_MODE, UpdateMode.IGNORE_EXISTING.name()); - options.withParameter(XLS_NAME, TEST_XLS); - return v3api.executeCustomASService(sessionToken, new CustomASServiceCode(XLS_IMPORT_API), options).toString(); - } - - static String createFrom(IApplicationServerInternalApi v3api, String sessionToken, Map<String, String> scripts, Path... xls_paths) - throws IOException - { - return TestUtils.createFrom(v3api, sessionToken, scripts, UpdateMode.IGNORE_EXISTING, xls_paths); - } - - static String createFrom(IApplicationServerInternalApi v3api, String sessionToken, Map<String, String> scripts, UpdateMode updateMode, - Path... xls_paths) throws IOException - { - List<byte[]> excels = new ArrayList<>(); - for (Path xls_path : xls_paths) - { - byte[] xls = readData(xls_path); - excels.add(xls); - } - CustomASServiceExecutionOptions options = new CustomASServiceExecutionOptions(); - options.withParameter(XLS_PARAM, excels); - options.withParameter(SCRIPTS_PARAM, scripts); - options.withParameter(UPDATE_MODE, updateMode.name()); - options.withParameter(XLS_NAME, TEST_XLS); - return v3api.executeCustomASService(sessionToken, new CustomASServiceCode(XLS_IMPORT_API), options).toString(); - } - - static String getValidationScript() - { - return "def validate(entity, isNew):\n if isNew:\n return"; - } + final String[] sessionWorkspaceFiles = Arrays.stream(xls_paths).map(Path::toString).toArray(String[]::new); + final ImportResult importResult = v3api.executeImport(sessionToken, new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles), + new ImportOptions(ImportMode.valueOf(updateMode.name()))); - static String getDynamicScript() - { - return "def calculate():\n return 1"; - } - - static Map<String, String> getValidationPluginMap() - { - String dynamicScriptString = getValidationScript(); - Map<String, String> scriptsMap = new HashMap<>(); - scriptsMap.put("valid.py", dynamicScriptString); - - return scriptsMap; - } - - static Map<String, String> getDynamicPluginMap() - { - String dynamicScriptString = getDynamicScript(); - Map<String, String> scriptsMap = new HashMap<>(); - scriptsMap.put("dynamic/dynamic.py", dynamicScriptString); - - return scriptsMap; + return importResult.getObjectIds(); } static VocabularyPermId createVocabulary(IApplicationServerInternalApi v3api, String sessionToken, String code, String description) @@ -447,17 +358,6 @@ public class TestUtils } } - static String extractSamplePermIdFromResults(String result) - { - // Note this will work only if we created single sample!! - int indexOfSamples = result.indexOf("CreateSamplesOperationResult"); - int permIdStart = indexOfSamples + "CreateSamplesOperationResult".length(); - int permIdEnd = result.indexOf("]", indexOfSamples); - String permId = result.substring(permIdStart, permIdEnd); - permId = StringUtils.strip(permId, "[]"); - return permId; - } - static List<PropertyAssignment> extractAndSortPropertyAssignmentsPerGivenPropertyName(IEntityType rawData, List<String> propertyNames) throws Exception { @@ -477,15 +377,4 @@ public class TestUtils return sortedPropertyAssignments; } - private static byte[] readData(Path xls_path) throws IOException - { - String path = xls_path.toString(); - try (InputStream resourceAsStream = TestUtils.class.getClassLoader().getResourceAsStream(path)) - { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - IOUtils.copy(resourceAsStream, byteArrayOutputStream); - return byteArrayOutputStream.toByteArray(); - } - } - } diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/experiments/update.xls b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/experiments/update.xls index 60bfe80c3e8fea6978f0145f164c9cb71130df34..ab24558768f2a6600fff42053a2ea52a5125cbe4 100644 GIT binary patch literal 7168 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 Hzb*eSXops< literal 32768 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/p9^xaAe7njtTbiq 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 bG52;?8pte-nY-5inSjL&UVk6|X$|}zLQBN= diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/property_types/normal_property_type.xls b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/property_types/normal_property_type.xls index 633a047ccafd7d2b61987293ac60eaa7c4699d17..21a24d210d4513255e21f55db657a2fcd929d162 100644 GIT binary patch literal 15360 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 literal 40960 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 I|K%L`KmM`#6aWAK diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/sample_types/with_dynamic_script.xls b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/sample_types/with_dynamic_script.xls index 1f4512890f85a50466176aa79763c5cb03309cb4..59ad19a0a9394856024903b37ec0b635d4f5ff77 100644 GIT binary patch literal 14848 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 V|NC<DzZR!g>i@$3-~6xV|2NJy^7#M& literal 40960 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 diff --git a/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/samples/space_project_experiment_elsewhere.xls b/core-plugin-openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/plugin/excelimport/test_files/samples/space_project_experiment_elsewhere.xls index 2ba767a0c663d8d29d81131d1507ace8ec071198..40d3452f0b4f42da9c6cec7ebb25a5f8c673a41c 100644 GIT binary patch literal 19968 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 literal 46080 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 O8SK`7zy8A|@c#h*Y0<6# diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java index df54c5679c0..1d4263cd87b 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApi.java @@ -173,7 +173,9 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.search.GlobalSearchCriter import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.search.SearchGloballyOperation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.search.SearchGloballyOperationResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportOperation; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.IImportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportOperationResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportData; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.Material; import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.MaterialType; @@ -1818,9 +1820,10 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi> } @Override - public void executeImport(final String sessionToken, final IImportData importData, final ImportOptions importOptions) + public ImportResult executeImport(final String sessionToken, final ImportData importData, final ImportOptions importOptions) { - executeOperation(sessionToken, new ImportOperation(importData, importOptions)); + final ImportOperationResult operationResult = executeOperation(sessionToken, new ImportOperation(importData, importOptions)); + return operationResult.getImportResult(); } @Override diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java index b730776794f..fbf88fbd32a 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiLogger.java @@ -18,7 +18,6 @@ package ch.ethz.sis.openbis.generic.server.asapi.v3; import java.util.List; import java.util.Map; -import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi; import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.AuthorizationGroup; import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.create.AuthorizationGroupCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.authorizationgroup.delete.AuthorizationGroupDeletionOptions; @@ -90,7 +89,8 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.update.ExternalDmsUp import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.GlobalSearchObject; import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.fetchoptions.GlobalSearchObjectFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.search.GlobalSearchCriteria; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.IImportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportData; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.Material; import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.MaterialType; @@ -1374,9 +1374,10 @@ public class ApplicationServerApiLogger extends AbstractServerLogger implements } @Override - public void executeImport(final String sessionToken, final IImportData importData, final ImportOptions importOptions) + public ImportResult executeImport(final String sessionToken, final ImportData importData, final ImportOptions importOptions) { - logAccess(sessionToken, "execute-import", "IImportData(%s) ImportOptions(%s)", importData, importOptions); + logAccess(sessionToken, "execute-import", "ImportData(%s) ImportOptions(%s)", importData, importOptions); + return null; } @Override diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiPersonalAccessTokenInvocationHandler.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiPersonalAccessTokenInvocationHandler.java index 1eba23cd465..fbce5f0a9ac 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiPersonalAccessTokenInvocationHandler.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/ApplicationServerApiPersonalAccessTokenInvocationHandler.java @@ -91,7 +91,8 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.update.ExternalDmsUp import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.GlobalSearchObject; import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.fetchoptions.GlobalSearchObjectFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.global.search.GlobalSearchCriteria; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.IImportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportData; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.Material; import ch.ethz.sis.openbis.generic.asapi.v3.dto.material.MaterialType; @@ -1258,9 +1259,9 @@ public class ApplicationServerApiPersonalAccessTokenInvocationHandler implements } @Override - public void executeImport(final String sessionToken, final IImportData importData, final ImportOptions importOptions) + public ImportResult executeImport(final String sessionToken, final ImportData importData, final ImportOptions importOptions) { - invocation.proceedWithNewFirstArgument(converter.convert(sessionToken)); + return invocation.proceedWithNewFirstArgument(converter.convert(sessionToken)); } @Override diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/importer/IImportExecutor.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/importer/IImportExecutor.java index ae242abdc90..e90d0fca54c 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/importer/IImportExecutor.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/importer/IImportExecutor.java @@ -18,11 +18,12 @@ package ch.ethz.sis.openbis.generic.server.asapi.v3.executor.importer; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportOperation; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportResult; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext; public interface IImportExecutor { - void doImport(final IOperationContext context, final ImportOperation operation); + ImportResult doImport(final IOperationContext context, final ImportOperation operation); } diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/importer/ImportExecutor.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/importer/ImportExecutor.java index 51363987d1f..2957d712024 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/importer/ImportExecutor.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/importer/ImportExecutor.java @@ -17,23 +17,13 @@ package ch.ethz.sis.openbis.generic.server.asapi.v3.executor.importer; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import ch.ethz.sis.openbis.generic.server.xls.importer.utils.FileServerUtils; import org.springframework.stereotype.Component; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportOperation; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.IImportData; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportScript; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportValue; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.UncompressedImportData; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ZipImportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportData; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportOptions; import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext; @@ -46,110 +36,11 @@ import ch.systemsx.cisd.openbis.generic.server.CommonServiceProvider; public class ImportExecutor implements IImportExecutor { - private static final String ZIP_PATH_SEPARATOR = "/"; - - private static final String SCRIPTS_FOLDER_NAME = "scripts" + ZIP_PATH_SEPARATOR; - - private static final String DATA_FOLDER_NAME = "data" + ZIP_PATH_SEPARATOR; - - private static final String MISCELLANEOUS_FOLDER_NAME = "miscellaneous" + ZIP_PATH_SEPARATOR; - - private static final String FILE_SERVICES_FOLDER_NAME = MISCELLANEOUS_FOLDER_NAME + "file-service" + ZIP_PATH_SEPARATOR; - - private static final String XLS_EXTENSION = "." + "xls"; - - private static final String XLSX_EXTENSION = "." + "xlsx"; - @Override - public void doImport(final IOperationContext context, final ImportOperation operation) + public ImportResult doImport(final IOperationContext context, final ImportOperation operation) { - final IImportData importData = operation.getImportData(); - - try - { - if (importData instanceof UncompressedImportData) - { - // XLS file - - final UncompressedImportData uncompressedImportData = (UncompressedImportData) importData; - - final Map<String, String> scripts = uncompressedImportData.getScripts() != null - ? uncompressedImportData.getScripts().stream().collect(Collectors.toMap(ImportScript::getName, ImportScript::getSource)) - : null; - final Map<String, String> importValues = uncompressedImportData.getImportValues() != null - ? uncompressedImportData.getImportValues().stream().collect(Collectors.toMap(ImportValue::getName, ImportValue::getValue)) - : null; - importXls(context, operation, scripts, importValues, uncompressedImportData.getFile(), null); - } else if (importData instanceof ZipImportData) - { - // ZIP file - final Map<String, String> scripts = new HashMap<>(); - final Map<String, String> importValues = new HashMap<>(); - byte[] xlsFileContent = null; + final ImportData importData = operation.getImportData(); - final ZipImportData zipImportData = (ZipImportData) importData; - try (final ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(zipImportData.getFile()))) - { - ZipEntry entry; - while ((entry = zip.getNextEntry()) != null) - { - final String entryName = entry.getName(); - if (entry.isDirectory()) - { - if (!SCRIPTS_FOLDER_NAME.equals(entryName) && !DATA_FOLDER_NAME.equals(entryName) && !MISCELLANEOUS_FOLDER_NAME.equals(entryName)) - { - throw UserFailureException.fromTemplate("Illegal directory '%s' is found inside the imported file.", entryName); - } - } else - { - if (!entryName.contains(ZIP_PATH_SEPARATOR) && (entryName.endsWith(XLS_EXTENSION) || entryName.endsWith(XLSX_EXTENSION))) - { - if (xlsFileContent == null) - { - xlsFileContent = zip.readAllBytes(); - } else - { - throw UserFailureException.fromTemplate("More than one XLS file found in the root of the imported ZIP file."); - } - } else if (entryName.startsWith(SCRIPTS_FOLDER_NAME)) - { - scripts.put(entryName.substring(SCRIPTS_FOLDER_NAME.length()), new String(zip.readAllBytes())); - } else if (entryName.startsWith(DATA_FOLDER_NAME)) - { - importValues.put(entryName.substring(DATA_FOLDER_NAME.length()), new String(zip.readAllBytes())); - } else - { - throw UserFailureException.fromTemplate( - "Entry '%s' is not allowed. Only one root XLS file is allowed and files inside the '%s' or '%s' folder", - entryName, SCRIPTS_FOLDER_NAME, DATA_FOLDER_NAME); - } - } - } - } - - if (xlsFileContent != null) - { - importXls(context, operation, scripts, importValues, xlsFileContent, zipImportData.getFile()); - } else - { - throw UserFailureException.fromTemplate("XLS file not found in the root of the imported ZIP file."); - } - - } else - { - throw UserFailureException.fromTemplate("Unknown instance of import data '%s'.", - importData != null ? importData.getClass().getName() : null); - } - } catch (final IOException e) - { - throw UserFailureException.fromTemplate(e, "IO exception importing."); - } - } - - private static void importXls(final IOperationContext context, final ImportOperation operation, final Map<String, String> scripts, - final Map<String, String> importValues, final byte[] xlsContent, byte[] zipImportDataOrNull) - throws IOException - { final IApplicationServerInternalApi applicationServerApi = CommonServiceProvider.getApplicationServerApi(); final ImportOptions importOptions = operation.getImportOptions(); @@ -160,31 +51,18 @@ public class ImportExecutor implements IImportExecutor .get("project-samples-enabled")); importerImportOptions.setAllowProjectSamples(projectSamplesEnabled); - final XLSImport xlsImport = new XLSImport(context.getSession().getSessionToken(), applicationServerApi, scripts, - importValues, ImportModes.valueOf(importOptions.getMode().name()), importerImportOptions, "DEFAULT"); - - xlsImport.importXLS(xlsContent); - if (zipImportDataOrNull != null) + try { - importZipData(zipImportDataOrNull); - } - } - - public static void importZipData(byte[] zipImportDataOrNull) throws IOException - { - try (final ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(zipImportDataOrNull))) + final XLSImport xlsImport = new XLSImport(context.getSession().getSessionToken(), applicationServerApi, + ImportModes.valueOf(importOptions.getMode().name()), importerImportOptions, importData.getSessionWorkspaceFiles(), false); + return new ImportResult(xlsImport.start()); + } catch (final IOException e) { - ZipEntry entry; - while ((entry = zip.getNextEntry()) != null) - { - final String filePath = entry.getName(); - if (!entry.isDirectory() && filePath.startsWith(FILE_SERVICES_FOLDER_NAME)) - { - String fileServicePath = ZIP_PATH_SEPARATOR + filePath.substring(FILE_SERVICES_FOLDER_NAME.length()); - byte[] fileBytes = zip.readAllBytes(); - FileServerUtils.write(fileServicePath, fileBytes); - } - } + throw UserFailureException.fromTemplate(e, "IO exception importing."); + } catch (final Exception e) + { + throw UserFailureException.fromTemplate(e,"Exception importing data: %s", e.getMessage()); } } + } diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/importer/ImportOperationExecutor.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/importer/ImportOperationExecutor.java index ef4032eeaa8..7702c16e50e 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/importer/ImportOperationExecutor.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/importer/ImportOperationExecutor.java @@ -22,6 +22,7 @@ import org.springframework.stereotype.Component; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportOperation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportOperationResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.ImportResult; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.common.OperationExecutor; @@ -41,8 +42,8 @@ public class ImportOperationExecutor extends OperationExecutor<ImportOperation, @Override protected ImportOperationResult doExecute(final IOperationContext context, final ImportOperation operation) { - executor.doImport(context, operation); - return new ImportOperationResult(); + final ImportResult importResult = executor.doImport(context, operation); + return new ImportOperationResult(importResult); } } diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/export/helper/AbstractXLSExportHelper.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/export/helper/AbstractXLSExportHelper.java index 2e15b9a1a95..5d7c32c7277 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/export/helper/AbstractXLSExportHelper.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/export/helper/AbstractXLSExportHelper.java @@ -220,8 +220,7 @@ public abstract class AbstractXLSExportHelper<ENTITY_TYPE extends IEntityType> i final String filePath = matcher.group(3); try { - final byte[] fileContent = FileServerUtils.read(filePath); - imageFiles.put(filePath, fileContent); + imageFiles.put(filePath, FileServerUtils.readAllBytes(filePath)); } catch (final IOException e) { warnings.add(String.format("Could not read the file at path '%s'.", filePath)); diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/MainImportXlsTest.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/MainImportXlsTest.java deleted file mode 100644 index 715cfae1dd0..00000000000 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/MainImportXlsTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright ETH 2022 - 2023 Zürich, 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. - */ -package ch.ethz.sis.openbis.generic.server.xls.importer; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Map; - -import org.apache.log4j.Logger; - -import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; -import ch.ethz.sis.openbis.generic.server.xls.importer.enums.ImportModes; -import ch.systemsx.cisd.common.filesystem.FileUtilities; -import ch.systemsx.cisd.common.logging.LogCategory; -import ch.systemsx.cisd.common.logging.LogFactory; -import ch.systemsx.cisd.common.spring.HttpInvokerUtils; -import ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl.MasterDataRegistrationHelper; - -public class MainImportXlsTest -{ - private static final Logger operationLog = - LogFactory.getLogger(LogCategory.OPERATION, MainImportXlsTest.class); - - private static final int TIMEOUT = 10000; - - private static final String URL = - "http://localhost:8888/openbis/openbis" + IApplicationServerApi.SERVICE_URL; - - private static final File ELN_MASTER_DATA_PATH = - new File("../core-plugin-openbis/dist/core-plugins/eln-lims/1/as"); - - private static final File LIFE_SCIENCES_MASTER_DATA_PATH = - new File("../core-plugin-openbis/dist/core-plugins/eln-lims-life-sciences/1/as"); - - // used only for development! - public static void main(String[] args) - { - IApplicationServerApi v3 = - HttpInvokerUtils.createServiceStub(IApplicationServerApi.class, URL, TIMEOUT); - String sessionToken = v3.login("system", "a"); - - Map<String, String> scripts = getListScripts(ELN_MASTER_DATA_PATH.getPath()); - ImportOptions options = new ImportOptions(); - - XLSImport importXls = - new XLSImport(sessionToken, v3, scripts, Map.of(), ImportModes.UPDATE_IF_EXISTS, options, - "ELN-LIMS"); - Collection<IObjectId> importedIds = new ArrayList<>(); - - File commonDataModel = new File(ELN_MASTER_DATA_PATH, "/master-data/common-data-model.xls"); - importedIds.addAll(importXls.importXLS(FileUtilities.loadToByteArray(commonDataModel))); - File singleGroupDataModel = - new File(ELN_MASTER_DATA_PATH, "/master-data/single-group-data-model.xls"); - importedIds.addAll( - importXls.importXLS(FileUtilities.loadToByteArray(singleGroupDataModel))); - - Map<String, String> lifeSciencesImporterScripts = - getListScripts(LIFE_SCIENCES_MASTER_DATA_PATH.getPath()); - XLSImport lifeSciencesImporter = - new XLSImport(sessionToken, v3, lifeSciencesImporterScripts, - Map.of(), ImportModes.UPDATE_IF_EXISTS, options, "ELN-LIMS-LIFE-SCIENCES"); - File lifeSciencesDataModel = - new File(LIFE_SCIENCES_MASTER_DATA_PATH, "/master-data/data-model.xls"); - importedIds.addAll(lifeSciencesImporter.importXLS( - FileUtilities.loadToByteArray(lifeSciencesDataModel))); - } - - private static Map<String, String> getListScripts(String path) - { - MasterDataRegistrationHelper helper = new MasterDataRegistrationHelper(Arrays.asList(path)); - return helper.getAllScripts(); - } -} diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/XLSImport.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/XLSImport.java index 6340b3a5b13..64c3dc87bf6 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/XLSImport.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/XLSImport.java @@ -15,12 +15,29 @@ */ package ch.ethz.sis.openbis.generic.server.xls.importer; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.apache.log4j.Logger; + import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; import ch.ethz.sis.openbis.generic.server.xls.importer.delay.DelayedExecutionDecorator; import ch.ethz.sis.openbis.generic.server.xls.importer.enums.ImportModes; import ch.ethz.sis.openbis.generic.server.xls.importer.enums.ImportTypes; import ch.ethz.sis.openbis.generic.server.xls.importer.enums.ScriptTypes; +import ch.ethz.sis.openbis.generic.server.xls.importer.handler.ExcelParser; import ch.ethz.sis.openbis.generic.server.xls.importer.handler.VersionInfoHandler; import ch.ethz.sis.openbis.generic.server.xls.importer.helper.DatasetTypeImportHelper; import ch.ethz.sis.openbis.generic.server.xls.importer.helper.ExperimentImportHelper; @@ -35,20 +52,34 @@ import ch.ethz.sis.openbis.generic.server.xls.importer.helper.SemanticAnnotation import ch.ethz.sis.openbis.generic.server.xls.importer.helper.SpaceImportHelper; import ch.ethz.sis.openbis.generic.server.xls.importer.helper.VocabularyImportHelper; import ch.ethz.sis.openbis.generic.server.xls.importer.helper.VocabularyTermImportHelper; -import ch.ethz.sis.openbis.generic.server.xls.importer.handler.ExcelParser; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.DatabaseConsistencyChecker; +import ch.ethz.sis.openbis.generic.server.xls.importer.utils.FileServerUtils; import ch.systemsx.cisd.common.exceptions.UserFailureException; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; -import org.apache.log4j.Logger; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import ch.systemsx.cisd.openbis.generic.server.CommonServiceProvider; +import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider; public class XLSImport { + private static final String PATH_SEPARATOR = "/"; + + private static final String SCRIPTS_FOLDER_NAME = "scripts" + PATH_SEPARATOR; + + private static final String DATA_FOLDER_NAME = "data" + PATH_SEPARATOR; + + private static final String MISCELLANEOUS_FOLDER_NAME = "miscellaneous" + PATH_SEPARATOR; + + private static final String FILE_SERVICES_FOLDER_NAME = MISCELLANEOUS_FOLDER_NAME + "file-service" + PATH_SEPARATOR; + + private static final String XLS_EXTENSION = "." + "xls"; + + private static final String XLSX_EXTENSION = "." + "xlsx"; + + private static final int XLSX_DOCUMENT_LIMIT = 536870912; // 512 MB + + private static final int EMBEDDED_DOCUMENT_LIMIT = 16777216; // 16 MB + private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, XLSImport.class); private final String sessionToken; @@ -85,8 +116,6 @@ public class XLSImport private final PropertyAssignmentImportHelper propertyAssignmentHelper; - private final ScriptImportHelper scriptHelper; - private final SemanticAnnotationImportHelper semanticAnnotationImportHelper; private final DatabaseConsistencyChecker dbChecker; @@ -95,8 +124,20 @@ public class XLSImport private final Map<String, String> importValues; - public XLSImport(String sessionToken, IApplicationServerApi api, Map<String, String> scripts, Map<String, String> importValues, - ImportModes mode, ImportOptions options, String xlsName) + private final ScriptImportHelper scriptHelper; + + private final String[] sessionWorkspaceFiles; + + private final byte[][] xls; + + private static final String ZIP_EXTENSION = "." + "zip"; + + public XLSImport(String sessionToken, + IApplicationServerApi api, + ImportModes mode, + ImportOptions options, + String[] sessionWorkspaceFiles, + boolean shouldCheckVersionsOnDatabase) throws IOException { this.sessionToken = sessionToken; this.api = api; @@ -105,8 +146,6 @@ public class XLSImport this.afterVersions = VersionInfoHandler.loadAllVersions(options); this.dbChecker = new DatabaseConsistencyChecker(this.sessionToken, this.api, this.afterVersions); this.delayedExecutor = new DelayedExecutionDecorator(this.sessionToken, this.api); - this.importValues = importValues; - this.vocabularyHelper = new VocabularyImportHelper(this.delayedExecutor, mode, options, afterVersions); this.vocabularyTermHelper = new VocabularyTermImportHelper(this.delayedExecutor, mode, options, afterVersions); this.sampleTypeHelper = new SampleTypeImportHelper(this.delayedExecutor, mode, options, afterVersions); @@ -118,122 +157,238 @@ public class XLSImport this.sampleHelper = new SampleImportHelper(this.delayedExecutor, mode, options); this.propertyHelper = new PropertyTypeImportHelper(this.delayedExecutor, mode, options, afterVersions); this.propertyAssignmentHelper = new PropertyAssignmentImportHelper(this.delayedExecutor, mode, options, beforeVersions); - this.scriptHelper = new ScriptImportHelper(this.delayedExecutor, mode, options, scripts); this.semanticAnnotationImportHelper = new SemanticAnnotationImportHelper(this.delayedExecutor, mode, options); - this.shouldCheckVersionsOnDatabase = xlsName == null || xlsName.equals("DEFAULT") == false; - } + this.shouldCheckVersionsOnDatabase = shouldCheckVersionsOnDatabase; - public List<IObjectId> importXLS(byte xls[]) - { - if (shouldCheckVersionsOnDatabase) - { - this.dbChecker.checkVersionsOnDataBase(); - } + // File Parsing + this.sessionWorkspaceFiles = sessionWorkspaceFiles; + final ISessionWorkspaceProvider sessionWorkspaceProvider = CommonServiceProvider.getSessionWorkspaceProvider(); - List<List<List<String>>> lines = ExcelParser.parseExcel(xls, importValues); - int pageNumber = 0; - int lineNumber; + final Map<String, String> scripts = new HashMap<>(); + byte[][] xls = new byte[sessionWorkspaceFiles.length][]; + this.importValues = new HashMap<>(); - while (pageNumber < lines.size()) + for (int i = 0; i < sessionWorkspaceFiles.length; i++) { - lineNumber = 0; - List<List<String>> page = lines.get(pageNumber); - int pageEnd = getPageEnd(page); - while (lineNumber < pageEnd) + InputStream read = sessionWorkspaceProvider.read(sessionToken, sessionWorkspaceFiles[i]); + if (sessionWorkspaceFiles[i].toLowerCase().endsWith(ZIP_EXTENSION)) { - int blockEnd = getBlockEnd(page, lineNumber); - ImportTypes blockType; - try + try (final ZipInputStream zip = new ZipInputStream(read)) { - blockType = ImportTypes.valueOf(page.get(lineNumber).get(0)); - } catch (Exception e) - { - throw new UserFailureException( - "Exception at page " + (pageNumber + 1) + " and line " + (lineNumber + 1) + " with message: " + e.getMessage()); - } - lineNumber++; - - switch (blockType) - { - case VOCABULARY_TYPE: - vocabularyHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2); - if (lineNumber + 2 != blockEnd) + ZipEntry entry; + while ((entry = zip.getNextEntry()) != null) + { + final String entryName = entry.getName(); + if (entry.isDirectory()) { - vocabularyTermHelper.importBlock(page, pageNumber, lineNumber, blockEnd); - } - break; - case SAMPLE_TYPE: - // parse and create scripts - scriptHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2, ScriptTypes.VALIDATION_SCRIPT); - // parse and create sample type - sampleTypeHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2); - semanticAnnotationImportHelper.importBlockForEntityType(page, pageNumber, lineNumber, lineNumber + 2, ImportTypes.SAMPLE_TYPE); - // parse and assignment properties - if (lineNumber + 2 != blockEnd) + if (!SCRIPTS_FOLDER_NAME.equals(entryName) && !DATA_FOLDER_NAME.equals(entryName) && + !entryName.startsWith(MISCELLANEOUS_FOLDER_NAME)) + { + throw UserFailureException.fromTemplate("Illegal directory '%s' is found inside the imported file.", entryName); + } + } else { - scriptHelper.importBlock(page, pageNumber, lineNumber + 2, blockEnd, ScriptTypes.DYNAMIC_SCRIPT); - propertyHelper.importBlock(page, pageNumber, lineNumber + 2, blockEnd); - propertyAssignmentHelper.importBlock(page, pageNumber, lineNumber, blockEnd, ImportTypes.SAMPLE_TYPE); - semanticAnnotationImportHelper.importBlockForEntityTypeProperty(page, pageNumber, lineNumber, blockEnd, ImportTypes.SAMPLE_TYPE); + if (!entryName.contains(PATH_SEPARATOR) && (entryName.endsWith(XLS_EXTENSION) || entryName.endsWith(XLSX_EXTENSION))) + { + validateEntrySize(entry.getSize(), XLSX_DOCUMENT_LIMIT); + if (xls[i] == null) + { + xls[i] = zip.readAllBytes(); + } else + { + throw UserFailureException.fromTemplate("More than one XLS file found in the root of the imported ZIP file."); + } + } else if (entryName.startsWith(SCRIPTS_FOLDER_NAME)) + { + validateEntrySize(entry.getSize(), EMBEDDED_DOCUMENT_LIMIT); + scripts.put(entryName.substring(SCRIPTS_FOLDER_NAME.length()), new String(zip.readAllBytes())); + } else if (entryName.startsWith(DATA_FOLDER_NAME)) + { + validateEntrySize(entry.getSize(), EMBEDDED_DOCUMENT_LIMIT); + this.importValues.put(entryName.substring(DATA_FOLDER_NAME.length()), new String(zip.readAllBytes())); + } else if (!entryName.startsWith(MISCELLANEOUS_FOLDER_NAME)) + { + throw UserFailureException.fromTemplate( + "Entry '%s' is not allowed. Only one root XLS file is allowed and files inside the '%s' or '%s' folder", + entryName, SCRIPTS_FOLDER_NAME, DATA_FOLDER_NAME); + } } - break; - case EXPERIMENT_TYPE: - // parse and create scripts - scriptHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2, ScriptTypes.VALIDATION_SCRIPT); - // parse and create experiment type - experimentTypeHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2); - semanticAnnotationImportHelper.importBlockForEntityType(page, pageNumber, lineNumber, lineNumber + 2, ImportTypes.EXPERIMENT_TYPE); - // parse and assignment properties - if (lineNumber + 2 != blockEnd) + } + } + } else + { + final File canonicalFile = sessionWorkspaceProvider.getCanonicalFile(sessionToken, sessionWorkspaceFiles[i]); + validateEntrySize(canonicalFile.length(), XLSX_DOCUMENT_LIMIT); + xls[i] = sessionWorkspaceProvider.readAllBytes(sessionToken, sessionWorkspaceFiles[i]); + + // Script folder support + final String scriptsFolderName = canonicalFile.getParent() + + PATH_SEPARATOR + SCRIPTS_FOLDER_NAME; + + final File scriptsFolder = new File(scriptsFolderName); + if (scriptsFolder.exists() && scriptsFolder.isDirectory()) + { + final File[] files = scriptsFolder.listFiles(); + if (files != null) + { + for (final File file : files) { - scriptHelper.importBlock(page, pageNumber, lineNumber + 2, blockEnd, ScriptTypes.DYNAMIC_SCRIPT); - propertyHelper.importBlock(page, pageNumber, lineNumber + 2, blockEnd); - propertyAssignmentHelper.importBlock(page, pageNumber, lineNumber, blockEnd, ImportTypes.EXPERIMENT_TYPE); - semanticAnnotationImportHelper.importBlockForEntityTypeProperty(page, pageNumber, lineNumber, blockEnd, ImportTypes.EXPERIMENT_TYPE); + scripts.put(file.getName(), Files.readString(file.toPath(), StandardCharsets.UTF_8)); } - break; - case DATASET_TYPE: - // parse and create scripts - scriptHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2, ScriptTypes.VALIDATION_SCRIPT); - // parse and create dataset type - datasetTypeHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2); - semanticAnnotationImportHelper.importBlockForEntityType(page, pageNumber, lineNumber, lineNumber + 2, ImportTypes.DATASET_TYPE); - // parse and assignment properties - if (lineNumber + 2 != blockEnd) + } + } + + // Data folder support + final String dataFolderName = canonicalFile.getParent() + + PATH_SEPARATOR + DATA_FOLDER_NAME; + + final File dataFolder = new File(dataFolderName); + if (dataFolder.exists() && dataFolder.isDirectory()) + { + final File[] files = dataFolder.listFiles(); + if (files != null) + { + for (final File file : files) { - scriptHelper.importBlock(page, pageNumber, lineNumber + 2, blockEnd, ScriptTypes.DYNAMIC_SCRIPT); - propertyHelper.importBlock(page, pageNumber, lineNumber + 2, blockEnd); - propertyAssignmentHelper.importBlock(page, pageNumber, lineNumber, blockEnd, ImportTypes.DATASET_TYPE); - semanticAnnotationImportHelper.importBlockForEntityTypeProperty(page, pageNumber, lineNumber, blockEnd, ImportTypes.DATASET_TYPE); + this.importValues.put(file.getName(), Files.readString(file.toPath(), StandardCharsets.UTF_8)); } - break; - case SPACE: - spaceHelper.importBlock(page, pageNumber, lineNumber, blockEnd); - break; - case PROJECT: - projectHelper.importBlock(page, pageNumber, lineNumber, blockEnd); - break; - case EXPERIMENT: - experimentHelper.importBlock(page, pageNumber, lineNumber, blockEnd); - break; - case SAMPLE: - sampleHelper.importBlock(page, pageNumber, lineNumber, blockEnd); - break; - case PROPERTY_TYPE: - propertyHelper.importBlock(page, pageNumber, lineNumber, blockEnd); - semanticAnnotationImportHelper.importBlockForPropertyType(page, pageNumber, lineNumber, blockEnd); - break; - default: - throw new UserFailureException("Unknown type: " + blockType); + } } - lineNumber = blockEnd + 1; } - pageNumber++; } - this.delayedExecutor.hasFinished(); + this.xls = xls; + this.scriptHelper = new ScriptImportHelper(this.delayedExecutor, mode, options, scripts); + } + + private static void validateEntrySize(final long size, final int limit) + { + if (size > limit) + { + throw UserFailureException.fromTemplate("Document limit exceeded: %d.", size); + } + } + public List<IObjectId> start() throws IOException + { + if (shouldCheckVersionsOnDatabase) + { + this.dbChecker.checkVersionsOnDataBase(); + } + + for (int i = 0; i < this.xls.length; i++) + { + List<List<List<String>>> lines = ExcelParser.parseExcel(this.xls[i], importValues); + int pageNumber = 0; + int lineNumber; + + while (pageNumber < lines.size()) + { + lineNumber = 0; + List<List<String>> page = lines.get(pageNumber); + int pageEnd = getPageEnd(page); + while (lineNumber < pageEnd) + { + int blockEnd = getBlockEnd(page, lineNumber); + ImportTypes blockType; + try + { + blockType = ImportTypes.valueOf(page.get(lineNumber).get(0)); + } catch (Exception e) + { + throw new UserFailureException( + "Exception at page " + (pageNumber + 1) + " and line " + (lineNumber + 1) + " with message: " + e.getMessage()); + } + lineNumber++; + + switch (blockType) + { + case VOCABULARY_TYPE: + vocabularyHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2); + if (lineNumber + 2 != blockEnd) + { + vocabularyTermHelper.importBlock(page, pageNumber, lineNumber, blockEnd); + } + break; + case SAMPLE_TYPE: + // parse and create scripts + scriptHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2, ScriptTypes.VALIDATION_SCRIPT); + // parse and create sample type + sampleTypeHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2); + semanticAnnotationImportHelper.importBlockForEntityType(page, pageNumber, lineNumber, lineNumber + 2, + ImportTypes.SAMPLE_TYPE); + // parse and assignment properties + if (lineNumber + 2 != blockEnd) + { + scriptHelper.importBlock(page, pageNumber, lineNumber + 2, blockEnd, ScriptTypes.DYNAMIC_SCRIPT); + propertyHelper.importBlock(page, pageNumber, lineNumber + 2, blockEnd); + propertyAssignmentHelper.importBlock(page, pageNumber, lineNumber, blockEnd, ImportTypes.SAMPLE_TYPE); + semanticAnnotationImportHelper.importBlockForEntityTypeProperty(page, pageNumber, lineNumber, blockEnd, + ImportTypes.SAMPLE_TYPE); + } + break; + case EXPERIMENT_TYPE: + // parse and create scripts + scriptHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2, ScriptTypes.VALIDATION_SCRIPT); + // parse and create experiment type + experimentTypeHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2); + semanticAnnotationImportHelper.importBlockForEntityType(page, pageNumber, lineNumber, lineNumber + 2, + ImportTypes.EXPERIMENT_TYPE); + // parse and assignment properties + if (lineNumber + 2 != blockEnd) + { + scriptHelper.importBlock(page, pageNumber, lineNumber + 2, blockEnd, ScriptTypes.DYNAMIC_SCRIPT); + propertyHelper.importBlock(page, pageNumber, lineNumber + 2, blockEnd); + propertyAssignmentHelper.importBlock(page, pageNumber, lineNumber, blockEnd, ImportTypes.EXPERIMENT_TYPE); + semanticAnnotationImportHelper.importBlockForEntityTypeProperty(page, pageNumber, lineNumber, blockEnd, + ImportTypes.EXPERIMENT_TYPE); + } + break; + case DATASET_TYPE: + // parse and create scripts + scriptHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2, ScriptTypes.VALIDATION_SCRIPT); + // parse and create dataset type + datasetTypeHelper.importBlock(page, pageNumber, lineNumber, lineNumber + 2); + semanticAnnotationImportHelper.importBlockForEntityType(page, pageNumber, lineNumber, lineNumber + 2, + ImportTypes.DATASET_TYPE); + // parse and assignment properties + if (lineNumber + 2 != blockEnd) + { + scriptHelper.importBlock(page, pageNumber, lineNumber + 2, blockEnd, ScriptTypes.DYNAMIC_SCRIPT); + propertyHelper.importBlock(page, pageNumber, lineNumber + 2, blockEnd); + propertyAssignmentHelper.importBlock(page, pageNumber, lineNumber, blockEnd, ImportTypes.DATASET_TYPE); + semanticAnnotationImportHelper.importBlockForEntityTypeProperty(page, pageNumber, lineNumber, blockEnd, + ImportTypes.DATASET_TYPE); + } + break; + case SPACE: + spaceHelper.importBlock(page, pageNumber, lineNumber, blockEnd); + break; + case PROJECT: + projectHelper.importBlock(page, pageNumber, lineNumber, blockEnd); + break; + case EXPERIMENT: + experimentHelper.importBlock(page, pageNumber, lineNumber, blockEnd); + break; + case SAMPLE: + sampleHelper.importBlock(page, pageNumber, lineNumber, blockEnd); + break; + case PROPERTY_TYPE: + propertyHelper.importBlock(page, pageNumber, lineNumber, blockEnd); + semanticAnnotationImportHelper.importBlockForPropertyType(page, pageNumber, lineNumber, blockEnd); + break; + default: + throw new UserFailureException("Unknown type: " + blockType); + } + lineNumber = blockEnd + 1; + } + pageNumber++; + } + } + + this.delayedExecutor.hasFinished(); VersionInfoHandler.writeAllVersions(options, afterVersions); + importZipData(); return new ArrayList<>(this.delayedExecutor.getIds()); } @@ -290,4 +445,34 @@ public class XLSImport } return true; } + + + private void importZipData() throws IOException + { + for (int i = 0; i < this.sessionWorkspaceFiles.length; i++) + { + if (this.sessionWorkspaceFiles[i].toLowerCase().endsWith(ZIP_EXTENSION)) + { + final ISessionWorkspaceProvider sessionWorkspaceProvider = CommonServiceProvider.getSessionWorkspaceProvider(); + InputStream read = sessionWorkspaceProvider.read(this.sessionToken, this.sessionWorkspaceFiles[i]); + + try (final ZipInputStream zip = new ZipInputStream(read)) + { + ZipEntry entry; + while ((entry = zip.getNextEntry()) != null) + { + final String filePath = entry.getName(); + if (!entry.isDirectory() && filePath.startsWith(FILE_SERVICES_FOLDER_NAME)) + { + String fileServicePath = PATH_SEPARATOR + filePath.substring(FILE_SERVICES_FOLDER_NAME.length()); + try (final OutputStream outputStream = FileServerUtils.newOutputStream(fileServicePath)) + { + zip.transferTo(outputStream); + } + } + } + } + } + } + } } \ No newline at end of file diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/delay/DelayedExecutionDecorator.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/delay/DelayedExecutionDecorator.java index b67fb2fb565..6d596c17d80 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/delay/DelayedExecutionDecorator.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/delay/DelayedExecutionDecorator.java @@ -15,6 +15,18 @@ */ package ch.ethz.sis.openbis.generic.server.xls.importer.delay; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.IObjectId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IEntityType; @@ -26,7 +38,6 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSetType; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.create.DataSetTypeCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.DataSetTypeFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.update.DataSetTypeUpdate; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.EntityKind; import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id.EntityTypePermId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id.IEntityTypeId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.Experiment; @@ -94,10 +105,6 @@ import ch.ethz.sis.openbis.generic.server.xls.importer.enums.ImportTypes; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.ImportUtils; import ch.systemsx.cisd.common.exceptions.UserFailureException; -import java.io.Serializable; -import java.util.*; -import java.util.stream.Stream; - public class DelayedExecutionDecorator { private String sessionToken; @@ -123,7 +130,7 @@ public class DelayedExecutionDecorator this.v3 = v3; this.ids = new LinkedHashSet<>(); this.resolvedVariables = new HashMap<>(); - this.delayedExecutions = new HashMap<>(); + this.delayedExecutions = new LinkedHashMap<>(); // The only reason this is a linked Hash Map is so the list of error messages is on the same order as the delayed executions are added. this.propertyTypeCache = new HashMap<>(); } @@ -173,7 +180,7 @@ public class DelayedExecutionDecorator if (!delayedExecutions.isEmpty()) { List<String> errors = new ArrayList<>(); - Set<DelayedExecution> delayedExecutionsAsList = new HashSet<>(); + Set<DelayedExecution> delayedExecutionsAsList = new LinkedHashSet<>(); // The only reason this is a linked Hash Set is so the list of error messages is on the same order as the delayed executions are added. for (List<DelayedExecution> valueList : delayedExecutions.values()) { delayedExecutionsAsList.addAll(valueList); diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/BasicImportHelper.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/BasicImportHelper.java index 68e38eb2b43..27692bc4fdd 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/BasicImportHelper.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/BasicImportHelper.java @@ -87,7 +87,10 @@ public abstract class BasicImportHelper extends AbstractImportHelper switch (mode) { case FAIL_IF_EXISTS: - throw new UserFailureException("Mode FAIL_IF_EXISTS - Found existing " + getTypeName()); + if (!(this instanceof SemanticAnnotationImportHelper)) + { + throw new UserFailureException("Mode FAIL_IF_EXISTS - Found existing " + getTypeName()); + } case UPDATE_IF_EXISTS: updateObject(header, page.get(lineIndex), pageIndex, lineIndex); updateVersion(header, page.get(lineIndex)); diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/DatasetTypeImportHelper.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/DatasetTypeImportHelper.java index 1b0e34aba04..e7087238d59 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/DatasetTypeImportHelper.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/DatasetTypeImportHelper.java @@ -15,6 +15,9 @@ */ package ch.ethz.sis.openbis.generic.server.xls.importer.helper; +import java.util.List; +import java.util.Map; + import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.create.DataSetTypeCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.DataSetTypeFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.update.DataSetTypeUpdate; @@ -28,9 +31,7 @@ import ch.ethz.sis.openbis.generic.server.xls.importer.utils.AttributeValidator; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.IAttribute; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.ImportUtils; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.VersionUtils; - -import java.util.List; -import java.util.Map; +import ch.systemsx.cisd.common.exceptions.UserFailureException; public class DatasetTypeImportHelper extends BasicImportHelper { @@ -81,9 +82,15 @@ public class DatasetTypeImportHelper extends BasicImportHelper @Override protected boolean isNewVersion(Map<String, Integer> header, List<String> values) { - String version = getValueByColumnName(header, values, Attribute.Version); String code = getValueByColumnName(header, values, Attribute.Code); + if (code == null) + { + throw new UserFailureException("Mandatory field is missing or empty: " + Attribute.Code); + } + + String version = getValueByColumnName(header, values, Attribute.Version); + if (version == null || version.isEmpty()) { return true; } else { diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/ExperimentImportHelper.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/ExperimentImportHelper.java index 1c90876232a..fea3039b97e 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/ExperimentImportHelper.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/ExperimentImportHelper.java @@ -92,7 +92,7 @@ public class ExperimentImportHelper extends BasicImportHelper experimentType = new EntityTypePermId(getValueByColumnName(header, page.get(lineIndex), EXPERIMENT_TYPE_FIELD)); if(experimentType.getPermId() == null || experimentType.getPermId().isEmpty()) { - throw new UserFailureException("Mandatory field missing or empty: " + EXPERIMENT_TYPE_FIELD); + throw new UserFailureException("Mandatory field is missing or empty: " + EXPERIMENT_TYPE_FIELD); } // first check that experiment type exist. @@ -171,7 +171,6 @@ public class ExperimentImportHelper extends BasicImportHelper @Override protected void updateObject(Map<String, Integer> header, List<String> values, int page, int line) { String identifier = getValueByColumnName(header, values, Attribute.Identifier); - String project = getValueByColumnName(header, values, Attribute.Project); if (identifier == null || identifier.isEmpty()) { @@ -182,6 +181,8 @@ public class ExperimentImportHelper extends BasicImportHelper IExperimentId experimentId = new ExperimentIdentifier(identifier); update.setExperimentId(experimentId); + String project = getValueByColumnName(header, values, Attribute.Project); + // Project is used to "MOVE", only set if present since can't be null if (project != null && !project.isEmpty()) { diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/ExperimentTypeImportHelper.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/ExperimentTypeImportHelper.java index 274c1a36a4b..7b816377c5b 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/ExperimentTypeImportHelper.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/ExperimentTypeImportHelper.java @@ -15,6 +15,9 @@ */ package ch.ethz.sis.openbis.generic.server.xls.importer.helper; +import java.util.List; +import java.util.Map; + import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id.EntityTypePermId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.create.ExperimentTypeCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.fetchoptions.ExperimentTypeFetchOptions; @@ -28,9 +31,7 @@ import ch.ethz.sis.openbis.generic.server.xls.importer.utils.AttributeValidator; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.IAttribute; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.ImportUtils; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.VersionUtils; - -import java.util.List; -import java.util.Map; +import ch.systemsx.cisd.common.exceptions.UserFailureException; public class ExperimentTypeImportHelper extends BasicImportHelper { @@ -81,9 +82,15 @@ public class ExperimentTypeImportHelper extends BasicImportHelper @Override protected boolean isNewVersion(Map<String, Integer> header, List<String> values) { - String version = getValueByColumnName(header, values, Attribute.Version); String code = getValueByColumnName(header, values, Attribute.Code); + if (code == null) + { + throw new UserFailureException("Mandatory field is missing or empty: " + Attribute.Code); + } + + String version = getValueByColumnName(header, values, Attribute.Version); + if (version == null || version.isEmpty()) { return true; } else { diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/PropertyAssignmentImportHelper.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/PropertyAssignmentImportHelper.java index a8b9c170e66..5eba0433083 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/PropertyAssignmentImportHelper.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/PropertyAssignmentImportHelper.java @@ -15,6 +15,13 @@ */ package ch.ethz.sis.openbis.generic.server.xls.importer.helper; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IEntityType; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.update.ListUpdateValue; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSetType; @@ -43,13 +50,7 @@ import ch.ethz.sis.openbis.generic.server.xls.importer.utils.AttributeValidator; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.IAttribute; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.ImportUtils; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.VersionUtils; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; +import ch.systemsx.cisd.common.exceptions.UserFailureException; public class PropertyAssignmentImportHelper extends BasicImportHelper { @@ -123,9 +124,15 @@ public class PropertyAssignmentImportHelper extends BasicImportHelper @Override protected boolean isNewVersion(Map<String, Integer> header, List<String> values) { - String version = getValueByColumnName(header, values, PropertyAssignmentImportHelper.Attribute.Version); String code = getValueByColumnName(header, values, PropertyAssignmentImportHelper.Attribute.Code); + if (code == null) + { + throw new UserFailureException("Mandatory field is missing or empty: " + Attribute.Code); + } + + String version = getValueByColumnName(header, values, PropertyAssignmentImportHelper.Attribute.Version); + if (version == null || version.isEmpty()) { return true; } else { diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/PropertyTypeImportHelper.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/PropertyTypeImportHelper.java index 74b87f10c9a..7f7ebd0b6d5 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/PropertyTypeImportHelper.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/PropertyTypeImportHelper.java @@ -15,6 +15,15 @@ */ package ch.ethz.sis.openbis.generic.server.xls.importer.helper; +import static ch.ethz.sis.openbis.generic.server.xls.importer.utils.PropertyTypeSearcher.SAMPLE_DATA_TYPE_MANDATORY_TYPE; +import static ch.ethz.sis.openbis.generic.server.xls.importer.utils.PropertyTypeSearcher.SAMPLE_DATA_TYPE_PREFIX; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Logger; + import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.EntityKind; import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id.EntityTypePermId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.DataType; @@ -29,21 +38,13 @@ import ch.ethz.sis.openbis.generic.server.xls.importer.delay.DelayedExecutionDec import ch.ethz.sis.openbis.generic.server.xls.importer.enums.ImportModes; import ch.ethz.sis.openbis.generic.server.xls.importer.enums.ImportTypes; import ch.ethz.sis.openbis.generic.server.xls.importer.handler.JSONHandler; -import ch.ethz.sis.openbis.generic.server.xls.importer.utils.IAttribute; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.AttributeValidator; +import ch.ethz.sis.openbis.generic.server.xls.importer.utils.IAttribute; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.ImportUtils; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.VersionUtils; import ch.systemsx.cisd.common.exceptions.UserFailureException; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; -import org.apache.log4j.Logger; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static ch.ethz.sis.openbis.generic.server.xls.importer.utils.PropertyTypeSearcher.SAMPLE_DATA_TYPE_MANDATORY_TYPE; -import static ch.ethz.sis.openbis.generic.server.xls.importer.utils.PropertyTypeSearcher.SAMPLE_DATA_TYPE_PREFIX; public class PropertyTypeImportHelper extends BasicImportHelper { @@ -132,8 +133,7 @@ public class PropertyTypeImportHelper extends BasicImportHelper } if (!propertyData.equals(this.propertyCache.get(code))) { - throw new UserFailureException( - "Unambiguous property " + code + " found, has been declared before with different attributes."); + throw new UserFailureException("Ambiguous property " + code + " found, it has been declared before with different attributes."); } } @@ -149,6 +149,11 @@ public class PropertyTypeImportHelper extends BasicImportHelper String version = getValueByColumnName(header, values, Attribute.Version); String code = getValueByColumnName(header, values, Attribute.Code); + if (code == null) + { + throw new UserFailureException("Mandatory field is missing or empty: " + Attribute.Code); + } + boolean isInternalNamespace = ImportUtils.isInternalNamespace(code); boolean isSystem = delayedExecutor.isSystem(); boolean canUpdate = (isInternalNamespace == false) || isSystem; diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/SampleImportHelper.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/SampleImportHelper.java index 0847c18c625..1d59d9745cb 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/SampleImportHelper.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/SampleImportHelper.java @@ -107,7 +107,7 @@ public class SampleImportHelper extends BasicImportHelper sampleType = new EntityTypePermId(getValueByColumnName(header, page.get(lineIndex), SAMPLE_TYPE_FIELD)); if(sampleType.getPermId() == null || sampleType.getPermId().isEmpty()) { - throw new UserFailureException("Mandatory field missing or empty: " + SAMPLE_TYPE_FIELD); + throw new UserFailureException("Mandatory field is missing or empty: " + SAMPLE_TYPE_FIELD); } // first check that sample type exist. @@ -268,7 +268,7 @@ public class SampleImportHelper extends BasicImportHelper } if (identifier == null || identifier.isEmpty()) { - throw new UserFailureException("'Identifier' is missing, is mandatory since is needed to select a sample to update."); + throw new UserFailureException("'Identifier' is missing, it is mandatory since it is needed to select a sample to update."); } ISampleId sampleId = ImportUtils.buildSampleIdentifier(identifier); diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/SampleTypeImportHelper.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/SampleTypeImportHelper.java index 7597d8ba6a2..91f624ebce1 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/SampleTypeImportHelper.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/SampleTypeImportHelper.java @@ -15,6 +15,9 @@ */ package ch.ethz.sis.openbis.generic.server.xls.importer.helper; +import java.util.List; +import java.util.Map; + import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id.EntityTypePermId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.plugin.id.PluginPermId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.create.SampleTypeCreation; @@ -28,9 +31,7 @@ import ch.ethz.sis.openbis.generic.server.xls.importer.utils.AttributeValidator; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.IAttribute; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.ImportUtils; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.VersionUtils; - -import java.util.List; -import java.util.Map; +import ch.systemsx.cisd.common.exceptions.UserFailureException; public class SampleTypeImportHelper extends BasicImportHelper { @@ -83,9 +84,15 @@ public class SampleTypeImportHelper extends BasicImportHelper @Override protected boolean isNewVersion(Map<String, Integer> header, List<String> values) { - String version = getValueByColumnName(header, values, Attribute.Version); String code = getValueByColumnName(header, values, Attribute.Code); + if (code == null) + { + throw new UserFailureException("Mandatory field is missing or empty: " + Attribute.Code); + } + + String version = getValueByColumnName(header, values, Attribute.Version); + if (version == null || version.isEmpty()) { return true; } else { @@ -110,6 +117,12 @@ public class SampleTypeImportHelper extends BasicImportHelper @Override protected boolean isObjectExist(Map<String, Integer> header, List<String> values) { String code = getValueByColumnName(header, values, Attribute.Code); + + if (code == null) + { + throw new UserFailureException("Mandatory field is missing or empty: " + Attribute.Code); + } + EntityTypePermId id = new EntityTypePermId(code); return delayedExecutor.getSampleType(id, new SampleTypeFetchOptions()) != null; diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/ScriptImportHelper.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/ScriptImportHelper.java index 1c116252adf..bec20f48cd5 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/ScriptImportHelper.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/ScriptImportHelper.java @@ -15,6 +15,10 @@ */ package ch.ethz.sis.openbis.generic.server.xls.importer.helper; +import java.io.File; +import java.util.List; +import java.util.Map; + import ch.ethz.sis.openbis.generic.asapi.v3.dto.plugin.PluginType; import ch.ethz.sis.openbis.generic.asapi.v3.dto.plugin.create.PluginCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.plugin.fetchoptions.PluginFetchOptions; @@ -27,9 +31,6 @@ import ch.ethz.sis.openbis.generic.server.xls.importer.enums.ImportTypes; import ch.ethz.sis.openbis.generic.server.xls.importer.enums.ScriptTypes; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.ImportUtils; -import java.util.List; -import java.util.Map; - public class ScriptImportHelper extends BasicImportHelper { private static final String OWNER_CODE = "Code"; @@ -80,36 +81,42 @@ public class ScriptImportHelper extends BasicImportHelper { String scriptPath = getValueByColumnName(header, values, scriptType.getColumnName()); - if (scriptPath == null || scriptPath.isEmpty()) + if (scriptPath != null && !scriptPath.isEmpty()) { - return; - } - - PluginCreation creation = new PluginCreation(); - creation.setName(getScriptName(header, values)); - creation.setScript(this.scripts.get(scriptPath)); - switch (scriptType) { - case VALIDATION_SCRIPT: - creation.setPluginType(PluginType.ENTITY_VALIDATION); - break; - case DYNAMIC_SCRIPT: - creation.setPluginType(PluginType.DYNAMIC_PROPERTY); - break; + String script = this.scripts.get(new File(scriptPath).getName()); + if (script != null) + { + PluginCreation creation = new PluginCreation(); + creation.setName(getScriptName(header, values)); + creation.setScript(script); + switch (scriptType) + { + case VALIDATION_SCRIPT: + creation.setPluginType(PluginType.ENTITY_VALIDATION); + break; + case DYNAMIC_SCRIPT: + creation.setPluginType(PluginType.DYNAMIC_PROPERTY); + break; + } + delayedExecutor.createPlugin(creation); + } } - delayedExecutor.createPlugin(creation); } @Override protected void updateObject(Map<String, Integer> header, List<String> values, int page, int line) { String scriptPath = getValueByColumnName(header, values, scriptType.getColumnName()); - String script = this.scripts.get(scriptPath); - if (script != null) + if (scriptPath != null && !scriptPath.isEmpty()) { - PluginUpdate update = new PluginUpdate(); - PluginPermId permId = new PluginPermId(getScriptName(header, values)); - update.setPluginId(permId); - update.setScript(this.scripts.get(scriptPath)); - delayedExecutor.updatePlugin(update); + String script = this.scripts.get(new File(scriptPath).getName()); + if (script != null) + { + PluginUpdate update = new PluginUpdate(); + PluginPermId permId = new PluginPermId(getScriptName(header, values)); + update.setPluginId(permId); + update.setScript(script); + delayedExecutor.updatePlugin(update); + } } } diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/VocabularyImportHelper.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/VocabularyImportHelper.java index 7554dec7cac..758795589b8 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/VocabularyImportHelper.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/VocabularyImportHelper.java @@ -15,6 +15,9 @@ */ package ch.ethz.sis.openbis.generic.server.xls.importer.helper; +import java.util.List; +import java.util.Map; + import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.create.VocabularyCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.fetchoptions.VocabularyFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.VocabularyPermId; @@ -27,9 +30,7 @@ import ch.ethz.sis.openbis.generic.server.xls.importer.utils.AttributeValidator; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.IAttribute; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.ImportUtils; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.VersionUtils; - -import java.util.List; -import java.util.Map; +import ch.systemsx.cisd.common.exceptions.UserFailureException; public class VocabularyImportHelper extends BasicImportHelper { @@ -79,6 +80,11 @@ public class VocabularyImportHelper extends BasicImportHelper String version = getValueByColumnName(header, values, Attribute.Version); String code = getValueByColumnName(header, values, Attribute.Code); + if (code == null) + { + throw new UserFailureException("Mandatory field is missing or empty: " + Attribute.Code); + } + boolean isInternalNamespace = ImportUtils.isInternalNamespace(code); boolean isSystem = delayedExecutor.isSystem(); boolean canUpdate = (isInternalNamespace == false) || isSystem; diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/VocabularyTermImportHelper.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/VocabularyTermImportHelper.java index cb22b739a74..32d35170e47 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/VocabularyTermImportHelper.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/helper/VocabularyTermImportHelper.java @@ -15,6 +15,9 @@ */ package ch.ethz.sis.openbis.generic.server.xls.importer.helper; +import java.util.List; +import java.util.Map; + import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.create.VocabularyTermCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.fetchoptions.VocabularyTermFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.VocabularyPermId; @@ -28,9 +31,7 @@ import ch.ethz.sis.openbis.generic.server.xls.importer.utils.AttributeValidator; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.IAttribute; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.ImportUtils; import ch.ethz.sis.openbis.generic.server.xls.importer.utils.VersionUtils; - -import java.util.List; -import java.util.Map; +import ch.systemsx.cisd.common.exceptions.UserFailureException; public class VocabularyTermImportHelper extends BasicImportHelper { @@ -92,6 +93,11 @@ public class VocabularyTermImportHelper extends BasicImportHelper String version = getValueByColumnName(header, values, Attribute.Version); String code = getValueByColumnName(header, values, Attribute.Code); + if (code == null) + { + throw new UserFailureException("Mandatory field is missing or empty: " + Attribute.Code); + } + boolean isInternalNamespace = ImportUtils.isInternalNamespace(code) || ImportUtils.isInternalNamespace(vocabularyCode); boolean isSystem = delayedExecutor.isSystem(); boolean canUpdate = (isInternalNamespace == false) || isSystem; diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/utils/FileServerUtils.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/utils/FileServerUtils.java index e8f8b280404..7f461860a81 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/utils/FileServerUtils.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/importer/utils/FileServerUtils.java @@ -5,6 +5,8 @@ import ch.systemsx.cisd.openbis.generic.server.CommonServiceProvider; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.Set; @@ -47,15 +49,27 @@ public class FileServerUtils } } - public static byte[] read(String filePath) throws IOException + public static InputStream read(String src) throws IOException { - return Files.readAllBytes(getFilePath(filePath)); + return Files.newInputStream(getFilePath(src)); } - public static Path write(String filePath, byte[] bytes) throws IOException + public static byte[] readAllBytes(final String src) throws IOException { - Path filePathAsPath = getFilePath(filePath); - Files.createDirectories(filePathAsPath); - return Files.write(filePathAsPath, bytes); + final Path filePathAsPath = getFilePath(src); + return Files.readAllBytes(filePathAsPath); } + + public static long write(final InputStream src, final String dst) throws IOException + { + final Path filePathAsPath = getFilePath(dst); + return Files.copy(src, filePathAsPath); + } + + public static OutputStream newOutputStream(String dst) throws IOException + { + final Path filePathAsPath = getFilePath(dst); + return Files.newOutputStream(filePathAsPath); + } + } diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadServiceServlet.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadServiceServlet.java index d845b16a11d..2c1a170dd54 100644 --- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadServiceServlet.java +++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/UploadServiceServlet.java @@ -127,7 +127,7 @@ public final class UploadServiceServlet extends AbstractController // @Override - @RequestMapping({ "/upload", "/openbis/upload" }) + @RequestMapping({ "/upload", "/openbis/upload", "/openbis/openbis/upload" }) protected ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) throws Exception { diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/ISessionWorkspaceProvider.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/ISessionWorkspaceProvider.java index 7ed1f3ff068..736746dde6b 100644 --- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/ISessionWorkspaceProvider.java +++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/ISessionWorkspaceProvider.java @@ -31,6 +31,8 @@ public interface ISessionWorkspaceProvider File getSessionWorkspace(String sessionToken); + File getCanonicalFile(String sessionToken, String relativePathToFile) throws IOException; + void deleteSessionWorkspace(String sessionToken); void write(String sessionToken, String relativePathToFile, InputStream inputStream) throws IOException; @@ -39,6 +41,8 @@ public interface ISessionWorkspaceProvider InputStream read(String sessionToken, String relativePathToFile) throws IOException; + byte[] readAllBytes(String sessionToken, String relativePathToFile) throws IOException; + void delete(String sessionToken, String relativePathToFile) throws IOException; } diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/SessionWorkspaceProvider.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/SessionWorkspaceProvider.java index b33d92b9ccf..a66afc20af4 100644 --- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/SessionWorkspaceProvider.java +++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/shared/SessionWorkspaceProvider.java @@ -133,6 +133,14 @@ public class SessionWorkspaceProvider implements ISessionWorkspaceProvider return sessionWorkspace; } + @Override + public File getCanonicalFile(String sessionToken, String relativePathToFile) throws IOException + { + File sessionWorkspace = getSessionWorkspace(sessionToken); + File targetFile = new File(sessionWorkspace, relativePathToFile); + return targetFile.getCanonicalFile(); + } + @Override public void deleteSessionWorkspace(String sessionTokenOrPAT) { @@ -156,6 +164,7 @@ public class SessionWorkspaceProvider implements ISessionWorkspaceProvider public void write(String sessionToken, String relativePathToFile, InputStream inputStream) throws IOException { File sessionWorkspace = getSessionWorkspace(sessionToken); File targetFile = new File(sessionWorkspace, relativePathToFile); + targetFile.getParentFile().mkdirs(); Files.copy(inputStream, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } @@ -172,6 +181,13 @@ public class SessionWorkspaceProvider implements ISessionWorkspaceProvider return Files.newInputStream(targetFile.toPath()); } + @Override + public byte[] readAllBytes(String sessionToken, String relativePathToFile) throws IOException { + File sessionWorkspace = getSessionWorkspace(sessionToken); + File targetFile = new File(sessionWorkspace, relativePathToFile); + return Files.readAllBytes(targetFile.toPath()); + } + @Override public void delete(String sessionToken, String relativePathToFile) throws IOException { File sessionWorkspace = getSessionWorkspace(sessionToken); diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractImportTest.java b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractImportTest.java index f67f997478f..21abc7ef30e 100644 --- a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractImportTest.java +++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractImportTest.java @@ -18,13 +18,18 @@ package ch.ethz.sis.openbis.systemtest.asapi.v3; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; +import java.util.Arrays; +import java.util.UUID; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeSuite; +import ch.systemsx.cisd.openbis.generic.server.CommonServiceProvider; +import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider; + public class AbstractImportTest extends AbstractTest { @@ -59,20 +64,26 @@ public class AbstractImportTest extends AbstractTest v3api.logout(sessionToken); } - protected static byte[] getFileContent(final String fileName) + protected static String[] uploadToAsSessionWorkspace(final String sessionToken, final String... relativeFilePaths) throws IOException { - try (final InputStream is = AbstractImportTest.class.getResourceAsStream("test_files/import/" + fileName)) - { - if (is == null) - { - throw new RuntimeException(); - } + final String[] canonicalFilePaths = getFilePaths(relativeFilePaths); + final ISessionWorkspaceProvider sessionWorkspaceProvider = CommonServiceProvider.getSessionWorkspaceProvider(); + final String uploadId = UUID.randomUUID().toString(); + final String[] destinations = new String[canonicalFilePaths.length]; - return is.readAllBytes(); - } catch (final IOException e) + for (int i = 0; i < canonicalFilePaths.length; i++) { - throw new RuntimeException(e); + destinations[i] = uploadId + "/" + relativeFilePaths[i]; + sessionWorkspaceProvider.write(sessionToken, destinations[i], new FileInputStream(canonicalFilePaths[i])); } + + return destinations; + } + + private static String[] getFilePaths(final String... fileNames) + { + return Arrays.stream(fileNames).map(fileName -> AbstractImportTest.class.getResource("test_files/import/" + fileName).getPath()) + .toArray(String[]::new); } } diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractTest.java b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractTest.java index 972d9eced25..b229afb708d 100644 --- a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractTest.java +++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/AbstractTest.java @@ -20,6 +20,9 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.fail; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.lang.reflect.Method; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -161,6 +164,8 @@ import ch.systemsx.cisd.common.exceptions.AuthorizationFailureException; import ch.systemsx.cisd.common.exceptions.UserFailureException; import ch.systemsx.cisd.common.logging.BufferedAppender; import ch.systemsx.cisd.common.test.AssertionUtil; +import ch.systemsx.cisd.openbis.generic.server.CommonServiceProvider; +import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider; import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialIdentifier; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.RoleWithHierarchy; diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/UncompressedImportTest.java b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/UncompressedImportTest.java index 534803e5b09..94ebd7a52ee 100644 --- a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/UncompressedImportTest.java +++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/UncompressedImportTest.java @@ -16,8 +16,8 @@ package ch.ethz.sis.openbis.systemtest.asapi.v3; +import static org.junit.Assert.assertNotNull; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import java.util.Arrays; @@ -25,21 +25,17 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import org.testng.annotations.Test; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSetType; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.DataSetTypeFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.search.DataSetTypeSearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.ExperimentType; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.fetchoptions.ExperimentTypeFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.search.ExperimentTypeSearchCriteria; -import ch.systemsx.cisd.common.action.IDelegatedAction; -import org.testng.annotations.Test; - -import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.IImportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportData; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportFormat; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportScript; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportValue; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.UncompressedImportData; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportMode; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.plugin.Plugin; @@ -55,15 +51,18 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.Vocabulary; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.VocabularyTerm; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.fetchoptions.VocabularyFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.search.VocabularySearchCriteria; +import ch.systemsx.cisd.common.action.IDelegatedAction; import ch.systemsx.cisd.common.exceptions.UserFailureException; public class UncompressedImportTest extends AbstractImportTest { @Test - public void testDataImport() + public void testDataImport() throws Exception { - final IImportData importData = new UncompressedImportData(ImportFormat.XLS, getFileContent("import.xlsx"), null, null); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "import.xlsx"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); v3api.executeImport(sessionToken, importData, importOptions); @@ -85,10 +84,11 @@ public class UncompressedImportTest extends AbstractImportTest } @Test - public void testLargeDataImport() + public void testLargeDataImport() throws Exception { - final UncompressedImportData importData = new UncompressedImportData(ImportFormat.XLS, getFileContent("import_large_cell.xlsx"), null, - List.of(new ImportValue("value-M20.txt", new String(getFileContent("data/value-M20.txt"))))); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "import_large_cell.xlsx", "data/value-M20.txt"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles[0]); final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); v3api.executeImport(sessionToken, importData, importOptions); @@ -110,9 +110,11 @@ public class UncompressedImportTest extends AbstractImportTest } @Test - public void testImportOptionsUpdateIfExists() + public void testImportOptionsUpdateIfExists() throws Exception { - final IImportData importData = new UncompressedImportData(ImportFormat.XLS, getFileContent("existing_vocabulary.xlsx"), null, null); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "existing_vocabulary.xlsx"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); v3api.executeImport(sessionToken, importData, importOptions); @@ -137,9 +139,11 @@ public class UncompressedImportTest extends AbstractImportTest } @Test - public void testImportOptionsIgnoreExisting() + public void testImportOptionsIgnoreExisting() throws Exception { - final IImportData importData = new UncompressedImportData(ImportFormat.XLS, getFileContent("existing_vocabulary.xlsx"), null, null); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "existing_vocabulary.xlsx"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); final ImportOptions importOptions = new ImportOptions(ImportMode.IGNORE_EXISTING); v3api.executeImport(sessionToken, importData, importOptions); @@ -164,21 +168,23 @@ public class UncompressedImportTest extends AbstractImportTest } @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = ".*FAIL_IF_EXISTS.*") - public void testImportOptionsFailIfExists() + public void testImportOptionsFailIfExists() throws Exception { - final IImportData importData = new UncompressedImportData(ImportFormat.XLS, getFileContent("existing_vocabulary.xlsx"), null, null); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "existing_vocabulary.xlsx"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); final ImportOptions importOptions = new ImportOptions(ImportMode.FAIL_IF_EXISTS); v3api.executeImport(sessionToken, importData, importOptions); } @Test - public void testWithValidationScript() + public void testWithValidationScript() throws Exception { final String name = "valid.py"; final String source = "print 'Test validation script'"; - final IImportData importData = new UncompressedImportData(ImportFormat.XLS, getFileContent("validation_script.xls"), - List.of(new ImportScript(name, source)), null); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "validation_script.xls", "scripts/" + name); + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles[0]); final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); v3api.executeImport(sessionToken, importData, importOptions); @@ -203,12 +209,12 @@ public class UncompressedImportTest extends AbstractImportTest } @Test - public void testWithDynamicScript() + public void testWithDynamicScript() throws Exception { final String name = "dynamic.py"; final String source = "1+1"; - final IImportData importData = new UncompressedImportData(ImportFormat.XLS, getFileContent("dynamic_script.xls"), - List.of(new ImportScript(name, source)), null); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "dynamic_script.xls", "scripts/" + name); + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles[0]); final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); v3api.executeImport(sessionToken, importData, importOptions); @@ -238,9 +244,11 @@ public class UncompressedImportTest extends AbstractImportTest } @Test - public void testInternalTypes_failForNormalUser() + public void testInternalTypes_failForNormalUser() throws Exception { - final IImportData importData = new UncompressedImportData(ImportFormat.XLS, getFileContent("import_internal_type.xlsx"), null, null); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "import_internal_type.xlsx"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); assertUserFailureException(new IDelegatedAction() @@ -255,13 +263,15 @@ public class UncompressedImportTest extends AbstractImportTest } @Test - public void testInternalTypes() + public void testInternalTypes() throws Exception { - final IImportData importData = new UncompressedImportData(ImportFormat.XLS, getFileContent("import_internal_type.xlsx"), null, null); - final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); - final String systemSessionToken = v3api.loginAsSystem(); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(systemSessionToken, "import_internal_type.xlsx"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); + final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); + v3api.executeImport(systemSessionToken, importData, importOptions); final DataSetTypeSearchCriteria dataSetTypeSearchCriteria = new DataSetTypeSearchCriteria(); diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/ZipImportTest.java b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/ZipImportTest.java index 43807a6b2b4..d2196dd938d 100644 --- a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/ZipImportTest.java +++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/ZipImportTest.java @@ -25,19 +25,17 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import org.testng.annotations.Test; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSetType; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.DataSetTypeFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.search.DataSetTypeSearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.ExperimentType; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.fetchoptions.ExperimentTypeFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.search.ExperimentTypeSearchCriteria; -import ch.systemsx.cisd.common.action.IDelegatedAction; -import org.testng.annotations.Test; - -import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.IImportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportData; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ImportFormat; -import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.data.ZipImportData; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportMode; import ch.ethz.sis.openbis.generic.asapi.v3.dto.importer.options.ImportOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.plugin.Plugin; @@ -53,15 +51,18 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.Vocabulary; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.VocabularyTerm; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.fetchoptions.VocabularyFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.search.VocabularySearchCriteria; +import ch.systemsx.cisd.common.action.IDelegatedAction; import ch.systemsx.cisd.common.exceptions.UserFailureException; public class ZipImportTest extends AbstractImportTest { @Test - public void testDataImport() + public void testDataImport() throws Exception { - final IImportData importData = new ZipImportData(ImportFormat.XLS, getFileContent("import.zip")); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "import.zip"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); v3api.executeImport(sessionToken, importData, importOptions); @@ -82,9 +83,11 @@ public class ZipImportTest extends AbstractImportTest } @Test - public void testDataImportInternalTypes_failForNormalUser() + public void testDataImportInternalTypes_failForNormalUser() throws Exception { - final IImportData importData = new ZipImportData(ImportFormat.XLS, getFileContent("import_internal_type.zip")); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "import_internal_type.zip"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); assertUserFailureException(new IDelegatedAction() @@ -99,13 +102,15 @@ public class ZipImportTest extends AbstractImportTest } @Test - public void testDataImportInternalTypes_systemUser() + public void testDataImportInternalTypes_systemUser() throws Exception { - final IImportData importData = new ZipImportData(ImportFormat.XLS, getFileContent("import_internal_type.zip")); - final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); - final String systemSessionToken = v3api.loginAsSystem(); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(systemSessionToken, "import_internal_type.zip"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); + final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); + v3api.executeImport(systemSessionToken, importData, importOptions); final DataSetTypeSearchCriteria dataSetTypeSearchCriteria = new DataSetTypeSearchCriteria(); @@ -130,7 +135,6 @@ public class ZipImportTest extends AbstractImportTest assertEquals(sampleTypeSearchResult.getTotalCount(), 1); assertEquals(sampleTypeSearchResult.getObjects().get(0).getDescription(), "Internal Sample Type"); - final ExperimentTypeSearchCriteria experimentTypeSearchCriteria = new ExperimentTypeSearchCriteria(); experimentTypeSearchCriteria.withCode().thatEquals("$INTERNAL_EXPERIMENT_TYPE"); @@ -144,9 +148,11 @@ public class ZipImportTest extends AbstractImportTest } @Test - public void testLargeDataImport() + public void testLargeDataImport() throws Exception { - final IImportData importData = new ZipImportData(ImportFormat.XLS, getFileContent("import_large_cell.zip")); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "import_large_cell.zip"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); v3api.executeImport(sessionToken, importData, importOptions); @@ -168,9 +174,11 @@ public class ZipImportTest extends AbstractImportTest } @Test - public void testImportOptionsUpdateIfExists() + public void testImportOptionsUpdateIfExists() throws Exception { - final IImportData importData = new ZipImportData(ImportFormat.XLS, getFileContent("existing_vocabulary.zip")); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "existing_vocabulary.zip"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); v3api.executeImport(sessionToken, importData, importOptions); @@ -195,9 +203,11 @@ public class ZipImportTest extends AbstractImportTest } @Test - public void testImportOptionsIgnoreExisting() + public void testImportOptionsIgnoreExisting() throws Exception { - final IImportData importData = new ZipImportData(ImportFormat.XLS, getFileContent("existing_vocabulary.zip")); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "existing_vocabulary.zip"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); final ImportOptions importOptions = new ImportOptions(ImportMode.IGNORE_EXISTING); v3api.executeImport(sessionToken, importData, importOptions); @@ -222,20 +232,24 @@ public class ZipImportTest extends AbstractImportTest } @Test(expectedExceptions = UserFailureException.class, expectedExceptionsMessageRegExp = ".*FAIL_IF_EXISTS.*") - public void testImportOptionsFailIfExists() + public void testImportOptionsFailIfExists() throws Exception { - final IImportData importData = new ZipImportData(ImportFormat.XLS, getFileContent("existing_vocabulary.zip")); + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "existing_vocabulary.zip"); + + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); final ImportOptions importOptions = new ImportOptions(ImportMode.FAIL_IF_EXISTS); v3api.executeImport(sessionToken, importData, importOptions); } @Test - public void testWithValidationScript() + public void testWithValidationScript() throws Exception { + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "validation_script.zip"); + final String name = "valid.py"; final String source = "print 'Test validation script'"; - final IImportData importData = new ZipImportData(ImportFormat.XLS, getFileContent("validation_script.zip")); + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); v3api.executeImport(sessionToken, importData, importOptions); @@ -260,11 +274,13 @@ public class ZipImportTest extends AbstractImportTest } @Test - public void testWithDynamicScript() + public void testWithDynamicScript() throws Exception { + final String[] sessionWorkspaceFiles = uploadToAsSessionWorkspace(sessionToken, "dynamic_script.zip"); + final String name = "dynamic.py"; final String source = "1+1"; - final IImportData importData = new ZipImportData(ImportFormat.XLS, getFileContent("dynamic_script.zip")); + final ImportData importData = new ImportData(ImportFormat.EXCEL, sessionWorkspaceFiles); final ImportOptions importOptions = new ImportOptions(ImportMode.UPDATE_IF_EXISTS); v3api.executeImport(sessionToken, importData, importOptions); diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/test_files/import/scripts/dynamic.py b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/test_files/import/scripts/dynamic.py index c040fa67d34..07d91dc6abd 100644 --- a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/test_files/import/scripts/dynamic.py +++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/test_files/import/scripts/dynamic.py @@ -1 +1 @@ -1+1 +1+1 \ No newline at end of file diff --git a/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/test_files/import/scripts/valid.py b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/test_files/import/scripts/valid.py new file mode 100644 index 00000000000..d23f9285861 --- /dev/null +++ b/server-application-server/sourceTest/java/ch/ethz/sis/openbis/systemtest/asapi/v3/test_files/import/scripts/valid.py @@ -0,0 +1 @@ +print 'Test validation script' \ No newline at end of file diff --git a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js index aa489331b1f..26d12018fc5 100644 --- a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js +++ b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/common.js @@ -1288,6 +1288,26 @@ define([ 'jquery', 'underscore'], function($, _) { this.assert.ok(false, msg); }; + this.base64ToBlob = (b64Data, contentType='', sliceSize=512) => { + var byteCharacters = atob(b64Data); + var byteArrays = []; + + for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) { + var slice = byteCharacters.slice(offset, offset + sliceSize); + + var byteNumbers = new Array(slice.length); + for (var i = 0; i < slice.length; i++) { + byteNumbers[i] = slice.charCodeAt(i); + } + + var byteArray = new Uint8Array(byteNumbers); + byteArrays.push(byteArray); + } + + var blob = new Blob(byteArrays, {type: contentType}); + return blob; + } + }; return Common; diff --git a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js index 8d498fe2aed..64d55203476 100644 --- a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js +++ b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/dtos.js @@ -58,12 +58,9 @@ var fullTypes = [ "as/dto/importer/options/ImportMode", "as/dto/importer/ImportOperation", "as/dto/importer/ImportOperationResult", +"as/dto/importer/ImportResult", +"as/dto/importer/data/ImportData", "as/dto/importer/data/ImportFormat", -"as/dto/importer/data/ZipImportData", -"as/dto/importer/data/UncompressedImportData", -"as/dto/importer/data/ImportValue", -"as/dto/importer/data/ImportScript", -"as/dto/importer/data/IImportData", "as/dto/entitytype/update/IEntityTypeUpdate", "as/dto/entitytype/update/PropertyAssignmentListUpdateValue", "as/dto/entitytype/EntityKind", diff --git a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/openbis-execute-operations.js b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/openbis-execute-operations.js index 8f57a107fb9..e17ce32cf2a 100644 --- a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/openbis-execute-operations.js +++ b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/openbis-execute-operations.js @@ -709,7 +709,7 @@ define([], function() { this.executeImport = function (importData, importOptions) { return this._executeOperation(new dtos.ImportOperation(importData, importOptions)).then(function (results) { - return results.getResults()[0] + return results.getResults()[0].getImportResult() }) } diff --git a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-import-export.ts b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-import-export.ts index df54153e4ff..b6b7f5065f7 100644 --- a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-import-export.ts +++ b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-import-export.ts @@ -15,10 +15,10 @@ * */ -import jquery from "./types/jquery" -import underscore from "./types/underscore" -import common from "./types/common" -import openbis from "./types/openbis.esm" +import jquery from './types/jquery' +import underscore from './types/underscore' +import common from './types/common' +import openbis from './types/openbis.esm' exports.default = new Promise((resolve) => { require(["jquery", "underscore", "openbis", "test/common", "test/openbis-execute-operations", "test/dtos"], function ( @@ -29,62 +29,7 @@ exports.default = new Promise((resolve) => { openbisExecuteOperations, dtos ) { - var fileContent = - "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" + var fileContent = "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" var executeModule = function (moduleName: string, facade: openbis.openbis, dtos: openbis.bundle) { QUnit.module(moduleName) @@ -117,14 +62,23 @@ exports.default = new Promise((resolve) => { var c = new common(assert, dtos) var fAction = function (facade: openbis.openbis) { - var importData = new dtos.UncompressedImportData() - importData.setFormat("XLS") - importData.setFile(fileContent) - - var importOptions = new dtos.ImportOptions() - importOptions.setMode("UPDATE_IF_EXISTS") - - return facade.executeImport(importData, importOptions) + var fileName = "model.xlsx" + var data = new window.File([c.base64ToBlob(fileContent)], fileName) + return facade.uploadToSessionWorkspace(data) + .then(function() { + c.ok("uploadToSessionWorkspace") + var importData = new dtos.ImportData() + importData.setFormat("EXCEL") + importData.setSessionWorkspaceFiles([fileName]) + + var importOptions = new dtos.ImportOptions() + importOptions.setMode("UPDATE_IF_EXISTS") + + return facade.executeImport(importData, importOptions) + .then(function() { + c.ok("executeImport") + }) + }) } var fCheck = function (facade: openbis.openbis) { diff --git a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/types/common.d.ts b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/types/common.d.ts index e05938aadc3..eea599e1d41 100644 --- a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/types/common.d.ts +++ b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/types/common.d.ts @@ -152,5 +152,6 @@ export namespace common { ok(msg?): void section(msg): void fail(msg?): void + base64ToBlob(b64Data: string, contentType: string='', sliceSize: number=512): Blob } } diff --git a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/types/openbis.esm.d.ts b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/types/openbis.esm.d.ts index 6add41dd7ec..7f6dc21ff7e 100644 --- a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/types/openbis.esm.d.ts +++ b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/types/openbis.esm.d.ts @@ -11139,9 +11139,6 @@ export namespace openbis { getIdentifier(): ObjectIdentifier; } - interface IImportData extends Serializable { - } - interface ILabelHolder { getLabel(): string; @@ -11619,12 +11616,32 @@ export namespace openbis { new <T extends IObjectId>(): IdsSearchCriteria<T>; } + interface ImportData extends Serializable { + + getFormat(): ImportFormat; + + getSessionWorkspaceFiles(): string[]; + + setFormat(arg0: ImportFormat): void; + + setSessionWorkspaceFiles(arg0: string[]): void; + } + + /** + */ + interface ImportDataConstructor { + + new (): ImportData; + + new (arg0: ImportFormat, arg1: string[]): ImportData; + } + /** */ interface ImportFormatObject { /** */ - XLS: ImportFormat<> = "XLS"; + EXCEL: ImportFormat<> = "EXCEL"; } /** @@ -11643,13 +11660,13 @@ export namespace openbis { interface ImportOperation extends Serializable, IOperation { - getImportData(): IImportData; + getImportData(): ImportData; getImportOptions(): ImportOptions; getMessage(): string; - setImportData(arg0: IImportData): void; + setImportData(arg0: ImportData): void; setImportOptions(arg0: ImportOptions): void; } @@ -11660,12 +11677,16 @@ export namespace openbis { new (): ImportOperation; - new (arg0: IImportData, arg1: ImportOptions): ImportOperation; + new (arg0: ImportData, arg1: ImportOptions): ImportOperation; } interface ImportOperationResult extends Serializable, as_dto_common_operation_IOperationResult { + getImportResult(): ImportResult; + getMessage(): string; + + setImportResult(arg0: ImportResult): void; } /** @@ -11673,6 +11694,8 @@ export namespace openbis { interface ImportOperationResultConstructor { new (): ImportOperationResult; + + new (arg0: ImportResult): ImportOperationResult; } interface ImportOptions extends Serializable { @@ -11691,44 +11714,20 @@ export namespace openbis { new (arg0: ImportMode): ImportOptions; } - interface ImportScript extends Serializable { - - getName(): string; - - getSource(): string; - - setName(arg0: string): void; - - setSource(arg0: string): void; - } - - /** - */ - interface ImportScriptConstructor { - - new (): ImportScript; - - new (arg0: string, arg1: string): ImportScript; - } + interface ImportResult extends Serializable { - interface ImportValue extends Serializable { - - getName(): string; - - getValue(): string; - - setName(arg0: string): void; + getObjectIds(): IObjectId[]; - setValue(arg0: string): void; + setObjectIds(arg0: IObjectId[]): void; } /** */ - interface ImportValueConstructor { + interface ImportResultConstructor { - new (): ImportValue; + new (): ImportResult; - new (arg0: string, arg1: string): ImportValue; + new (arg0: IObjectId[]): ImportResult; } interface LabelSearchCriteria extends StringFieldSearchCriteria { @@ -13851,7 +13850,7 @@ export namespace openbis { executeExport(arg1: ExportData, arg2: ExportOptions): Promise<ExportResult>; - executeImport(arg1: IImportData, arg2: ImportOptions): Promise<void>; + executeImport(arg1: ImportData, arg2: ImportOptions): Promise<ImportResult>; executeOperations(arg1: IOperation[], arg2: IOperationExecutionOptions): Promise<IOperationExecutionResults>; @@ -14068,6 +14067,8 @@ export namespace openbis { updateVocabularies(arg1: VocabularyUpdate[]): Promise<void>; updateVocabularyTerms(arg1: VocabularyTermUpdate[]): Promise<void>; + + uploadToSessionWorkspace(arg0: any): Promise<void>; } /** @@ -23027,34 +23028,6 @@ export namespace openbis { new (): UnarchiveDataSetsOperationResult; } - interface UncompressedImportData extends Serializable, IImportData { - - getFile(): string; - - getFormat(): ImportFormat; - - getImportValues(): ImportValue[]; - - getScripts(): ImportScript[]; - - setFile(arg0: string): void; - - setFormat(arg0: ImportFormat): void; - - setImportValues(arg0: ImportValue[]): void; - - setScripts(arg0: ImportScript[]): void; - } - - /** - */ - interface UncompressedImportDataConstructor { - - new (): UncompressedImportData; - - new (arg0: ImportFormat, arg1: string, arg2: ImportScript[], arg3: ImportValue[]): UncompressedImportData; - } - interface UnknownRelatedObjectId extends IObjectId { getRelatedObjectId(): string; @@ -24590,26 +24563,6 @@ export namespace openbis { RICH: XlsTextFormat<> = "RICH"; } - interface ZipImportData extends Serializable, IImportData { - - getFile(): string; - - getFormat(): ImportFormat; - - setFile(arg0: string): void; - - setFormat(arg0: ImportFormat): void; - } - - /** - */ - interface ZipImportDataConstructor { - - new (): ZipImportData; - - new (arg0: ImportFormat, arg1: string): ZipImportData; - } - interface as_dto_common_operation_IOperationResult extends Serializable { getMessage(): string; @@ -25182,13 +25135,13 @@ export namespace openbis { IdSearchCriteria: IdSearchCriteriaConstructor; IdentifierSearchCriteria: IdentifierSearchCriteriaConstructor; IdsSearchCriteria: IdsSearchCriteriaConstructor; + ImportData: ImportDataConstructor; ImportFormat: ImportFormatObject; ImportMode: ImportModeObject; ImportOperation: ImportOperationConstructor; ImportOperationResult: ImportOperationResultConstructor; ImportOptions: ImportOptionsConstructor; - ImportScript: ImportScriptConstructor; - ImportValue: ImportValueConstructor; + ImportResult: ImportResultConstructor; LabelSearchCriteria: LabelSearchCriteriaConstructor; LastNameSearchCriteria: LastNameSearchCriteriaConstructor; LinkedData: LinkedDataConstructor; @@ -25571,7 +25524,6 @@ export namespace openbis { TimeZone: TimeZoneConstructor; UnarchiveDataSetsOperation: UnarchiveDataSetsOperationConstructor; UnarchiveDataSetsOperationResult: UnarchiveDataSetsOperationResultConstructor; - UncompressedImportData: UncompressedImportDataConstructor; UnknownRelatedObjectId: UnknownRelatedObjectIdConstructor; UnlockDataSetsOperation: UnlockDataSetsOperationConstructor; UnlockDataSetsOperationResult: UnlockDataSetsOperationResultConstructor; @@ -25649,7 +25601,6 @@ export namespace openbis { WebAppSettingsSortOptions: WebAppSettingsSortOptionsConstructor; WebAppSettingsUpdateValue: WebAppSettingsUpdateValueConstructor; XlsTextFormat: XlsTextFormatObject; - ZipImportData: ZipImportDataConstructor; as_dto_attachment_Attachment: AttachmentConstructor; as_dto_attachment_create_AttachmentCreation: AttachmentCreationConstructor; as_dto_attachment_fetchoptions_AttachmentFetchOptions: AttachmentFetchOptionsConstructor; @@ -26024,11 +25975,9 @@ export namespace openbis { as_dto_history_id_UnknownRelatedObjectId: UnknownRelatedObjectIdConstructor; as_dto_importer_ImportOperation: ImportOperationConstructor; as_dto_importer_ImportOperationResult: ImportOperationResultConstructor; + as_dto_importer_ImportResult: ImportResultConstructor; + as_dto_importer_data_ImportData: ImportDataConstructor; as_dto_importer_data_ImportFormat: ImportFormatObject; - as_dto_importer_data_ImportScript: ImportScriptConstructor; - as_dto_importer_data_ImportValue: ImportValueConstructor; - as_dto_importer_data_UncompressedImportData: UncompressedImportDataConstructor; - as_dto_importer_data_ZipImportData: ZipImportDataConstructor; as_dto_importer_options_ImportMode: ImportModeObject; as_dto_importer_options_ImportOptions: ImportOptionsConstructor; as_dto_material_Material: MaterialConstructor; @@ -26695,7 +26644,7 @@ MATERIAL : "MATERIAL", SAMPLE : "SAMPLE"} as const const ImportFormat = { -XLS : "XLS"} as const +EXCEL : "EXCEL"} as const const ImportMode = { FAIL_IF_EXISTS : "FAIL_IF_EXISTS", @@ -26979,7 +26928,7 @@ MATERIAL : "MATERIAL", SAMPLE : "SAMPLE"} as const const as_dto_importer_data_ImportFormat = { -XLS : "XLS"} as const +EXCEL : "EXCEL"} as const const as_dto_importer_options_ImportMode = { FAIL_IF_EXISTS : "FAIL_IF_EXISTS", @@ -27882,15 +27831,15 @@ SAMPLE : "SAMPLE"} as const export const IdsSearchCriteria:IdsSearchCriteriaConstructor + export const ImportData:ImportDataConstructor + export const ImportOperation:ImportOperationConstructor export const ImportOperationResult:ImportOperationResultConstructor export const ImportOptions:ImportOptionsConstructor - export const ImportScript:ImportScriptConstructor - - export const ImportValue:ImportValueConstructor + export const ImportResult:ImportResultConstructor export const LabelSearchCriteria:LabelSearchCriteriaConstructor @@ -28624,8 +28573,6 @@ SAMPLE : "SAMPLE"} as const export const UnarchiveDataSetsOperationResult:UnarchiveDataSetsOperationResultConstructor - export const UncompressedImportData:UncompressedImportDataConstructor - export const UnknownRelatedObjectId:UnknownRelatedObjectIdConstructor export const UnlockDataSetsOperation:UnlockDataSetsOperationConstructor @@ -28778,8 +28725,6 @@ SAMPLE : "SAMPLE"} as const export const WebAppSettingsUpdateValue:WebAppSettingsUpdateValueConstructor - export const ZipImportData:ZipImportDataConstructor - export const as_dto_attachment_Attachment:AttachmentConstructor export const as_dto_attachment_create_AttachmentCreation:AttachmentCreationConstructor @@ -29490,13 +29435,9 @@ SAMPLE : "SAMPLE"} as const export const as_dto_importer_ImportOperationResult:ImportOperationResultConstructor - export const as_dto_importer_data_ImportScript:ImportScriptConstructor - - export const as_dto_importer_data_ImportValue:ImportValueConstructor - - export const as_dto_importer_data_UncompressedImportData:UncompressedImportDataConstructor + export const as_dto_importer_ImportResult:ImportResultConstructor - export const as_dto_importer_data_ZipImportData:ZipImportDataConstructor + export const as_dto_importer_data_ImportData:ImportDataConstructor export const as_dto_importer_options_ImportOptions:ImportOptionsConstructor @@ -31466,17 +31407,11 @@ SAMPLE : "SAMPLE"} as const type as_dto_importer_ImportOperationResult = ImportOperationResult - type as_dto_importer_data_IImportData = IImportData + type as_dto_importer_ImportResult = ImportResult - type as_dto_importer_data_ImportFormat = typeof as_dto_importer_data_ImportFormat[keyof typeof as_dto_importer_data_ImportFormat] - - type as_dto_importer_data_ImportScript = ImportScript - - type as_dto_importer_data_ImportValue = ImportValue + type as_dto_importer_data_ImportData = ImportData - type as_dto_importer_data_UncompressedImportData = UncompressedImportData - - type as_dto_importer_data_ZipImportData = ZipImportData + type as_dto_importer_data_ImportFormat = typeof as_dto_importer_data_ImportFormat[keyof typeof as_dto_importer_data_ImportFormat] type as_dto_importer_options_ImportMode = typeof as_dto_importer_options_ImportMode[keyof typeof as_dto_importer_options_ImportMode] diff --git a/test-integration/sourceTest/java/ch/ethz/sis/openbis/systemtests/IntegrationSessionWorkspaceTest.java b/test-integration/sourceTest/java/ch/ethz/sis/openbis/systemtests/IntegrationSessionWorkspaceTest.java new file mode 100644 index 00000000000..49768482efe --- /dev/null +++ b/test-integration/sourceTest/java/ch/ethz/sis/openbis/systemtests/IntegrationSessionWorkspaceTest.java @@ -0,0 +1,90 @@ +package ch.ethz.sis.openbis.systemtests; + +import static ch.ethz.sis.transaction.TransactionTestUtil.TestTransaction; +import static ch.ethz.sis.transaction.TransactionTestUtil.assertTransactions; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.File; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import javax.sql.DataSource; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +import ch.ethz.sis.openbis.generic.OpenBIS; +import ch.ethz.sis.openbis.generic.asapi.v3.ITransactionCoordinatorApi; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id.EntityTypePermId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.Experiment; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.create.ExperimentCreation; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.fetchoptions.ExperimentFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id.ExperimentPermId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.Project; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.create.ProjectCreation; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.fetchoptions.ProjectFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.id.ProjectIdentifier; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.id.ProjectPermId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.Space; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.create.SpaceCreation; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.fetchoptions.SpaceFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.id.SpacePermId; +import ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.fetchoptions.DataSetFileFetchOptions; +import ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.search.DataSetFileSearchCriteria; +import ch.ethz.sis.openbis.generic.server.asapi.v3.TransactionConfiguration; +import ch.ethz.sis.openbis.generic.server.asapi.v3.TransactionCoordinatorApi; +import ch.ethz.sis.openbis.systemtests.common.AbstractIntegrationTest; +import ch.ethz.sis.transaction.TransactionStatus; + +public class IntegrationSessionWorkspaceTest extends AbstractIntegrationTest +{ + + @Test + public void testUploadToSessionWorkspace() throws Exception + { + final OpenBIS openBIS = createOpenBIS(); + final String sessionToken = openBIS.login(USER, PASSWORD); + + final Path originalFilePath = Path.of("sourceTest/java/tests.xml"); + + // Testing upload + + final String uploadId = openBIS.uploadToSessionWorkspace(originalFilePath); + + // Verifying upload ID + + assertTrue(uploadId.endsWith("tests.xml")); + + // Verifying file info + + final Path uploadedFilePath = Path.of(String.format("targets/sessionWorkspace/%s/tests.xml", sessionToken)); + final File originalFile = originalFilePath.toFile(); + final File uploadedFile = uploadedFilePath.toFile(); + + assertTrue(uploadedFile.exists()); + assertEquals(uploadedFile.length(), originalFile.length()); + + // Verifying file content + + final byte[] originalFileContent = Files.readAllBytes(originalFilePath); + final byte[] uploadedFileContent = Files.readAllBytes(uploadedFilePath); + + assertEquals(uploadedFileContent, originalFileContent); + + openBIS.logout(); + } + +} 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 dce1366af7d..441ff4576e0 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,19 +1,11 @@ -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 -from ch.ethz.sis.openbis.generic.server.asapi.v3.executor.importer import ImportExecutor -def get_update_mode(parameters): - update_mode = parameters.get('update_mode', 'FAIL_IF_EXISTS') +def getMode(parameters): + update_mode = parameters.get('mode', 'FAIL_IF_EXISTS') if update_mode == "IGNORE_EXISTING": return ImportModes.IGNORE_EXISTING elif update_mode == "FAIL_IF_EXISTS": @@ -25,20 +17,16 @@ def get_update_mode(parameters): str(update_mode) if update_mode else 'None')) -def get_import_options(parameters): +def getImportOptions(parameters): options = ImportOptions() - experiments_by_type = parameters.get('experiments_by_type', None) - options.setExperimentsByType(experiments_by_type) - spaces_by_type = parameters.get('spaces_by_type', None) - options.setSpacesByType(spaces_by_type) - definitions_only = parameters.get('definitions_only', False) - options.setDefinitionsOnly(definitions_only) - disallow_creations = parameters.get("disallow_creations", False) - options.setDisallowEntityCreations(disallow_creations) - ignore_versioning = parameters.get('ignore_versioning', False) - options.setIgnoreVersioning(ignore_versioning) - render_result = parameters.get('render_result', True) - options.setRenderResult(render_result) + allowedSampleTypes = parameters.get("allowedSampleTypes") + + options.setExperimentsByType(parameters.get('experimentsByType', None)) + options.setSpacesByType(parameters.get('spacesByType', None)) + options.setDefinitionsOnly(False) + options.setDisallowEntityCreations(False) + options.setIgnoreVersioning(False) + options.setRenderResult(True) return options @@ -47,82 +35,20 @@ def process(context, parameters): result = None if method == "import": - zip = parameters.get('zip', False) - temp = None - zip_bytes = 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) - if (len(temp.listFiles()) == 1): - singleFile = temp.listFiles()[0] - if (singleFile.isDirectory()): - temp = singleFile - tempPath = singleFile.getAbsolutePath() - byteArrays = MasterDataRegistrationHelper.getByteArrays(Path.of(tempPath), ".xls") - if len(byteArrays) == 0: - raise UserFailureException('No .xls or .xlsx files found on the root folder of the zip file. This error could be caused by the way the zip file was generated.') - parameters.put('xls', byteArrays) - allScripts = MasterDataRegistrationHelper.getAllScripts(Path.of(tempPath)) - parameters.put('scripts', allScripts) - largeValues = MasterDataRegistrationHelper.getAllLargeValues(Path.of(tempPath)) - parameters.put('values', largeValues) - 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.put('xls', [ base64.b64decode(xls_base64_string) ]) - result = _import(context, parameters, zip_bytes) - if temp is not None: - FileUtils.deleteDirectory(temp) + result = _import(context, parameters) return result -def _import(context, parameters, zip_bytes): - """ - Excel import AS service. - For extensive documentation of usage and Excel layout, - please visit https://wiki-bsse.ethz.ch/display/openBISDoc/Excel+import+service +def _import(context, parameters): + fileName = parameters.get("fileName") - :param context: Standard Openbis AS Service context object - :param parameters: Contains two elements - { - 'xls' : excel byte blob, - optional - 'xls_base64' : base64-encoded excel byte blob - optional - 'xls_name': identifier of excel file - mandatory - 'zip' : True / False - optional (default: False) - 'scripts': { - optional - file path: loaded file - }, - 'experiments_by_type', - optional - 'spaces_by_type', - optional - 'definitions_only', - optional (default: False) - 'disallow_creations', - optional (default: False) - 'ignore_versioning', - optional (default: False) - 'render_result', - optional (default: True) - 'update_mode': [IGNORE_EXISTING|FAIL_IF_EXISTS|UPDATE_IF_EXISTS] - optional, default FAIL_IF_EXISTS - This only takes duplicates that are ON THE SERVER - } - :return: Openbis's execute operations result string. It should contain report on what was created. - """ session_token = context.sessionToken api = context.applicationService - scripts = parameters.get('scripts', {}) - values = parameters.get('values', {}) - mode = get_update_mode(parameters) - options = get_import_options(parameters) - xls_name = parameters.get('xls_name', None) + mode = getMode(parameters) + options = getImportOptions(parameters) - importXls = XLSImport(session_token, api, scripts, values, mode, options, xls_name) + importXLS = XLSImport(session_token, api, mode, options, [fileName], False) ids = ArrayList() - xls_byte_arrays = parameters.get('xls', None) - for xls_byte_array in xls_byte_arrays: - ids.addAll(importXls.importXLS(xls_byte_array)) - if zip_bytes is not None: - ImportExecutor.importZipData(zip_bytes) - + ids.addAll(importXLS.start()) return ids diff --git a/ui-admin/src/js/components/tools/form/import/all/ImportAllFormFacade.js b/ui-admin/src/js/components/tools/form/import/all/ImportAllFormFacade.js index c1bf057f5f3..5bf59d1a984 100644 --- a/ui-admin/src/js/components/tools/form/import/all/ImportAllFormFacade.js +++ b/ui-admin/src/js/components/tools/form/import/all/ImportAllFormFacade.js @@ -3,25 +3,19 @@ import openbis from '@src/js/services/openbis.js' export default class ImportAllFormFacade { async import(file, updateMode) { return new Promise((resolve, reject) => { - const reader = new FileReader() - reader.onload = () => { - const resultBase64 = reader.result.split(',')[1] - const serviceId = new openbis.CustomASServiceCode('xls-import') - const serviceOptions = new openbis.CustomASServiceExecutionOptions() - serviceOptions.withParameter('method', 'import') - serviceOptions.withParameter( - 'zip', - file.name.toLowerCase().endsWith('.zip') - ) - serviceOptions.withParameter('xls_name', 'DEFAULT') - serviceOptions.withParameter('xls_base64', resultBase64) - serviceOptions.withParameter('update_mode', updateMode) - openbis.executeService(serviceId, serviceOptions).then(result => { - resolve(result) - }, reject) - } - reader.onerror = reject - reader.readAsDataURL(file) - }) + openbis.uploadToSessionWorkspace(file) + .then(() => { + const serviceId = new openbis.CustomASServiceCode('xls-import') + const serviceOptions = new openbis.CustomASServiceExecutionOptions() + + serviceOptions.withParameter('method', 'import') + serviceOptions.withParameter('mode', updateMode) + serviceOptions.withParameter('fileName', file.name) + + return openbis.executeService(serviceId, serviceOptions) + .then(result => resolve(result)) + .catch(error => reject(error)) + }, (error) => reject(error)); + }); } } diff --git a/ui-admin/src/js/services/openbis/api.js b/ui-admin/src/js/services/openbis/api.js index d79fc89e1d3..40bd2e7e6cc 100644 --- a/ui-admin/src/js/services/openbis/api.js +++ b/ui-admin/src/js/services/openbis/api.js @@ -252,6 +252,10 @@ class Facade { return this.promise(this.v3.evaluatePlugin(options)) } + uploadToSessionWorkspace(file) { + return this.promise(this.v3.uploadToSessionWorkspace(file)) + } + executeCustomDSSService(serviceId, options) { return this.promise(this.v3.getDataStoreFacade().executeCustomDSSService(serviceId, options)); } diff --git a/ui-eln-lims/src/core-plugins/eln-lims/1/as/services/as-eln-lims-api/script.py b/ui-eln-lims/src/core-plugins/eln-lims/1/as/services/as-eln-lims-api/script.py index 2ab162548ba..60ea54881c2 100644 --- a/ui-eln-lims/src/core-plugins/eln-lims/1/as/services/as-eln-lims-api/script.py +++ b/ui-eln-lims/src/core-plugins/eln-lims/1/as/services/as-eln-lims-api/script.py @@ -2,15 +2,12 @@ import ch.systemsx.cisd.openbis.generic.server.ComponentNames as ComponentNames import ch.systemsx.cisd.openbis.generic.server.CommonServiceProvider as CommonServiceProvider import ch.ethz.sis.openbis.generic.server.xls.export.XLSExportExtendedService as XLSExportExtendedService import ch.systemsx.cisd.common.exceptions.UserFailureException as UserFailureException -import base64 import json import re import ch.ethz.sis.openbis.generic.server.xls.importer.utils.AttributeValidator as AttributeValidator import ch.ethz.sis.openbis.generic.server.xls.importer.helper.SampleImportHelper as SampleImportHelper import ch.systemsx.cisd.common.logging.LogCategory as LogCategory; import ch.systemsx.cisd.common.logging.LogFactory as LogFactory; -from java.nio.file import Files -from java.io import File isOpenBIS2020 = True; enableNewSearchEngine = isOpenBIS2020; @@ -79,8 +76,6 @@ def process(context, parameters): result = getUserManagementMaintenanceTaskReport(context, parameters) elif method == "removeUserManagementMaintenanceTaskReport": result = removeUserManagementMaintenanceTaskReport(context, parameters) - elif method == "importSamples": - result = importSamples(context, parameters) elif method == "getSamplesImportTemplate": result = getSamplesImportTemplate(context, parameters) elif method == "createSpace": @@ -343,21 +338,6 @@ def _create_cell(row, cell_index, style, value): cell.setCellValue(value) return cell_index + 1 -def importSamples(context, parameters): - sessionKey = parameters.get("sessionKey") - allowedSampleTypes = parameters.get("allowedSampleTypes") - experimentsByType = parameters.get("experimentsByType", {}) - spacesByType = parameters.get("spacesByType", {}) - mode = parameters.get("mode") - barcodeValidationInfo = json.loads(parameters.get("barcodeValidationInfo")) - sessionManager = CommonServiceProvider.getApplicationContext().getBean("session-manager") - sessionWorkspaceProvider = CommonServiceProvider.getApplicationContext().getBean("session-workspace-provider") - workspaceFolder = sessionWorkspaceProvider.getSessionWorkspace(context.getSessionToken()) - uploadedFile = File(workspaceFolder, sessionKey) - bytes = Files.readAllBytes(uploadedFile.toPath()) - results = importData(context, bytes, sessionKey, experimentsByType, spacesByType, mode, False) - return results - def validateExperimentOrSpaceDefined(row_number, properties, mode, experiment, space): if experiment is None and space is None and not mode.startswith("UPDATE"): exp = properties.get("experiment") @@ -380,29 +360,6 @@ def validateBarcode(row_number, properties, barcodeValidationInfo): raise UserFailureException("Error in row %s: custom barcode %s does not match " "the regular expression '%s'." % (row_number, barcode, pattern.pattern)) -def importData(context, bytes, file_name, experimentsByType, spacesByType, mode, definitionsOnly): - 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.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id import ExperimentIdentifier - - sessionToken = context.getSessionToken() - api = context.getApplicationService() - props = CustomASServiceExecutionOptions().withParameter('xls', [bytes]) - props.withParameter('method', 'import') - props.withParameter('zip', False) - props.withParameter('xls_name', 'DEFAULT') - props.withParameter('update_mode', mode) - props.withParameter('disallow_creations', mode == 'UPDATE_IF_EXISTS') - props.withParameter('render_result', False) - props.withParameter('ignore_versioning', True) - if definitionsOnly: - props.withParameter('definitions_only', True) - if experimentsByType is not None: - props.withParameter('experiments_by_type', experimentsByType) - if spacesByType is not None: - props.withParameter('spaces_by_type', spacesByType) - return api.executeCustomASService(sessionToken, CustomASServiceCode("xls-import"), props) - def getUserManagementMaintenanceTaskConfig(context, parameters): from ch.systemsx.cisd.common.filesystem import FileUtilities diff --git a/ui-eln-lims/src/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js b/ui-eln-lims/src/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js index 026ab63b8ea..188deda519f 100644 --- a/ui-eln-lims/src/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js +++ b/ui-eln-lims/src/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js @@ -120,19 +120,16 @@ function ServerFacade(openbisServer) { }); } - this.registerSamples = function(allowedSampleTypes, experimentsByType, spacesByType, barcodeValidationInfo, sessionKey, callback) { - this.customELNASAPI({ - "method" : "importSamples", - "allowedSampleTypes" : allowedSampleTypes, - "experimentsByType" : experimentsByType, - "spacesByType" : spacesByType, - "barcodeValidationInfo" : barcodeValidationInfo, - "mode" : "FAIL_IF_EXISTS", - "sessionKey" : sessionKey - }, function(result) { - callback(result) - }, true); - } + this.importSamples = function(mode, sessionKey, allowedSampleTypes, experimentsByType, spacesByType, callback) { + this.customASService({ + "method" : "import", + "mode" : mode, + "fileName" : sessionKey, + "allowedSampleTypes" : allowedSampleTypes, + "experimentsByType" : experimentsByType, + "spacesByType" : spacesByType, + }, callback, "xls-import", null, true); + } this.deleteSpace = function(code, reason, callback) { this.customELNASAPI({ @@ -144,18 +141,6 @@ function ServerFacade(openbisServer) { }); } - this.updateSamples = function(allowedSampleTypes, barcodeValidationInfo, sessionKey, callback) { - this.customELNASAPI({ - "method" : "importSamples", - "allowedSampleTypes" : allowedSampleTypes, - "barcodeValidationInfo" : barcodeValidationInfo, - "mode" : "UPDATE_IF_EXISTS", - "sessionKey" : sessionKey - }, function(result) { - callback(result) - }, true); - } - this.getSamplesImportTemplate = function(allowedSampleTypes, templateType, importMode, callback) { this.customELNASAPI({ "method" : "getSamplesImportTemplate", diff --git a/ui-eln-lims/src/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleTable/SampleTableController.js b/ui-eln-lims/src/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleTable/SampleTableController.js index baed5463168..c9cc4f1e290 100644 --- a/ui-eln-lims/src/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleTable/SampleTableController.js +++ b/ui-eln-lims/src/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SampleTable/SampleTableController.js @@ -173,13 +173,13 @@ function SampleTableController(parentController, title, experimentIdentifier, pr var allowSampleTypeSelection = experimentIdentifier == null; var batchController = new BatchController(title, "REGISTRATION", allowedSampleTypes, allowSampleTypeSelection, function(file, selectedSampleTypes) { Util.blockUI(); - mainController.serverFacade.fileUpload(file, function() { - mainController.serverFacade.registerSamples(selectedSampleTypes, experimentsByType, spacesByType, - _this.getBarcodeValidationInfo(), file.name, - function(result) { + mainController.openbisV3.uploadToSessionWorkspace(file) + .done(function() { + mainController.serverFacade.importSamples("FAIL_IF_EXISTS", file.name, selectedSampleTypes, experimentsByType, spacesByType, + function(result) { _this._handleResult(result, "created", experimentIdentifier); - }); - }); + }); + }); }); batchController.init(); } @@ -196,11 +196,11 @@ function SampleTableController(parentController, title, experimentIdentifier, pr var allowSampleTypeSelection = experimentIdentifier == null; var batchController = new BatchController(title, "UPDATE", allowedSampleTypes, allowSampleTypeSelection, function(file, selectedSampleTypes) { Util.blockUI(); - mainController.serverFacade.fileUpload(file, function() { - mainController.serverFacade.updateSamples(selectedSampleTypes, - _this.getBarcodeValidationInfo(), file.name, - function(result) { - _this._handleResult(result, "updated", experimentIdentifier); + mainController.openbisV3.uploadToSessionWorkspace(file) + .done(function() { + mainController.serverFacade.importSamples("UPDATE_IF_EXISTS", file.name, selectedSampleTypes, experimentsByType, spacesByType, + function(result) { + _this._handleResult(result, "updated", experimentIdentifier); }); }); }); -- GitLab