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 50a3642462e5464aaf62625acb1b36a1482c5fe5..bd1ec54d8cec68848cb6ffe19e7017edaf66d141 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 @@ -75,6 +75,9 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.search.ExperimentSear import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.search.ExperimentTypeSearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.update.ExperimentTypeUpdate; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.update.ExperimentUpdate; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.ExportResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.data.ExportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.options.ExportOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDms; import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.create.ExternalDmsCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.delete.ExternalDmsDeletionOptions; @@ -2261,6 +2264,6 @@ public interface IApplicationServerApi extends IRpcService public void executeImport(String sessionToken, IImportData importData, ImportOptions importOptions); -// public ExportResult executeExport(String sessionToken, ExportData exportData, ExportOptions exportOptions); + 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/exporter/ExportOperation.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/exporter/ExportOperation.java new file mode 100644 index 0000000000000000000000000000000000000000..dde0e692ed685aeff446be4245ba581a36b7ff56 --- /dev/null +++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/exporter/ExportOperation.java @@ -0,0 +1,90 @@ +/* + * 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.exporter; + +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.ethz.sis.openbis.generic.asapi.v3.dto.common.operation.IOperation; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.data.ExportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.options.ExportOptions; +import ch.systemsx.cisd.base.annotation.JsonObject; + +@JsonObject("as.dto.exporter.ExportOperation") +public class ExportOperation implements Serializable, IOperation +{ + + private static final long serialVersionUID = 1L; + + @JsonProperty + private ExportData exportData; + + @JsonProperty + private ExportOptions exportOptions; + + @SuppressWarnings("unused") + public ExportOperation() + { + } + + public ExportOperation(final ExportData exportData, final ExportOptions exportOptions) + { + this.exportData = exportData; + this.exportOptions = exportOptions; + } + + @Override + public String getMessage() + { + return toString(); + } + + @JsonIgnore + public ExportData getExportData() + { + return exportData; + } + + @JsonIgnore + public void setExportData(final ExportData exportData) + { + this.exportData = exportData; + } + + @JsonIgnore + public ExportOptions getExportOptions() + { + return exportOptions; + } + + @JsonIgnore + public void setExportOptions(final ExportOptions exportOptions) + { + this.exportOptions = exportOptions; + } + + @Override + public String toString() + { + return new ObjectToString(this).append("exportData", exportData).append("exportOptions", exportOptions).toString(); + } + +} diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/exporter/ExportOperationResult.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/exporter/ExportOperationResult.java new file mode 100644 index 0000000000000000000000000000000000000000..c2a86cb7f60102452af62b93c5b256a28b332130 --- /dev/null +++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/exporter/ExportOperationResult.java @@ -0,0 +1,64 @@ +/* + * 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.exporter; + +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; + +@JsonObject("as.dto.exporter.ExportOperationResult") +public class ExportOperationResult implements Serializable, IOperationResult +{ + + private static final long serialVersionUID = 1L; + + @JsonProperty + private ExportResult exportResult; + + public ExportOperationResult() + { + } + + public ExportOperationResult(final ExportResult exportResult) + { + this.exportResult = exportResult; + } + + @JsonIgnore + public ExportResult getExportResult() + { + return exportResult; + } + + @Override + public String getMessage() + { + return toString(); + } + + @Override + public String toString() + { + return this.getClass().getSimpleName(); + } + +} diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/exporter/data/Attribute.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/exporter/data/Attribute.java index 5b5828e5b37c4fa8c1691d192332b03096180484..9539425e10207be472e15d9d0f68aa75c25d0493 100644 --- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/exporter/data/Attribute.java +++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/exporter/data/Attribute.java @@ -22,10 +22,70 @@ import ch.systemsx.cisd.base.annotation.JsonObject; public enum Attribute { + ARCHIVING_STATUS, + + AUTO_GENERATE_CODES, + + AUTO_GENERATE_CODE, + + CHILDREN, + + CODE, + + DESCRIPTION, + + DISALLOW_DELETION, + + EXPERIMENT, + + GENERATE_CODES, + + GENERATED_CODE_PREFIX, + + IDENTIFIER, + + LABEL, + + MAIN_DATA_SET_PATH, + + MAIN_DATA_SET_PATTERN, + + MODIFICATION_DATE, + + MODIFIER, + + ONTOLOGY_ID, + + ONTOLOGY_VERSION, + + ONTOLOGY_ANNOTATION_ID, + + PARENTS, + + PERM_ID, + + PRESENT_IN_ARCHIVE, + + PROJECT, + + REGISTRATION_DATE, + + REGISTRATOR, + + SIZE, + + SAMPLE, + SPACE, - SAMPLE_TYPE, + STORAGE_CONFIRMATION, + + UNIQUE_SUBCODES, + + URL_TEMPLATE, + + VALIDATION_SCRIPT, - EXPERIMENT_TYPE, + VERSION } diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/exporter/options/ExportFormat.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/exporter/options/ExportFormat.java index 23e2fefd4cf0704ee945583514e92c629957431b..64d679274c1a3ceff76ee5f12641e485d7057619 100644 --- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/exporter/options/ExportFormat.java +++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/dto/exporter/options/ExportFormat.java @@ -22,10 +22,12 @@ import ch.systemsx.cisd.base.annotation.JsonObject; public enum ExportFormat { - XLS, + XLSX, PDF, + HTML, + DATA } diff --git a/api-openbis-javascript/src/v3/as/dto/exporter/ExportOperation.js b/api-openbis-javascript/src/v3/as/dto/exporter/ExportOperation.js new file mode 100644 index 0000000000000000000000000000000000000000..d66b58e30002c482dd288f3233256825450d6adc --- /dev/null +++ b/api-openbis-javascript/src/v3/as/dto/exporter/ExportOperation.js @@ -0,0 +1,55 @@ +/* + * 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/common/operation/IOperation"], + function (stjs, IOperation) { + var ExportOperation = function(exportData, exportOptions) { + this.exportData = exportData; + this.exportOptions = exportOptions; + } + + stjs.extend( + ExportOperation, + IOperation, + [IOperation], + function (constructor, prototype) { + prototype["@type"] = "as.dto.exporter.ExportOperation"; + + constructor.serialVersionUID = 1; + prototype.exportData = null; + prototype.exportOptions = null; + + prototype.getMessage = function() { + return "ExportOperation"; + }; + + prototype.getExportData = function() { + return this.exportData; + }; + + prototype.getExportOptions = function() { + return this.exportOptions; + }; + }, + { + exportData: "ExportData", + exportOptions: "ExportOptions" + } + ); + + return ExportOperation; + }); \ No newline at end of file diff --git a/api-openbis-javascript/src/v3/as/dto/exporter/ExportOperationResult.js b/api-openbis-javascript/src/v3/as/dto/exporter/ExportOperationResult.js new file mode 100644 index 0000000000000000000000000000000000000000..a2035e175022e6877105ab7cb32c4bdb06d12768 --- /dev/null +++ b/api-openbis-javascript/src/v3/as/dto/exporter/ExportOperationResult.js @@ -0,0 +1,49 @@ +/* + * 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/common/operation/IOperationResult"], + function (stjs, IOperationResult) { + var ExportOperationResult = function(exportResult) { + this.exportResult = exportResult; + } + + stjs.extend( + ExportOperationResult, + IOperationResult, + [IOperationResult], + function (constructor, prototype) { + prototype["@type"] = "as.dto.exporter.ExportOperationResult"; + + constructor.serialVersionUID = 1; + + prototype.exportResult = null; + + prototype.getMessage = function() { + return "ExportOperationResult"; + }; + + prototype.getExportResult = function() { + return this.exportResult; + } + }, + { + exportResult: "ExportResult" + } + ); + + return ExportOperationResult; + }); \ No newline at end of file diff --git a/api-openbis-javascript/src/v3/as/dto/exporter/data/Attribute.js b/api-openbis-javascript/src/v3/as/dto/exporter/data/Attribute.js index 3314038e7d360f76689d01a9776d8d64c0c54dd5..9c85ba3bc40903146ae98757969df9e5d0b089f4 100644 --- a/api-openbis-javascript/src/v3/as/dto/exporter/data/Attribute.js +++ b/api-openbis-javascript/src/v3/as/dto/exporter/data/Attribute.js @@ -17,7 +17,41 @@ define(["stjs", "as/dto/common/Enum"], function (stjs, Enum) { var Attribute = function() { - Enum.call(this, ["SPACE", "SAMPLE_TYPE", "EXPERIMENT_TYPE"]); + Enum.call(this, [ + "ARCHIVING_STATUS", + "AUTO_GENERATE_CODES", + "AUTO_GENERATE_CODE", + "CHILDREN", + "CODE", + "DESCRIPTION", + "DISALLOW_DELETION", + "EXPERIMENT", + "GENERATE_CODES", + "GENERATED_CODE_PREFIX", + "IDENTIFIER", + "LABEL", + "MAIN_DATA_SET_PATH", + "MAIN_DATA_SET_PATTERN", + "MODIFICATION_DATE", + "MODIFIER", + "ONTOLOGY_ID", + "ONTOLOGY_VERSION", + "ONTOLOGY_ANNOTATION_ID", + "PARENTS", + "PERM_ID", + "PRESENT_IN_ARCHIVE", + "PROJECT", + "REGISTRATION_DATE", + "REGISTRATOR", + "SIZE", + "SAMPLE", + "SPACE", + "STORAGE_CONFIRMATION", + "UNIQUE_SUBCODES", + "URL_TEMPLATE", + "VALIDATION_SCRIPT", + "VERSION" + ]); } stjs.extend( diff --git a/api-openbis-javascript/src/v3/as/dto/exporter/data/ExportData.js b/api-openbis-javascript/src/v3/as/dto/exporter/data/ExportData.js index 81ad031587966c1c54d217b31bb035dc1547e7e1..1ced9cc1096125bf38e4db64fec3c9965475a5bb 100644 --- a/api-openbis-javascript/src/v3/as/dto/exporter/data/ExportData.js +++ b/api-openbis-javascript/src/v3/as/dto/exporter/data/ExportData.js @@ -16,7 +16,9 @@ */ define(["stjs"], function (stjs) { - var ExportData = function() { + var ExportData = function(permIds, fields) { + this.permIds = permIds; + this.fields = fields; } stjs.extend( diff --git a/api-openbis-javascript/src/v3/as/dto/exporter/data/ExportablePermId.js b/api-openbis-javascript/src/v3/as/dto/exporter/data/ExportablePermId.js index df6affab0faf7dadc9e7ad9cedf8c80e2e3bd6d6..4a0569dca3d067f42745c9960ed48fb5cf2b4991 100644 --- a/api-openbis-javascript/src/v3/as/dto/exporter/data/ExportablePermId.js +++ b/api-openbis-javascript/src/v3/as/dto/exporter/data/ExportablePermId.js @@ -16,7 +16,9 @@ */ define(["stjs"], function (stjs) { - var ExportablePermId = function() { + var ExportablePermId = function(exportableKind, permId) { + this.exportableKind = exportableKind; + this.permId = permId; } stjs.extend( diff --git a/api-openbis-javascript/src/v3/as/dto/exporter/options/ExportFormat.js b/api-openbis-javascript/src/v3/as/dto/exporter/options/ExportFormat.js index 75baed9a5c4b1fbc873e3c2802f07b724e75d9ba..3c45af05f3d2948b427aa4c1bfd0c2f62393205a 100644 --- a/api-openbis-javascript/src/v3/as/dto/exporter/options/ExportFormat.js +++ b/api-openbis-javascript/src/v3/as/dto/exporter/options/ExportFormat.js @@ -17,7 +17,7 @@ define(["stjs", "as/dto/common/Enum"], function (stjs, Enum) { var ExportFormat = function() { - Enum.call(this, ["XLS", "PDF", "DATA"]); + Enum.call(this, ["XLSX", "PDF", "HTML", "DATA"]); } stjs.extend( diff --git a/api-openbis-javascript/src/v3/as/dto/exporter/options/ExportOptions.js b/api-openbis-javascript/src/v3/as/dto/exporter/options/ExportOptions.js index 005f53ddb45bb934b447fbe55375891f8ecefdcd..586119a7948febdeca546ed852b8efd6419d7bff 100644 --- a/api-openbis-javascript/src/v3/as/dto/exporter/options/ExportOptions.js +++ b/api-openbis-javascript/src/v3/as/dto/exporter/options/ExportOptions.js @@ -16,7 +16,11 @@ */ define(["stjs"], function (stjs) { - var ExportOptions = function() { + var ExportOptions = function(formats, xlsTextFormat, withReferredTypes, withImportCompatibility) { + this.formats = formats; + this.xlsTextFormat = xlsTextFormat; + this.withReferredTypes = withReferredTypes; + this.withImportCompatibility = withImportCompatibility; } stjs.extend( diff --git a/api-openbis-javascript/src/v3/openbis.js b/api-openbis-javascript/src/v3/openbis.js index 1f4aa3c7b00f30dbc7aaa836fa7b4b32ba1d51b8..f7b04ec5da23c81b264ce065f74219d5761a1c17 100644 --- a/api-openbis-javascript/src/v3/openbis.js +++ b/api-openbis-javascript/src/v3/openbis.js @@ -2347,16 +2347,27 @@ define([ 'jquery', 'util/Json', 'as/dto/datastore/search/DataStoreSearchCriteria }); } - this.isSessionActive = function() { - var thisFacade = this; - return thisFacade._private.ajaxRequest({ - url : openbisUrl, - data : { - "method" : "isSessionActive", - "params" : [ thisFacade._private.sessionToken ] - } - }); - } + this.executeExport = function(exportData, exportOptions) { + var thisFacade = this; + return thisFacade._private.ajaxRequest({ + url : openbisUrl, + data : { + "method" : "executeExport", + "params" : [ thisFacade._private.sessionToken, exportData, exportOptions ] + } + }); + } + + this.isSessionActive = function() { + var thisFacade = this; + return thisFacade._private.ajaxRequest({ + url : openbisUrl, + data : { + "method" : "isSessionActive", + "params" : [ thisFacade._private.sessionToken ] + } + }); + } this.getDataStoreFacade = function() { var dataStoreCodes = []; diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/FileServiceServlet.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/FileServiceServlet.java index 24b3e41fd8aad42f09c4f7f48fceadc29890c974..3118f94025ed81c8da07989980f4e81fab39f747 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/FileServiceServlet.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/FileServiceServlet.java @@ -45,7 +45,6 @@ import ch.systemsx.cisd.common.properties.PropertyUtils; import ch.systemsx.cisd.common.spring.ExposablePropertyPlaceholderConfigurer; import ch.systemsx.cisd.common.string.Template; import ch.systemsx.cisd.openbis.generic.client.web.server.AbstractServlet; -import ch.systemsx.cisd.openbis.generic.shared.basic.GenericSharedConstants; /** @@ -59,12 +58,12 @@ public class FileServiceServlet extends AbstractServlet public static final String FILE_SERVICE_PATH = "file-service"; public static final String FILE_SERVICE_PATH_MAPPING = FILE_SERVICE_PATH + "/**/*"; - private static final String APP_PREFIX = "/" + FILE_SERVICE_PATH + "/"; private static final String KEY_PREFIX = "file-server."; - - private static final String REPO_PATH_KEY = KEY_PREFIX + "repository-path"; - private static final String DEFAULT_REPO_PATH = "../../../data/file-server"; - + public static final String REPO_PATH_KEY = KEY_PREFIX + "repository-path"; + + public static final String DEFAULT_REPO_PATH = "../../../data/file-server"; + private static final String APP_PREFIX = "/" + FILE_SERVICE_PATH + "/"; + private static final String MAX_SIZE_KEY = KEY_PREFIX + "maximum-file-size-in-MB"; private static final int DEFAULT_MAX_SIZE = 10; 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 6aa3bd93402d5181749d60db27da33d92d192d48..df54c5679c00ae347859bbca0e5318e136d8713e 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 @@ -146,6 +146,11 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.update.ExperimentType import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.update.ExperimentUpdate; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.update.UpdateExperimentTypesOperation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.update.UpdateExperimentsOperation; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.ExportOperation; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.ExportOperationResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.ExportResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.data.ExportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.options.ExportOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDms; import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.create.CreateExternalDmsOperation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.create.CreateExternalDmsOperationResult; @@ -1818,7 +1823,15 @@ public class ApplicationServerApi extends AbstractServer<IApplicationServerApi> executeOperation(sessionToken, new ImportOperation(importData, importOptions)); } - @Override public IApplicationServerApi createPersonalAccessTokenInvocationHandler(final IPersonalAccessTokenInvocation invocation) + @Override + public ExportResult executeExport(final String sessionToken, final ExportData exportData, final ExportOptions exportOptions) + { + final ExportOperationResult operationResult = executeOperation(sessionToken, new ExportOperation(exportData, exportOptions)); + return operationResult.getExportResult(); + } + + @Override + public IApplicationServerApi createPersonalAccessTokenInvocationHandler(final IPersonalAccessTokenInvocation invocation) { return new ApplicationServerApiPersonalAccessTokenInvocationHandler(invocation); } 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 e2c20fbece7bfb0645e0994a6d8d615ca93178ac..f1ec87277d6c59b0716a50ffe6c854d93cfd047e 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 @@ -76,6 +76,9 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.search.ExperimentSear import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.search.ExperimentTypeSearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.update.ExperimentTypeUpdate; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.update.ExperimentUpdate; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.ExportResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.data.ExportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.options.ExportOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDms; import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.create.ExternalDmsCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.delete.ExternalDmsDeletionOptions; @@ -1371,4 +1374,11 @@ public class ApplicationServerApiLogger extends AbstractServerLogger implements logAccess(sessionToken, "execute-import", "IImportData(%s) ImportOptions(%s)", importData, importOptions); } + @Override + public ExportResult executeExport(final String sessionToken, final ExportData exportData, final ExportOptions exportOptions) + { + logAccess(sessionToken, "execute-export", "ExportData(%s) ExportOptions(%s)", exportData, exportOptions); + return null; + } + } 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 d7d792f889e393c67da51b5066328d4bdc43ec4b..1eba23cd4650c3f3bbda3dff28015498fc1281e3 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 @@ -77,6 +77,9 @@ import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.search.ExperimentSear import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.search.ExperimentTypeSearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.update.ExperimentTypeUpdate; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.update.ExperimentUpdate; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.ExportResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.data.ExportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.options.ExportOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.ExternalDms; import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.create.ExternalDmsCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.delete.ExternalDmsDeletionOptions; @@ -1260,6 +1263,12 @@ public class ApplicationServerApiPersonalAccessTokenInvocationHandler implements invocation.proceedWithNewFirstArgument(converter.convert(sessionToken)); } + @Override + public ExportResult executeExport(final String sessionToken, final ExportData exportData, final ExportOptions exportOptions) + { + return invocation.proceedWithNewFirstArgument(converter.convert(sessionToken)); + } + private void checkPersonalAccessTokensEnabled() { if (!config.arePersonalAccessTokensEnabled()) diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/AbstractExportFieldsFinder.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/AbstractExportFieldsFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..bb307a1f2735c9784389ed83e1fabbfb7d806d23 --- /dev/null +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/AbstractExportFieldsFinder.java @@ -0,0 +1,98 @@ +/* + * 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.server.asapi.v3.executor.exporter; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IEntityType; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IPermIdHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.data.Attribute; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.data.SelectedFields; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.PropertyAssignment; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.PropertyType; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.id.IPropertyTypeId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.id.PropertyTypePermId; +import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi; +import ch.ethz.sis.openbis.generic.server.xls.export.FieldType; + +public abstract class AbstractExportFieldsFinder<ENTITY_TYPE extends IEntityType & IPermIdHolder> implements IExportFieldsFinder +{ + + @Override + public Map<String, List<Map<String, String>>> findExportFields(final Set<IPropertyTypeId> properties, + final IApplicationServerInternalApi applicationServerApi, final String sessionToken, final SelectedFields selectedFields) + { + final SearchResult<ENTITY_TYPE> entityTypeSearchResult = findEntityTypes(properties, applicationServerApi, sessionToken); + + final List<ENTITY_TYPE> entityTypes = entityTypeSearchResult.getObjects(); + final Collector<ENTITY_TYPE, ?, Map<String, List<Map<String, String>>>> entityTypeToMapCollector = + getEntityTypeMapCollector(selectedFields, entityTypes); + return entityTypes.stream().collect(entityTypeToMapCollector); + } + + private Collector<ENTITY_TYPE, ?, Map<String, List<Map<String, String>>>> getEntityTypeMapCollector(final SelectedFields selectedFields, + final List<ENTITY_TYPE> entityTypes) + { + final Map<String, Map<PropertyTypePermId, String>> propertyTypePermIdsByEntityType = + entityTypes.stream().collect(Collectors.toMap(this::getPermId, + entityType -> entityType.getPropertyAssignments().stream() + .map(PropertyAssignment::getPropertyType) + .collect(Collectors.toMap(PropertyType::getPermId, PropertyType::getCode)))); + + return Collectors.toMap(this::getPermId, + entityType -> + { + final Map<PropertyTypePermId, String> propertyTypePermIds = + propertyTypePermIdsByEntityType.get(getPermId(entityType)); + final List<String> selectedPropertyTypeCodes = + selectedFields.getProperties().stream().flatMap( + propertyTypePermId -> + { + final String propertyTypeCode = propertyTypePermIds.get(propertyTypePermId); + return propertyTypeCode != null ? Stream.of(propertyTypeCode) : Stream.empty(); + }) + .collect(Collectors.toList()); + return mergePropertiesAndAttributes(selectedPropertyTypeCodes, selectedFields.getAttributes()); + }); + } + + private static List<Map<String, String>> mergePropertiesAndAttributes(final List<String> selectedPropertyTypeCodes, + final Collection<Attribute> attributes) + { + final Stream<Map<String, String>> attributesStream = attributes.stream() + .map(attribute -> Map.of(TYPE, FieldType.ATTRIBUTE.name(), ID, attribute.name())); + + final Stream<Map<String, String>> propertiesStream = selectedPropertyTypeCodes.stream() + .map(propertyTypeCode -> Map.of(TYPE, FieldType.PROPERTY.name(), ID, propertyTypeCode)); + + return Stream.concat(attributesStream, propertiesStream).collect(Collectors.toList()); + } + + public abstract SearchResult<ENTITY_TYPE> findEntityTypes(Set<IPropertyTypeId> properties, + IApplicationServerInternalApi applicationServerApi, String sessionToken); + + public abstract String getPermId(ENTITY_TYPE entityType); + +} diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/DataSetExportFieldsFinder.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/DataSetExportFieldsFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..f60cd6a239d0a712dc55a898f4c0b4883c6e249e --- /dev/null +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/DataSetExportFieldsFinder.java @@ -0,0 +1,51 @@ +/* + * 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.server.asapi.v3.executor.exporter; + +import java.util.Set; + +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.property.id.IPropertyTypeId; +import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi; + +public class DataSetExportFieldsFinder extends AbstractExportFieldsFinder<DataSetType> +{ + + @Override + public SearchResult<DataSetType> findEntityTypes(final Set<IPropertyTypeId> properties, + final IApplicationServerInternalApi applicationServerApi, final String sessionToken) + { + final DataSetTypeSearchCriteria typeSearchCriteria = new DataSetTypeSearchCriteria(); + typeSearchCriteria.withPropertyAssignments().withPropertyType().withIds().thatIn(properties); + + final DataSetTypeFetchOptions fetchOptions = new DataSetTypeFetchOptions(); + fetchOptions.withPropertyAssignments().withPropertyType(); + + return applicationServerApi.searchDataSetTypes(sessionToken, typeSearchCriteria, fetchOptions); + } + + @Override + public String getPermId(final DataSetType dataSetType) + { + return dataSetType.getPermId().getPermId(); + } + +} diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/DocumentBuilder.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/DocumentBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..da12409d5487c2d09dc2e7fb93f9987821799bbf --- /dev/null +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/DocumentBuilder.java @@ -0,0 +1,172 @@ +/* + * Copyright ETH 2021 - 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.asapi.v3.executor.exporter; + +import java.util.Base64; + +import org.apache.log4j.Logger; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import ch.systemsx.cisd.common.http.JettyHttpClientFactory; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; + +class DocumentBuilder +{ + + private static final Logger LOG = LogFactory.getLogger(LogCategory.OPERATION, DocumentBuilder.class); + + private static final String START_RICH_TEXT = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<html><head></head><body>"; + + private static final String END_RICH_TEXT = "</body></html>"; + + private StringBuffer doc = new StringBuffer(); + + private String closedDoc; + + private boolean closed = false; + + public DocumentBuilder() + { + System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"); + startDoc(); + } + + public void setDocument(final String doc) + { + this.doc = new StringBuffer(doc); + closed = true; + } + + private void startDoc() + { + if (!closed) + { + doc.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"); + doc.append("<html xmlns=\"http://www.w3.org/1999/xhtml\">"); + doc.append("<head></head>"); + doc.append("<body>"); + } + } + + private void endDoc() + { + if (!closed) + { + doc.append("</body>"); + doc.append("</html>"); + closed = true; + closedDoc = fixImages(doc); + } + } + + public void addProperty(final String key, final String value) + { + if (!closed) + { + doc.append("<p>").append("<b>").append(key).append(": ").append("</b>").append("</p>"); + addParagraph(value); + } + } + + public void addParagraph(final String value) + { + if (!closed) + { + doc.append("<p>").append(cleanXMLEnvelope(value)).append("</p>"); + } + } + + public void addTitle(final String title) + { + if (!closed) + { + doc.append("<h1>").append(title).append("</h1>"); + } + } + + public void addHeader(final String header) + { + if (!closed) + { + doc.append("<h2>").append(header).append("</h2>"); + } + } + + public String getHtml() + { + if (!closed) + { + endDoc(); + } + return closedDoc; + } + + private String cleanXMLEnvelope(final String value) + { + if (value.startsWith(START_RICH_TEXT) && value.endsWith(END_RICH_TEXT)) + { + return value.substring(START_RICH_TEXT.length(), value.length() - END_RICH_TEXT.length()); + } else + { + return value; + } + } + + private String fixImages(StringBuffer buffer) + { + final Document jsoupDoc = Jsoup.parse(buffer.toString()); + jsoupDoc.outputSettings().syntax(Document.OutputSettings.Syntax.xml); + final Elements elements = jsoupDoc.select("img"); + + // Fixes images sizes + for (final Element element : elements) + { + final String style = element.attr("style"); + final String[] rules = style.split(";"); + for (final String rule : rules) + { + final String[] ruleElements = rule.split(":"); + if (ruleElements.length == 2) + { + final String ruleKey = ruleElements[0].trim(); + final String ruleValue = ruleElements[1].trim(); + if ((ruleKey.equalsIgnoreCase("width") || ruleKey.equalsIgnoreCase("height")) && ruleValue.endsWith("px")) + { + element.attr(ruleKey, ruleValue.substring(0, ruleValue.length() - 2)); + } + } + } + } + + return jsoupDoc.html(); + } + + private static String getDataUriFromUri(final String url) throws Exception + { + final HttpClient client = JettyHttpClientFactory.getHttpClient(); + final Request requestEntity = client.newRequest(url).method("GET"); + final ContentResponse contentResponse = requestEntity.send(); + return "data:" + contentResponse.getMediaType() + ";base64," + Base64.getEncoder().encodeToString(contentResponse.getContent()); + } + +} diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/EntitiesFinder.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/EntitiesFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..8d24e7d70337f7e00d99546d24d54c201ce749c7 --- /dev/null +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/EntitiesFinder.java @@ -0,0 +1,266 @@ +/* + * 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.server.asapi.v3.executor.exporter; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.ICodeHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSet; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSetType; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.DataSetFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.DataSetTypeFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.id.DataSetPermId; +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; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.ExperimentType; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.fetchoptions.ExperimentFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.fetchoptions.ExperimentTypeFetchOptions; +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.fetchoptions.ProjectFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.id.ProjectPermId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.fetchoptions.PropertyAssignmentFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.SampleType; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.fetchoptions.SampleFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.fetchoptions.SampleTypeFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.id.SamplePermId; +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.id.SpacePermId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.Vocabulary; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.fetchoptions.VocabularyFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.IVocabularyId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.id.VocabularyPermId; +import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi; +import ch.ethz.sis.openbis.generic.server.xls.export.ExportableKind; +import ch.ethz.sis.openbis.generic.server.xls.export.ExportablePermId; +import ch.systemsx.cisd.openbis.generic.server.CommonServiceProvider; + +class EntitiesFinder +{ + + public static Collection<ICodeHolder> getEntities(final String sessionToken, final Collection<ExportablePermId> permIds) + { + final Map<ExportableKind, List<ExportablePermId>> groupedExportables = + permIds.stream().collect(Collectors.groupingBy(ExportablePermId::getExportableKind)); + + return groupedExportables.entrySet().stream().flatMap(entry -> + { + final Collection<String> stringPermIds = entry.getValue().stream().map(permId -> permId.getPermId().getPermId()) + .collect(Collectors.toList()); + switch (entry.getKey()) + { + case SAMPLE_TYPE: + { + return Stream.of(getSampleTypes(sessionToken, stringPermIds)).map(value -> (ICodeHolder) value); + } + case EXPERIMENT_TYPE: + { + return Stream.of(getExperimentTypes(sessionToken, stringPermIds)).map(value -> (ICodeHolder) value); + } + case DATASET_TYPE: + { + return Stream.of(getDataSetTypes(sessionToken, stringPermIds)).map(value -> (ICodeHolder) value); + } + case VOCABULARY_TYPE: + { + return Stream.of(getVocabularies(sessionToken, stringPermIds)).map(value -> (ICodeHolder) value); + } + case SPACE: + { + return Stream.of(getSpaces(sessionToken, stringPermIds)).map(value -> (ICodeHolder) value); + } + case PROJECT: + { + return Stream.of(getProjects(sessionToken, stringPermIds)).map(value -> (ICodeHolder) value); + } + case SAMPLE: + { + return Stream.of(getSamples(sessionToken, stringPermIds)).map(value -> (ICodeHolder) value); + } + case EXPERIMENT: + { + return Stream.of(getExperiments(sessionToken, stringPermIds)).map(value -> (ICodeHolder) value); + } + case DATASET: + { + return Stream.of(getDataSets(sessionToken, stringPermIds)).map(value -> (ICodeHolder) value); + } + default: + { + throw new IllegalArgumentException(); + } + } + }).collect(Collectors.toList()); + } + + public static Collection<DataSetType> getDataSetTypes(final String sessionToken, final Collection<String> permIds) + { + final IApplicationServerInternalApi api = CommonServiceProvider.getApplicationServerApi(); + final DataSetTypeFetchOptions fetchOptions = new DataSetTypeFetchOptions(); + fetchOptions.withValidationPlugin().withScript(); + final PropertyAssignmentFetchOptions propertyAssignmentFetchOptions = fetchOptions.withPropertyAssignments(); + propertyAssignmentFetchOptions.withPropertyType().withVocabulary(); + propertyAssignmentFetchOptions.withPropertyType().withSampleType(); + propertyAssignmentFetchOptions.withPropertyType().withMaterialType(); + propertyAssignmentFetchOptions.withPlugin().withScript(); + final Map<IEntityTypeId, DataSetType> dataSetTypes = api.getDataSetTypes(sessionToken, + permIds.stream().map(permId -> new EntityTypePermId(permId, EntityKind.DATA_SET)).collect(Collectors.toList()), fetchOptions); + + assert dataSetTypes.size() <= 1; + + return dataSetTypes.values(); + } + + public static Collection<DataSet> getDataSets(final String sessionToken, final Collection<String> permIds) + { + final IApplicationServerInternalApi api = CommonServiceProvider.getApplicationServerApi(); + final List<DataSetPermId> dataSetPermIds = permIds.stream().map(DataSetPermId::new) + .collect(Collectors.toList()); + final DataSetFetchOptions fetchOptions = new DataSetFetchOptions(); + fetchOptions.withSample(); + fetchOptions.withExperiment().withProject().withSpace(); + fetchOptions.withType().withPropertyAssignments().withPropertyType(); + fetchOptions.withProperties(); + fetchOptions.withRegistrator(); + fetchOptions.withModifier(); + fetchOptions.withPhysicalData(); + fetchOptions.withParents().withProperties(); + fetchOptions.withChildren().withProperties(); + return api.getDataSets(sessionToken, dataSetPermIds, fetchOptions).values(); + } + + public static Collection<Experiment> getExperiments(final String sessionToken, final Collection<String> permIds) + { + final IApplicationServerInternalApi api = CommonServiceProvider.getApplicationServerApi(); + final List<ExperimentPermId> experimentPermIds = permIds.stream().map(ExperimentPermId::new) + .collect(Collectors.toList()); + final ExperimentFetchOptions fetchOptions = new ExperimentFetchOptions(); + final ProjectFetchOptions projectFetchOptions = fetchOptions.withProject(); + projectFetchOptions.withSpace(); + projectFetchOptions.withRegistrator(); + projectFetchOptions.withModifier(); + + fetchOptions.withType().withPropertyAssignments().withPropertyType(); + fetchOptions.withProperties(); + fetchOptions.withRegistrator(); + fetchOptions.withModifier(); + fetchOptions.withDataSets().withType(); + return api.getExperiments(sessionToken, experimentPermIds, fetchOptions).values(); + } + + public static Collection<ExperimentType> getExperimentTypes(final String sessionToken, final Collection<String> permIds) + { + final IApplicationServerInternalApi api = CommonServiceProvider.getApplicationServerApi(); + final ExperimentTypeFetchOptions fetchOptions = new ExperimentTypeFetchOptions(); + fetchOptions.withValidationPlugin().withScript(); + final PropertyAssignmentFetchOptions propertyAssignmentFetchOptions = fetchOptions.withPropertyAssignments(); + propertyAssignmentFetchOptions.withPropertyType().withVocabulary(); + propertyAssignmentFetchOptions.withPropertyType().withSampleType(); + propertyAssignmentFetchOptions.withPropertyType().withMaterialType(); + propertyAssignmentFetchOptions.withPlugin().withScript(); + final Map<IEntityTypeId, ExperimentType> experimentTypes = api.getExperimentTypes(sessionToken, + permIds.stream().map(permId -> new EntityTypePermId(permId, EntityKind.EXPERIMENT)).collect(Collectors.toList()), fetchOptions); + + assert experimentTypes.size() <= 1; + + return experimentTypes.values(); + } + + public static Collection<Project> getProjects(final String sessionToken, final Collection<String> permIds) + { + final IApplicationServerInternalApi api = CommonServiceProvider.getApplicationServerApi(); + final List<ProjectPermId> projectPermIds = permIds.stream().map(ProjectPermId::new) + .collect(Collectors.toList()); + final ProjectFetchOptions fetchOptions = new ProjectFetchOptions(); + fetchOptions.withSpace(); + fetchOptions.withRegistrator(); + fetchOptions.withModifier(); + return api.getProjects(sessionToken, projectPermIds, fetchOptions).values(); + } + + public static Collection<Sample> getSamples(final String sessionToken, final Collection<String> permIds) + { + final IApplicationServerInternalApi api = CommonServiceProvider.getApplicationServerApi(); + final List<SamplePermId> samplePermIds = permIds.stream().map(SamplePermId::new) + .collect(Collectors.toList()); + final SampleFetchOptions fetchOptions = new SampleFetchOptions(); + final ExperimentFetchOptions experimentFetchOptions = fetchOptions.withExperiment(); + experimentFetchOptions.withProperties(); + experimentFetchOptions.withProject().withSpace(); + fetchOptions.withSpace(); + fetchOptions.withProject().withSpace(); + fetchOptions.withParents().withProperties(); + fetchOptions.withChildren().withProperties(); + fetchOptions.withType().withPropertyAssignments().withPropertyType(); + fetchOptions.withProperties(); + fetchOptions.withRegistrator(); + fetchOptions.withModifier(); + fetchOptions.withDataSets().withType(); + fetchOptions.withContainer(); + return api.getSamples(sessionToken, samplePermIds, fetchOptions).values(); + } + + public static Collection<SampleType> getSampleTypes(final String sessionToken, final Collection<String> permIds) + { + final IApplicationServerInternalApi api = CommonServiceProvider.getApplicationServerApi(); + final SampleTypeFetchOptions fetchOptions = new SampleTypeFetchOptions(); + fetchOptions.withValidationPlugin().withScript(); + final PropertyAssignmentFetchOptions propertyAssignmentFetchOptions = fetchOptions.withPropertyAssignments(); + propertyAssignmentFetchOptions.withPropertyType().withVocabulary(); + propertyAssignmentFetchOptions.withPropertyType().withSampleType(); + propertyAssignmentFetchOptions.withPropertyType().withMaterialType(); + propertyAssignmentFetchOptions.withPlugin().withScript(); + final Map<IEntityTypeId, SampleType> sampleTypes = api.getSampleTypes(sessionToken, + permIds.stream().map(permId -> new EntityTypePermId(permId, EntityKind.SAMPLE)).collect(Collectors.toList()), fetchOptions); + + assert sampleTypes.size() <= 1; + + return sampleTypes.values(); + } + + public static Collection<Space> getSpaces(final String sessionToken, final Collection<String> permIds) + { + final IApplicationServerInternalApi api = CommonServiceProvider.getApplicationServerApi(); + final List<SpacePermId> spacePermIds = permIds.stream().map(SpacePermId::new).collect(Collectors.toList()); + final SpaceFetchOptions fetchOptions = new SpaceFetchOptions(); + fetchOptions.withRegistrator(); + return api.getSpaces(sessionToken, spacePermIds, fetchOptions).values(); + } + + public static Collection<Vocabulary> getVocabularies(final String sessionToken, final Collection<String> permIds) + { + final IApplicationServerInternalApi api = CommonServiceProvider.getApplicationServerApi(); + final VocabularyFetchOptions fetchOptions = new VocabularyFetchOptions(); + fetchOptions.withTerms(); + fetchOptions.withRegistrator(); + final Map<IVocabularyId, Vocabulary> vocabularies = api.getVocabularies(sessionToken, + permIds.stream().map(VocabularyPermId::new).collect(Collectors.toList()), fetchOptions); + + assert vocabularies.size() <= 1; + + return vocabularies.values(); + } + +} diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/ExperimentExportFieldsFinder.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/ExperimentExportFieldsFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..0404d54dbd986676ead458449e166c29814a283f --- /dev/null +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/ExperimentExportFieldsFinder.java @@ -0,0 +1,51 @@ +/* + * 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.server.asapi.v3.executor.exporter; + +import java.util.Set; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.ExperimentType; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.id.IPropertyTypeId; +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.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi; + +public class ExperimentExportFieldsFinder extends AbstractExportFieldsFinder<ExperimentType> +{ + + @Override + public SearchResult<ExperimentType> findEntityTypes(final Set<IPropertyTypeId> properties, + final IApplicationServerInternalApi applicationServerApi, final String sessionToken) + { + final ExperimentTypeSearchCriteria typeSearchCriteria = new ExperimentTypeSearchCriteria(); + typeSearchCriteria.withPropertyAssignments().withPropertyType().withIds().thatIn(properties); + + final ExperimentTypeFetchOptions fetchOptions = new ExperimentTypeFetchOptions(); + fetchOptions.withPropertyAssignments().withPropertyType(); + + return applicationServerApi.searchExperimentTypes(sessionToken, typeSearchCriteria, fetchOptions); + } + + @Override + public String getPermId(final ExperimentType experimentType) + { + return experimentType.getPermId().getPermId(); + } + +} diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/ExportExecutor.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/ExportExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..4731d9f7adb5320e51c7755f632a1674b52e6895 --- /dev/null +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/ExportExecutor.java @@ -0,0 +1,1615 @@ +/* + * 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.server.asapi.v3.executor.exporter; + +import static ch.ethz.sis.openbis.generic.server.FileServiceServlet.DEFAULT_REPO_PATH; +import static ch.ethz.sis.openbis.generic.server.FileServiceServlet.REPO_PATH_KEY; +import static ch.ethz.sis.openbis.generic.server.xls.export.ExportableKind.DATASET; +import static ch.ethz.sis.openbis.generic.server.xls.export.ExportableKind.EXPERIMENT; +import static ch.ethz.sis.openbis.generic.server.xls.export.ExportableKind.MASTER_DATA_EXPORTABLE_KINDS; +import static ch.ethz.sis.openbis.generic.server.xls.export.ExportableKind.PROJECT; +import static ch.ethz.sis.openbis.generic.server.xls.export.ExportableKind.SAMPLE; +import static ch.ethz.sis.openbis.generic.server.xls.export.ExportableKind.SPACE; +import static ch.ethz.sis.openbis.generic.server.xls.export.FieldType.ATTRIBUTE; +import static ch.ethz.sis.openbis.generic.server.xls.export.FieldType.PROPERTY; +import static ch.ethz.sis.openbis.generic.server.xls.export.XLSExport.ExportResult; +import static ch.ethz.sis.openbis.generic.server.xls.export.XLSExport.SCRIPTS_DIRECTORY; +import static ch.ethz.sis.openbis.generic.server.xls.export.XLSExport.TextFormatting; +import static ch.ethz.sis.openbis.generic.server.xls.export.XLSExport.ZIP_EXTENSION; +import static ch.ethz.sis.openbis.generic.server.xls.export.helper.AbstractXLSExportHelper.FIELD_ID_KEY; +import static ch.ethz.sis.openbis.generic.server.xls.export.helper.AbstractXLSExportHelper.FIELD_TYPE_KEY; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; + +import org.apache.commons.io.filefilter.NameFileFilter; +import org.apache.log4j.Logger; +import org.apache.poi.ss.usermodel.Workbook; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.node.TextNode; +import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; + +import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectIdentifier; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.ICodeHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IDescriptionHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IEntityType; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IEntityTypeHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IExperimentHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IIdentifierHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IModificationDateHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IModifierHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IParentChildrenHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IPermIdHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IPropertiesHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IRegistrationDateHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.IRegistratorHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.interfaces.ISampleHolder; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSet; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.DataSetKind; +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.Experiment; +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.ethz.sis.openbis.generic.asapi.v3.dto.exporter.ExportOperation; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.data.Attribute; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.data.ExportData; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.data.IExportableFields; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.data.SelectedFields; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.options.ExportFormat; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.options.ExportOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.person.Person; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.Project; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.DataType; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.PropertyAssignment; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.PropertyType; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.id.IPropertyTypeId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.SampleType; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.fetchoptions.SampleTypeFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.search.SampleTypeSearchCriteria; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.space.Space; +import ch.ethz.sis.openbis.generic.asapi.v3.exceptions.NotFetchedException; +import ch.ethz.sis.openbis.generic.dssapi.v3.IDataStoreServerApi; +import ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.DataSetFile; +import ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.download.DataSetFileDownload; +import ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.download.DataSetFileDownloadOptions; +import ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.download.DataSetFileDownloadReader; +import ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.fetchoptions.DataSetFileFetchOptions; +import ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.id.DataSetFilePermId; +import ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.search.DataSetFileSearchCriteria; +import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext; +import ch.ethz.sis.openbis.generic.server.sharedapi.v3.json.ObjectMapperResource; +import ch.ethz.sis.openbis.generic.server.xls.export.ExportableKind; +import ch.ethz.sis.openbis.generic.server.xls.export.ExportablePermId; +import ch.ethz.sis.openbis.generic.server.xls.export.XLSExport; +import ch.systemsx.cisd.common.exceptions.UserFailureException; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.common.spring.ExposablePropertyPlaceholderConfigurer; +import ch.systemsx.cisd.openbis.generic.server.CommonServiceProvider; +import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider; + +@SuppressWarnings("SizeReplaceableByIsEmpty") +@Component +public class ExportExecutor implements IExportExecutor +{ + + public static final String METADATA_FILE_PREFIX = "metadata"; + + public static final String EXPORT_FILE_PREFIX = "export"; + + public static final String METADATA_FILE_NAME = "metadata" + XLSExport.XLSX_EXTENSION; + + public static final String XLSX_DIRECTORY = "xlsx"; + + public static final String PDF_DIRECTORY = "pdf"; + + public static final String DATA_DIRECTORY = "data"; + + public static final String META_FILE_NAME = "meta.json"; + + public static final String SHARED_SAMPLES_DIRECTORY = "(shared)"; + + public static final String HTML_EXTENSION = ".html"; + + public static final String PDF_EXTENSION = ".pdf"; + + public static final String JSON_EXTENSION = ".json"; + + static final String NAME_PROPERTY_NAME = "$NAME"; + + private static final String TYPE_EXPORT_FIELD_KEY = "TYPE"; + + private static final Map<ExportableKind, IExportFieldsFinder> FIELDS_FINDER_BY_EXPORTABLE_KIND = + Map.of(ExportableKind.SAMPLE, new SampleExportFieldsFinder(), + ExportableKind.EXPERIMENT, new ExperimentExportFieldsFinder(), + ExportableKind.DATASET, new DataSetExportFieldsFinder()); + + private static final Set<ExportableKind> TYPE_EXPORTABLE_KINDS = EnumSet.of(ExportableKind.SAMPLE_TYPE, ExportableKind.EXPERIMENT_TYPE, + ExportableKind.DATASET_TYPE, ExportableKind.VOCABULARY_TYPE, ExportableKind.SPACE, ExportableKind.PROJECT); + + private static final String PYTHON_EXTENSION = ".py"; + + private static final String COMMON_STYLE = "border: 1px solid black;"; + + private static final String TABLE_STYLE = COMMON_STYLE + " border-collapse: collapse;"; + + private static final String DATA_TAG_START = "<DATA>"; + + private static final int DATA_TAG_START_LENGTH = DATA_TAG_START.length(); + + private static final String DATA_TAG_END = "</DATA>"; + + private static final int DATA_TAG_END_LENGTH = DATA_TAG_END.length(); + + private static final String PNG_MEDIA_TYPE = "image/png"; + + private static final String JPEG_MEDIA_TYPE = "image/jpeg"; + + /** Buffer size for the buffer stream for Base64 encoding. Should be a multiple of 3. */ + private static final int BUFFER_SIZE = 3 * 1024; + + private static final Map<String, String> MEDIA_TYPE_BY_EXTENSION = Map.of( + ".png", PNG_MEDIA_TYPE, + ".jpg", JPEG_MEDIA_TYPE, + ".jpeg", JPEG_MEDIA_TYPE, + ".jfif", JPEG_MEDIA_TYPE, + ".pjpeg", JPEG_MEDIA_TYPE, + ".pjp", JPEG_MEDIA_TYPE, + ".gif", "image/gif", + ".bmp", "image/bmp", + ".webp", "image/webp", + ".tiff", "image/tiff"); + + private static final String DEFAULT_MEDIA_TYPE = JPEG_MEDIA_TYPE; + + private static final String DATA_PREFIX_TEMPLATE = "data:%s;base64,"; + + private static final String KIND_DOCUMENT_PROPERTY_ID = "Kind"; + + private static final Logger OPERATION_LOG = LogFactory.getLogger(LogCategory.OPERATION, ExportExecutor.class); + + /** All characters except the ones we consider safe as a folder name. */ + private static final String UNSAFE_CHARACTERS_REGEXP = "[^\\w $!#%'()+,\\-.;=@\\[\\]^{}_~]"; + + @Autowired + private ISessionWorkspaceProvider sessionWorkspaceProvider; + + @Resource(name = ObjectMapperResource.NAME) + private ObjectMapper objectMapper; + + @Resource(name = ExposablePropertyPlaceholderConfigurer.PROPERTY_CONFIGURER_BEAN_NAME) + private ExposablePropertyPlaceholderConfigurer configurer; + + private ObjectWriter objectWriter; + + @PostConstruct + private void postConstruct() + { + objectWriter = objectMapper.writerWithDefaultPrettyPrinter(); + } + + @Override + public ExportResult doExport(final IOperationContext context, final ExportOperation operation) + { + try + { + final ExportData exportData = operation.getExportData(); + final ExportOptions exportOptions = operation.getExportOptions(); + final String sessionToken = context.getSession().getSessionToken(); + + return doExport(sessionToken, exportData, exportOptions); + } catch (final IOException e) + { + throw UserFailureException.fromTemplate(e, "IO exception exporting."); + } + } + + private ExportResult doExport(final String sessionToken, final ExportData exportData, final ExportOptions exportOptions) + throws IOException + { + final IApplicationServerInternalApi applicationServerApi = CommonServiceProvider.getApplicationServerApi(); + + final List<ExportablePermId> exportablePermIds = exportData.getPermIds().stream() + .map(exportablePermIdDto -> new ExportablePermId( + ExportableKind.valueOf(exportablePermIdDto.getExportableKind().name()), exportablePermIdDto.getPermId())) + .collect(Collectors.toList()); + final Set<ExportableKind> exportableKinds = exportablePermIds.stream() + .map(ExportablePermId::getExportableKind) + .collect(Collectors.toSet()); + + final IExportableFields fields = exportData.getFields(); + final Map<String, Map<String, List<Map<String, String>>>> exportFields; + if (fields instanceof SelectedFields) + { + final SelectedFields selectedFields = (SelectedFields) fields; + final Set<IPropertyTypeId> properties = new HashSet<>(selectedFields.getProperties()); + + exportFields = exportableKinds.stream().flatMap(exportableKind -> + { + final IExportFieldsFinder fieldsFinder = FIELDS_FINDER_BY_EXPORTABLE_KIND.get(exportableKind); + if (fieldsFinder != null) + { + final Map<String, List<Map<String, String>>> selectedFieldMap = + fieldsFinder.findExportFields(properties, applicationServerApi, sessionToken, selectedFields); + return Stream.of(new AbstractMap.SimpleEntry<>(exportableKind.name(), selectedFieldMap)); + } else if (TYPE_EXPORTABLE_KINDS.contains(exportableKind)) + { + final Map<String, List<Map<String, String>>> selectedAttributesMap = findExportAttributes(exportableKind, selectedFields); + return Stream.of(new AbstractMap.SimpleEntry<>(TYPE_EXPORT_FIELD_KEY, selectedAttributesMap)); + } else + { + return Stream.empty(); + } + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } else + { + exportFields = null; + } + + return doExport(applicationServerApi, sessionToken, + exportablePermIds, exportOptions.isWithReferredTypes(), exportFields, + TextFormatting.valueOf(exportOptions.getXlsTextFormat().name()), exportOptions.isWithImportCompatibility(), + exportOptions.getFormats()); + } + + private ExportResult doExport(final IApplicationServerApi api, + final String sessionToken, final List<ExportablePermId> exportablePermIds, + final boolean exportReferredMasterData, + final Map<String, Map<String, List<Map<String, String>>>> exportFields, + final TextFormatting textFormatting, final boolean compatibleWithImport, + final Set<ExportFormat> exportFormats) throws IOException + { + final String zipFileName = String.format("%s.%s%s", EXPORT_FILE_PREFIX, new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS").format(new Date()), + ZIP_EXTENSION); + final Collection<String> warnings = new ArrayList<>(); + + final boolean hasXlsxFormat = exportFormats.contains(ExportFormat.XLSX); + final boolean hasHtmlFormat = exportFormats.contains(ExportFormat.HTML); + final boolean hasPdfFormat = exportFormats.contains(ExportFormat.PDF); + final boolean hasDataFormat = exportFormats.contains(ExportFormat.DATA); + + if (hasXlsxFormat) + { + exportXlsx(api, sessionToken, exportablePermIds, exportReferredMasterData, exportFields, textFormatting, compatibleWithImport, warnings); + } + + if (hasHtmlFormat || hasPdfFormat || hasDataFormat) + { + final EntitiesVo entitiesVo = new EntitiesVo(sessionToken, exportablePermIds); + + if (hasPdfFormat || hasHtmlFormat) + { + final ISessionWorkspaceProvider sessionWorkspaceProvider = CommonServiceProvider.getSessionWorkspaceProvider(); + final File sessionWorkspaceDirectory = sessionWorkspaceProvider.getSessionWorkspace(sessionToken).getCanonicalFile(); + final File docDirectory = new File(sessionWorkspaceDirectory, PDF_DIRECTORY); + mkdirs(docDirectory); + + exportSpacesDoc(sessionToken, exportFields, entitiesVo, exportFormats, docDirectory); + exportProjectsDoc(sessionToken, docDirectory, entitiesVo, exportFields, exportFormats); + exportExperimentsDoc(sessionToken, docDirectory, entitiesVo, exportFields, exportFormats); + exportSamplesDoc(sessionToken, docDirectory, entitiesVo, exportFields, exportFormats); + exportDataSetsDoc(sessionToken, docDirectory, entitiesVo, exportFields, exportFormats); + } + + if (hasDataFormat) + { + exportData(sessionToken, entitiesVo); + } + } + + final ISessionWorkspaceProvider sessionWorkspaceProvider = CommonServiceProvider.getSessionWorkspaceProvider(); + final String sessionWorkspaceDirectoryPath = sessionWorkspaceProvider.getSessionWorkspace(sessionToken).getCanonicalPath(); + zipDirectory(sessionWorkspaceDirectoryPath, new File(sessionWorkspaceDirectoryPath, zipFileName)); + + deleteDirectory(sessionWorkspaceDirectoryPath + '/' + XLSX_DIRECTORY); + deleteDirectory(sessionWorkspaceDirectoryPath + '/' + PDF_DIRECTORY); + deleteDirectory(sessionWorkspaceDirectoryPath + '/' + DATA_DIRECTORY); + + return new ExportResult(zipFileName, warnings); + } + + private static void exportXlsx(final IApplicationServerApi api, final String sessionToken, final List<ExportablePermId> exportablePermIds, + final boolean exportReferredMasterData, final Map<String, Map<String, List<Map<String, String>>>> exportFields, + final TextFormatting textFormatting, final boolean compatibleWithImport, final Collection<String> warnings) throws IOException + { + final ISessionWorkspaceProvider sessionWorkspaceProvider = CommonServiceProvider.getSessionWorkspaceProvider(); + final File sessionWorkspaceDirectory = sessionWorkspaceProvider.getSessionWorkspace(sessionToken).getCanonicalFile(); + final XLSExport.PrepareWorkbookResult xlsExportResult = XLSExport.prepareWorkbook(api, sessionToken, exportablePermIds, + exportReferredMasterData, exportFields, textFormatting, compatibleWithImport); + + final File xlsxDirectory = new File(sessionWorkspaceDirectory, XLSX_DIRECTORY); + mkdirs(xlsxDirectory); + + final File scriptsDirectory = new File(xlsxDirectory, SCRIPTS_DIRECTORY); + + final Map<String, String> xlsExportScripts = xlsExportResult.getScripts(); + if (!xlsExportScripts.isEmpty()) + { + mkdirs(scriptsDirectory); + + for (final Map.Entry<String, String> script : xlsExportScripts.entrySet()) + { + final File scriptFile = new File(scriptsDirectory, script.getKey() + PYTHON_EXTENSION); + try (final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(scriptFile), BUFFER_SIZE)) + { + bos.write(script.getValue().getBytes()); + bos.flush(); + } + } + } + + try ( + final Workbook wb = xlsExportResult.getWorkbook(); + final BufferedOutputStream bos = new BufferedOutputStream( + new FileOutputStream(new File(xlsxDirectory, METADATA_FILE_NAME)), BUFFER_SIZE); + ) + { + wb.write(bos); + } + + warnings.addAll(xlsExportResult.getWarnings()); + } + + private void exportData(final String sessionToken, final EntitiesVo entitiesVo) throws IOException + { + final Collection<Sample> samples = entitiesVo.getSamples(); + for (final Sample sample : samples) + { + exportDatasetsData(sessionToken, 'O', sample.getDataSets(), sample, sample.getContainer()); + } + + final Collection<Experiment> experiments = entitiesVo.getExperiments(); + for (final Experiment experiment : experiments) + { + exportDatasetsData(sessionToken, 'E', experiment.getDataSets(), experiment, null); + } + } + + private void exportDatasetsData(final String sessionToken, + final char prefix, final List<DataSet> dataSets, final ICodeHolder codeHolder, + final Sample container) throws IOException + { + final String spaceCode = getSpaceCode(codeHolder); + final String projectCode = getProjectCode(codeHolder); + final String containerCode = container == null ? null : container.getCode(); + final String code = codeHolder.getCode(); + final String codeHolderJson = objectWriter.writeValueAsString(codeHolder); + final IDataStoreServerApi v3Dss = CommonServiceProvider.getDataStoreServerApi(); + + for (final DataSet dataSet : dataSets) + { + final String dataSetPermId = dataSet.getPermId().getPermId(); + final String dataSetCode = dataSet.getCode(); + final String dataSetTypeCode = dataSet.getType().getCode(); + final String dataSetName = getEntityName(dataSet); + + createMetadataJsonFile(sessionToken, prefix, spaceCode, projectCode, containerCode, code, dataSetTypeCode, + dataSetCode, dataSetName, codeHolderJson); + + if (dataSet.getKind() != DataSetKind.LINK) + { + final DataSetFileSearchCriteria criteria = new DataSetFileSearchCriteria(); + criteria.withDataSet().withPermId().thatEquals(dataSetPermId); + + final SearchResult<DataSetFile> results = v3Dss.searchFiles(sessionToken, criteria, new DataSetFileFetchOptions()); + + OPERATION_LOG.info(String.format("Found: %d files", results.getTotalCount())); + + final List<DataSetFile> dataSetFiles = results.getObjects(); + final List<DataSetFilePermId> fileIds = dataSetFiles.stream().map(DataSetFile::getPermId).collect(Collectors.toList()); + + final DataSetFileDownloadOptions options = new DataSetFileDownloadOptions(); + options.setRecursive(true); + + try (final InputStream is = v3Dss.downloadFiles(sessionToken, fileIds, options)) + { + final DataSetFileDownloadReader reader = new DataSetFileDownloadReader(is); + DataSetFileDownload file; + while ((file = reader.read()) != null) + { + createNextDataFile(sessionToken, prefix, spaceCode, projectCode, + containerCode, code, dataSetTypeCode, dataSetCode, dataSetName, file); + } + } + } else + { + OPERATION_LOG.info(String.format("Omitted data export for link dataset with permId: %s", dataSetPermId)); + } + } + } + + private static void createMetadataJsonFile(final String sessionToken, final char prefix, final String spaceCode, final String projectCode, + final String containerCode, final String code, final String dataSetTypeCode, final String dataSetCode, final String dataSetName, + final String codeHolderJson) throws IOException + { + final ISessionWorkspaceProvider sessionWorkspaceProvider = CommonServiceProvider.getSessionWorkspaceProvider(); + final File sessionWorkspaceDirectory = sessionWorkspaceProvider.getSessionWorkspace(sessionToken).getCanonicalFile(); + final File dataDirectory = new File(sessionWorkspaceDirectory, DATA_DIRECTORY); + mkdirs(dataDirectory); + + final File metadataFile = new File(dataDirectory, + getDataDirectoryName(prefix, spaceCode, projectCode, containerCode, code, dataSetTypeCode, dataSetCode, dataSetName, META_FILE_NAME)); + + final File dataSubdirectory = metadataFile.getParentFile(); + mkdirs(dataSubdirectory); + + try (final OutputStream os = new BufferedOutputStream(new FileOutputStream(metadataFile))) + { + writeInChunks(os, codeHolderJson.getBytes(StandardCharsets.UTF_8)); + } + } + + private void exportSpacesDoc(final String sessionToken, final Map<String, Map<String, List<Map<String, String>>>> exportFields, + final EntitiesVo entitiesVo, final Set<ExportFormat> exportFormats, final File docDirectory) throws IOException + { + createFilesAndFoldersForSpacesOfEntities(sessionToken, docDirectory, entitiesVo.getSpaces(), exportFields, exportFormats); + createFilesAndFoldersForSpacesOfEntities(sessionToken, docDirectory, entitiesVo.getProjects(), exportFields, exportFormats); + createFilesAndFoldersForSpacesOfEntities(sessionToken, docDirectory, entitiesVo.getExperiments(), exportFields, exportFormats); + createFilesAndFoldersForSpacesOfEntities(sessionToken, docDirectory, entitiesVo.getSamples(), exportFields, exportFormats); + } + + private void createFilesAndFoldersForSpacesOfEntities(final String sessionToken, final File docDirectory, final Collection<?> entities, + final Map<String, Map<String, List<Map<String, String>>>> exportFields, final Set<ExportFormat> exportFormats) throws IOException + { + final boolean hasHtmlFormat = exportFormats.contains(ExportFormat.HTML); + final boolean hasPdfFormat = exportFormats.contains(ExportFormat.PDF); + + for (final Object entity : entities) + { + if (entity instanceof Space) + { + final Space space = (Space) entity; + + final Map<String, List<Map<String, String>>> entityTypeExportFieldsMap = getEntityTypeExportFieldsMap(exportFields, SPACE); + final String html = getHtml(sessionToken, space, entityTypeExportFieldsMap); + final byte[] htmlBytes = html.getBytes(StandardCharsets.UTF_8); + + if (hasHtmlFormat) + { + final File htmlFile = createNextDocFile(docDirectory, space.getCode(), null, null, null, null, null, null, null, HTML_EXTENSION); + try (final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(htmlFile), BUFFER_SIZE)) + { + writeInChunks(bos, htmlBytes); + bos.flush(); + } + } + + if (hasPdfFormat) + { + final File pdfFile = createNextDocFile(docDirectory, space.getCode(), null, null, null, null, null, null, null, PDF_EXTENSION); + try (final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(pdfFile), BUFFER_SIZE)) + { + final PdfRendererBuilder builder = new PdfRendererBuilder(); + builder.withHtmlContent(html, null); + builder.toStream(bos); + builder.run(); + } + } + } else + { + final String spaceCode = getSpaceCode(entity); + final String folderName = spaceCode == null && entity instanceof Sample ? SHARED_SAMPLES_DIRECTORY : spaceCode; + final File space = createNextDocFile(docDirectory, folderName, null, null, null, null, null, null, null, null); + mkdirs(space); + } + } + } + + private void exportProjectsDoc(final String sessionToken, final File docDirectory, final EntitiesVo entitiesVo, + final Map<String, Map<String, List<Map<String, String>>>> exportFields, final Set<ExportFormat> exportFormats) throws IOException + { + createFilesAndFoldersForProjectsOfEntities(sessionToken, docDirectory, entitiesVo.getProjects(), exportFields, exportFormats); + createFilesAndFoldersForProjectsOfEntities(sessionToken, docDirectory, entitiesVo.getExperiments(), exportFields, exportFormats); + createFilesAndFoldersForProjectsOfEntities(sessionToken, docDirectory, entitiesVo.getSamples(), exportFields, exportFormats); + } + + private void createFilesAndFoldersForProjectsOfEntities(final String sessionToken, final File docDirectory, final Collection<?> entities, + final Map<String, Map<String, List<Map<String, String>>>> exportFields, final Set<ExportFormat> exportFormats) throws IOException + { + for (final Object entity : entities) + { + if (entity instanceof Project) + { + final Project project = (Project) entity; + final Map<String, List<Map<String, String>>> entityTypeExportFieldsMap = getEntityTypeExportFieldsMap(exportFields, PROJECT); + createDocFilesForEntity(sessionToken, docDirectory, entityTypeExportFieldsMap, project, + project.getSpace().getCode(), project.getCode(), null, null, null, null, null, null, + exportFormats); + } else + { + final String projectCode = getProjectCode(entity); + if (projectCode != null) + { + final File space = createNextDocFile(docDirectory, getSpaceCode(entity), projectCode, null, null, null, null, null, null, null); + mkdirs(space); + } + } + } + } + + private void exportExperimentsDoc(final String sessionToken, final File docDirectory, final EntitiesVo entitiesVo, + final Map<String, Map<String, List<Map<String, String>>>> exportFields, final Set<ExportFormat> exportFormats) throws IOException + { + createFilesAndFoldersForExperimentsOfEntities(sessionToken, docDirectory, entitiesVo.getExperiments(), exportFields, exportFormats); + createFilesAndFoldersForExperimentsOfEntities(sessionToken, docDirectory, entitiesVo.getSamples(), exportFields, exportFormats); + createFilesAndFoldersForExperimentsOfEntities(sessionToken, docDirectory, entitiesVo.getDataSets(), exportFields, exportFormats); + } + + private void createFilesAndFoldersForExperimentsOfEntities(final String sessionToken, final File docDirectory, + final Collection<?> entities, final Map<String, Map<String, List<Map<String, String>>>> exportFields, + final Set<ExportFormat> exportFormats) throws IOException + { + for (final Object entity : entities) + { + if (entity instanceof IExperimentHolder) + { + final Experiment experiment = ((IExperimentHolder) entity).getExperiment(); + if (experiment != null) + { + final Project project = experiment.getProject(); + final File docFile = createNextDocFile(docDirectory, project.getSpace().getCode(), project.getCode(), experiment.getCode(), + getEntityName(experiment), null, null, null, null, null); + mkdirs(docFile); + } + } + + if (entity instanceof Experiment) + { + final Experiment experiment = (Experiment) entity; + final Project project = experiment.getProject(); + final Map<String, List<Map<String, String>>> entityTypeExportFieldsMap = getEntityTypeExportFieldsMap(exportFields, EXPERIMENT); + createDocFilesForEntity(sessionToken, docDirectory, entityTypeExportFieldsMap, experiment, + project.getSpace().getCode(), project.getCode(), experiment.getCode(), getEntityName(experiment), null, null, null, null, + exportFormats); + } + } + } + + private void exportSamplesDoc(final String sessionToken, final File docDirectory, final EntitiesVo entitiesVo, + final Map<String, Map<String, List<Map<String, String>>>> exportFields, final Set<ExportFormat> exportFormats) + throws IOException + { + createFilesAndFoldersForSamplesOfEntities(sessionToken, docDirectory, entitiesVo.getSamples(), exportFields, exportFormats); + } + + private static Map<String, List<Map<String, String>>> getEntityTypeExportFieldsMap( + final Map<String, Map<String, List<Map<String, String>>>> exportFields, final ExportableKind exportableKind) + { + return exportFields == null + ? null + : exportFields.get(MASTER_DATA_EXPORTABLE_KINDS.contains(exportableKind) || exportableKind == SPACE || exportableKind == PROJECT + ? TYPE_EXPORT_FIELD_KEY : exportableKind.toString()); + } + + private void createFilesAndFoldersForSamplesOfEntities(final String sessionToken, final File docDirectory, + final Collection<?> entities, final Map<String, Map<String, List<Map<String, String>>>> exportFields, + final Set<ExportFormat> exportFormats) throws IOException + { + for (final Object entity : entities) + { + if (entity instanceof ISampleHolder) + { + final Sample sample = ((ISampleHolder) entity).getSample(); + final Experiment experiment = sample.getExperiment(); + final File docFile; + + if (experiment != null) + { + final Project project = experiment.getProject(); + docFile = createNextDocFile(docDirectory, project.getSpace().getCode(), project.getCode(), experiment.getCode(), + getEntityName(experiment), null, null, null, null, null); + } else + { + final Project project = sample.getProject(); + if (project != null) + { + docFile = createNextDocFile(docDirectory, project.getSpace().getCode(), project.getCode(), null, + null, null, null, null, null, null); + } else + { + final Space space = sample.getSpace(); + docFile = createNextDocFile(docDirectory, space != null ? space.getCode() : SHARED_SAMPLES_DIRECTORY, null, null, + null, null, null, null, null, null); + } + } + + mkdirs(docFile); + } + + if (entity instanceof Sample) + { + final Sample sample = (Sample) entity; + final Experiment experiment = sample.getExperiment(); + final Sample container = sample.getContainer(); + + final String spaceCode = getSpaceCode(sample); + final String spaceFolder = spaceCode != null ? spaceCode : SHARED_SAMPLES_DIRECTORY; + final Map<String, List<Map<String, String>>> entityTypeExportFieldsMap = getEntityTypeExportFieldsMap(exportFields, SAMPLE); + + createDocFilesForEntity(sessionToken, docDirectory, entityTypeExportFieldsMap, sample, + spaceFolder, getProjectCode(sample), experiment != null ? experiment.getCode() : null, + experiment != null ? getEntityName(experiment) : null, container != null ? container.getCode() : null, sample.getCode(), + getEntityName(sample), null, exportFormats); + } + } + } + + private void exportDataSetsDoc(final String sessionToken, final File docDirectory, final EntitiesVo entitiesVo, + final Map<String, Map<String, List<Map<String, String>>>> exportFields, final Set<ExportFormat> exportFormats) throws IOException + { + createFilesAndFoldersForDataSetsOfEntities(sessionToken, docDirectory, entitiesVo.getDataSets(), exportFields, exportFormats); + } + + private void createFilesAndFoldersForDataSetsOfEntities(final String sessionToken, final File docDirectory, + final Collection<?> entities, final Map<String, Map<String, List<Map<String, String>>>> exportFields, + final Set<ExportFormat> exportFormats) throws IOException + { + for (final Object entity : entities) + { + if (entity instanceof DataSet) + { + final DataSet dataSet = (DataSet) entity; + final Sample sample = dataSet.getSample(); + final Sample container = sample != null ? sample.getContainer() : null; + final Experiment experiment = sample != null ? sample.getExperiment() : dataSet.getExperiment(); + final Map<String, List<Map<String, String>>> entityTypeExportFieldsMap = getEntityTypeExportFieldsMap(exportFields, DATASET); + + createDocFilesForEntity(sessionToken, docDirectory, entityTypeExportFieldsMap, dataSet, + getSpaceCode(entity), getProjectCode(entity), experiment != null ? experiment.getCode() : null, + experiment != null ? getEntityName(experiment) : null, container != null ? container.getCode() : null, + sample != null ? sample.getCode() : null, sample != null ? getEntityName(sample) : null, dataSet.getCode(), exportFormats); + } + } + } + + private static String getSpaceCode(final Object entity) + { + if (entity instanceof Space) + { + return getSpaceCode((Space) entity); + } else if (entity instanceof Project) + { + return getSpaceCode((Project) entity); + } else if (entity instanceof Experiment) + { + return getSpaceCode((Experiment) entity); + } else if (entity instanceof Sample) + { + return getSpaceCode((Sample) entity); + } else if (entity instanceof DataSet) + { + return getSpaceCode((DataSet) entity); + } else + { + throw new IllegalArgumentException(); + } + } + + private static String getSpaceCode(final Space entity) + { + return entity.getCode(); + } + + private static String getSpaceCode(final Project entity) + { + return entity.getSpace().getCode(); + } + + private static String getSpaceCode(final Experiment entity) + { + return entity.getProject().getSpace().getCode(); + } + + private static String getSpaceCode(final Sample sample) + { + final Space space = sample.getSpace(); + if (space != null) + { + return sample.getSpace().getCode(); + } else + { + final Experiment experiment = sample.getExperiment(); + final Project project = sample.getProject(); + if (experiment != null) + { + return experiment.getProject().getSpace().getCode(); + } else if (project != null) + { + return project.getSpace().getCode(); + } else + { + return null; + } + } + } + + private static String getSpaceCode(final DataSet dataSet) + { + final Sample sample = dataSet.getSample(); + return sample != null ? getSpaceCode(sample) : getSpaceCode(dataSet.getExperiment()); + } + + private static String getProjectCode(final Object entity) + { + if (entity instanceof Project) + { + return getProjectCode((Project) entity); + } else if (entity instanceof Experiment) + { + return getProjectCode((Experiment) entity); + } else if (entity instanceof Sample) + { + return getProjectCode((Sample) entity); + } else if (entity instanceof DataSet) + { + return getProjectCode((DataSet) entity); + } else + { + throw new IllegalArgumentException(); + } + } + + private static String getProjectCode(final Project project) + { + return project.getCode(); + } + + private static String getProjectCode(final Experiment experiment) + { + return experiment.getProject().getCode(); + } + + private static String getProjectCode(final Sample sample) + { + final Project project = getProjectForSample(sample); + return project != null ? project.getCode() : null; + } + + private static String getProjectCode(final DataSet dataSet) + { + final Sample sample = dataSet.getSample(); + return sample != null ? getProjectCode(sample) : getProjectCode(dataSet.getExperiment()); + } + + private static Project getProjectForSample(final Sample sample) + { + final Experiment experiment = sample.getExperiment(); + if (experiment != null) + { + return experiment.getProject(); + } else + { + return sample.getProject(); + } + } + + private static void writeInChunks(final OutputStream os, final byte[] bytes) throws IOException + { + final int length = bytes.length; + for (int pos = 0; pos < length; pos += BUFFER_SIZE) + { + os.write(Arrays.copyOfRange(bytes, pos, Math.min(pos + BUFFER_SIZE, length))); + } + os.flush(); + } + + private static void writeInChunks(final OutputStream os, final InputStream is) throws IOException + { + final byte[] buffer = new byte[BUFFER_SIZE]; + int length; + while ((length = is.read(buffer)) > 0) + { + os.write(buffer, 0, length); + } + os.flush(); + } + + private static File createNextDocFile(final File docDirectory, final String spaceCode, final String projectCode, final String experimentCode, + final String experimentName, final String containerCode, final String sampleCode, final String sampleName, final String dataSetCode, + final String extension) + { + return new File(docDirectory, getNextDocDirectoryName(spaceCode, projectCode, experimentCode, experimentName, containerCode, sampleCode, + sampleName, dataSetCode, extension)); + } + + private void createDocFilesForEntity(final String sessionToken, final File docDirectory, + final Map<String, List<Map<String, String>>> entityTypeExportFieldsMap, + final ICodeHolder entity, final String spaceCode, final String projectCode, final String experimentCode, + final String experimentName, final String containerCode, final String sampleCode, final String sampleName, final String dataSetCode, + final Set<ExportFormat> exportFormats) throws IOException + { + final boolean hasHtmlFormat = exportFormats.contains(ExportFormat.HTML); + final boolean hasPdfFormat = exportFormats.contains(ExportFormat.PDF); + final String html = getHtml(sessionToken, entity, entityTypeExportFieldsMap); + final byte[] htmlBytes = html.getBytes(StandardCharsets.UTF_8); + + if (hasHtmlFormat) + { + final File htmlFile = createNextDocFile(docDirectory, spaceCode, projectCode, experimentCode, experimentName, containerCode, sampleCode, + sampleName, dataSetCode, HTML_EXTENSION); + try (final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(htmlFile), BUFFER_SIZE)) + { + writeInChunks(bos, htmlBytes); + bos.flush(); + } + } + + if (hasPdfFormat) + { + final File pdfFile = createNextDocFile(docDirectory, spaceCode, projectCode, experimentCode, experimentName, containerCode, sampleCode, + sampleName, dataSetCode, PDF_EXTENSION); + try (final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(pdfFile), BUFFER_SIZE)) + { + final PdfRendererBuilder builder = new PdfRendererBuilder(); + builder.withHtmlContent(html, null); + builder.toStream(bos); + builder.run(); + } + } + } + + static String getNextDocDirectoryName(final String spaceCode, final String projectCode, final String experimentCode, final String experimentName, + final String containerCode, final String sampleCode, final String sampleName, final String dataSetCode, final String extension) + { + final StringBuilder entryBuilder = new StringBuilder(); + + if (spaceCode == null && (projectCode != null || experimentCode != null || dataSetCode != null || (sampleCode == null && extension != null))) + { + throw new IllegalArgumentException(); + } else if (spaceCode != null) + { + entryBuilder.append(spaceCode); + } + + if (projectCode != null) + { + entryBuilder.append('/').append(projectCode); + if (experimentCode != null) + { + entryBuilder.append('/'); + addFullEntityName(entryBuilder, null, experimentCode, experimentName); + + if (sampleCode == null && dataSetCode != null) + { + // Experiment data set + entryBuilder.append('/').append(dataSetCode); + } + } else if (sampleCode == null && dataSetCode != null) + { + throw new IllegalArgumentException(); + } + } else if (experimentCode != null || (dataSetCode != null && sampleCode == null)) + { + throw new IllegalArgumentException(); + } + + if (sampleCode != null) + { + if (spaceCode != null) + { + entryBuilder.append('/'); + } + addFullEntityName(entryBuilder, containerCode, sampleCode, sampleName); + + if (dataSetCode != null) + { + // Sample data set + entryBuilder.append('/').append(dataSetCode); + } + } + + entryBuilder.append(extension != null ? extension : '/'); + return entryBuilder.toString(); + } + + private static void createNextDataFile(final String sessionToken, final char prefix, final String spaceCode, final String projectCode, + final String containerCode, final String entityCode, final String dataSetTypeCode, final String dataSetCode, + final String dataSetName, final DataSetFileDownload dataSetFileDownload) throws IOException + { + final ISessionWorkspaceProvider sessionWorkspaceProvider = CommonServiceProvider.getSessionWorkspaceProvider(); + final File sessionWorkspaceDirectory = sessionWorkspaceProvider.getSessionWorkspace(sessionToken).getCanonicalFile(); + + final DataSetFile dataSetFile = dataSetFileDownload.getDataSetFile(); + final String filePath = dataSetFile.getPath(); + final boolean isDirectory = dataSetFile.isDirectory(); + + final File dataDirectory = new File(sessionWorkspaceDirectory, DATA_DIRECTORY); + mkdirs(dataDirectory); + + final File dataSetFsEntry = new File(dataDirectory, getDataDirectoryName(prefix, spaceCode, projectCode, containerCode, entityCode, + dataSetTypeCode, dataSetCode, dataSetName, filePath) + (isDirectory ? "/" : "")); + + final File dataSubdirectory = dataSetFsEntry.getParentFile(); + mkdirs(dataSubdirectory); + + if (!isDirectory) + { + try ( + final InputStream is = dataSetFileDownload.getInputStream(); + final BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(dataSetFsEntry)) + ) + { + writeInChunks(os, is); + } + } else + { + mkdirs(dataSetFsEntry); + } + } + + static String getDataDirectoryName(final char prefix, final String spaceCode, final String projectCode, + final String containerCode, final String entityCode, final String dataSetTypeCode, + final String dataSetCode, final String dataSetName, final String fileName) + { + if (prefix != 'O' && prefix != 'E') + { + throw new IllegalArgumentException(String.format("Only 'O' and 'E' can be used as prefix got '%c' instead.", prefix)); + } + + if (containerCode != null && prefix != 'O') + { + throw new IllegalArgumentException("Only objects can have containers."); + } + + final StringBuilder entryBuilder = new StringBuilder(String.valueOf(prefix)); + + if (spaceCode != null) + { + entryBuilder.append('+').append(spaceCode); + } else if (prefix == 'E') + { + throw new IllegalArgumentException("Space code cannot be null for experiments."); + } else if (projectCode != null) + { + throw new IllegalArgumentException("If space code is null project code should be also null."); + } + + if (projectCode != null) + { + entryBuilder.append('+').append(projectCode); + } else if (prefix == 'E') + { + throw new IllegalArgumentException("Project code cannot be null for experiments."); + } + + if (entityCode != null) + { + entryBuilder.append('+'); + addFullEntityCode(entryBuilder, containerCode, entityCode); + } else + { + throw new IllegalArgumentException("Entity code is mandatory"); + } + + if (dataSetTypeCode != null) + { + entryBuilder.append('+').append(dataSetTypeCode); + } else + { + throw new IllegalArgumentException("Data set type code is mandatory"); + } + + if (dataSetCode != null) + { + entryBuilder.append('+'); + addFullEntityName(entryBuilder, null, dataSetCode, dataSetName); + } else + { + throw new IllegalArgumentException("Data set code is mandatory"); + } + + if (fileName != null) + { + entryBuilder.append('/').append(fileName); + } + + return entryBuilder.toString(); + } + + private static void addFullEntityName(final StringBuilder entryBuilder, final String containerCode, final String entityCode, + final String entityName) + { + if (entityName == null || entityName.isEmpty()) + { + addFullEntityCode(entryBuilder, containerCode, entityCode); + } else + { + entryBuilder.append(entityName).append(" ("); + addFullEntityCode(entryBuilder, containerCode, entityCode); + entryBuilder.append(")"); + } + } + + private static void addFullEntityCode(final StringBuilder entryBuilder, final String containerCode, final String entityCode) + { + if (containerCode != null) + { + entryBuilder.append(containerCode).append('*'); + } + + entryBuilder.append(entityCode); + } + + private static String getEntityName(final IPropertiesHolder entity) + { + try + { + return escapeUnsafeCharacters(entity.getVarcharProperty(NAME_PROPERTY_NAME)); + } catch (final NotFetchedException e) + { + return null; + } + } + + static String escapeUnsafeCharacters(final String name) + { + return name != null ? name.replaceAll(UNSAFE_CHARACTERS_REGEXP, "_") : null; + } + + private String getHtml(final String sessionToken, final ICodeHolder entityObj, + final Map<String, List<Map<String, String>>> entityTypeExportFieldsMap) throws IOException + { + final IApplicationServerInternalApi v3 = CommonServiceProvider.getApplicationServerApi(); + + final DocumentBuilder documentBuilder = new DocumentBuilder(); + documentBuilder.addTitle(entityObj.getCode()); + documentBuilder.addHeader("Identification Info"); + + final IEntityType typeObj; + if (entityObj instanceof Experiment) + { + documentBuilder.addProperty(KIND_DOCUMENT_PROPERTY_ID, "Experiment"); + final ExperimentTypeSearchCriteria searchCriteria = new ExperimentTypeSearchCriteria(); + searchCriteria.withCode().thatEquals(((Experiment) entityObj).getType().getCode()); + final ExperimentTypeFetchOptions fetchOptions = new ExperimentTypeFetchOptions(); + fetchOptions.withPropertyAssignments().withPropertyType(); + final SearchResult<ExperimentType> results = v3.searchExperimentTypes(sessionToken, searchCriteria, fetchOptions); + typeObj = results.getObjects().get(0); + } else if (entityObj instanceof Sample) + { + documentBuilder.addProperty(KIND_DOCUMENT_PROPERTY_ID, "Sample"); + final SampleTypeSearchCriteria searchCriteria = new SampleTypeSearchCriteria(); + searchCriteria.withCode().thatEquals(((Sample) entityObj).getType().getCode()); + final SampleTypeFetchOptions fetchOptions = new SampleTypeFetchOptions(); + fetchOptions.withPropertyAssignments().withPropertyType(); + final SearchResult<SampleType> results = v3.searchSampleTypes(sessionToken, searchCriteria, fetchOptions); + typeObj = results.getObjects().get(0); + } else if (entityObj instanceof DataSet) + { + final DataSet dataSet = (DataSet) entityObj; + documentBuilder.addProperty(KIND_DOCUMENT_PROPERTY_ID, "DataSet"); + final DataSetTypeSearchCriteria searchCriteria = new DataSetTypeSearchCriteria(); + searchCriteria.withCode().thatEquals(dataSet.getType().getCode()); + final DataSetTypeFetchOptions fetchOptions = new DataSetTypeFetchOptions(); + fetchOptions.withPropertyAssignments().withPropertyType(); + final SearchResult<DataSetType> results = v3.searchDataSetTypes(sessionToken, searchCriteria, fetchOptions); + typeObj = results.getObjects().get(0); + } else + { + typeObj = null; + } + + if (entityObj instanceof Project) + { + documentBuilder.addProperty(KIND_DOCUMENT_PROPERTY_ID, "Project"); + } else if (entityObj instanceof Space) + { + documentBuilder.addProperty(KIND_DOCUMENT_PROPERTY_ID, "Space"); + } else + { + documentBuilder.addProperty("Type", ((IEntityTypeHolder) entityObj).getType().getCode()); + } + + final List<Map<String, String>> selectedExportFields; + if (entityTypeExportFieldsMap == null || entityTypeExportFieldsMap.isEmpty()) + { + selectedExportFields = null; + } else if (typeObj != null) + { + selectedExportFields = entityTypeExportFieldsMap.get(typeObj.getCode()); + } else if (entityObj instanceof Space) + { + selectedExportFields = entityTypeExportFieldsMap.get(SPACE.name()); + } else if (entityObj instanceof Project) + { + selectedExportFields = entityTypeExportFieldsMap.get(PROJECT.name()); + } else + { + selectedExportFields = null; + } + + final Set<String> selectedExportAttributes = selectedExportFields != null + ? selectedExportFields.stream().filter(map -> Objects.equals(map.get(FIELD_TYPE_KEY), ATTRIBUTE.name())) + .map(map -> map.get(FIELD_ID_KEY)).collect(Collectors.toSet()) + : null; + + final Set<String> selectedExportProperties = selectedExportFields != null + ? selectedExportFields.stream().filter(map -> Objects.equals(map.get(FIELD_TYPE_KEY), PROPERTY.name())) + .map(map -> map.get(FIELD_ID_KEY)).collect(Collectors.toSet()) + : null; + + if (allowsValue(selectedExportAttributes, Attribute.CODE.name())) + { + documentBuilder.addProperty("Code", entityObj.getCode()); + } + + if (entityObj instanceof IPermIdHolder && allowsValue(selectedExportAttributes, Attribute.PERM_ID.name())) + { + documentBuilder.addProperty("Perm ID", ((IPermIdHolder) entityObj).getPermId().toString()); + } + + if (entityObj instanceof IIdentifierHolder && allowsValue(selectedExportAttributes, Attribute.IDENTIFIER.name())) + { + final ObjectIdentifier identifier = ((IIdentifierHolder) entityObj).getIdentifier(); + if (identifier != null) + { + documentBuilder.addProperty("Identifier", identifier.getIdentifier()); + } + } + + if (entityObj instanceof IRegistratorHolder && allowsValue(selectedExportAttributes, Attribute.REGISTRATOR.name())) + { + final Person registrator = ((IRegistratorHolder) entityObj).getRegistrator(); + if (registrator != null) + { + documentBuilder.addProperty("Registrator", registrator.getUserId()); + } + } + + if (entityObj instanceof IRegistrationDateHolder && allowsValue(selectedExportAttributes, Attribute.REGISTRATION_DATE.name())) + { + final Date registrationDate = ((IRegistrationDateHolder) entityObj).getRegistrationDate(); + if (registrationDate != null) + { + documentBuilder.addProperty("Registration Date", String.valueOf(registrationDate)); + } + } + + if (entityObj instanceof IModifierHolder && allowsValue(selectedExportAttributes, Attribute.MODIFIER.name())) + { + final Person modifier = ((IModifierHolder) entityObj).getModifier(); + if (modifier != null) + { + documentBuilder.addProperty("Modifier", modifier.getUserId()); + } + } + + if (entityObj instanceof IModificationDateHolder && allowsValue(selectedExportAttributes, Attribute.MODIFICATION_DATE.name())) + { + final Date modificationDate = ((IModificationDateHolder) entityObj).getModificationDate(); + if (modificationDate != null) + { + documentBuilder.addProperty("Modification Date", String.valueOf(modificationDate)); + } + } + + if (entityObj instanceof IDescriptionHolder && allowsValue(selectedExportAttributes, Attribute.DESCRIPTION.name())) + { + final String description = ((IDescriptionHolder) entityObj).getDescription(); + if (description != null) + { + documentBuilder.addHeader("Description"); + documentBuilder.addParagraph(description); + } + } + + if (entityObj instanceof IParentChildrenHolder<?>) + { + final IParentChildrenHolder<?> parentChildrenHolder = (IParentChildrenHolder<?>) entityObj; + if (allowsValue(selectedExportAttributes, Attribute.PARENTS.name())) + { + documentBuilder.addHeader("Parents"); + final List<?> parents = parentChildrenHolder.getParents(); + for (final Object parent : parents) + { + final String relCodeName = ((ICodeHolder) parent).getCode(); + final Map<String, Serializable> properties = ((IPropertiesHolder) parent).getProperties(); + final String name = getEntityName((IPropertiesHolder) parent); + documentBuilder.addParagraph(relCodeName + (name != null ? " (" + properties.get("NAME") + ")" : "")); + } + } + + if (allowsValue(selectedExportAttributes, Attribute.CHILDREN.name())) + { + documentBuilder.addHeader("Children"); + final List<?> children = parentChildrenHolder.getChildren(); + for (final Object child : children) + { + final String relCodeName = ((ICodeHolder) child).getCode(); + final Map<String, Serializable> properties = ((IPropertiesHolder) child).getProperties(); + final String name = getEntityName((IPropertiesHolder) child); + documentBuilder.addParagraph(relCodeName + (name != null ? " (" + properties.get("NAME") + ")" : "")); + } + } + } + + if (entityObj instanceof IPropertiesHolder) + { + documentBuilder.addHeader("Properties"); + if (typeObj != null) + { + final List<PropertyAssignment> propertyAssignments = typeObj.getPropertyAssignments(); + if (propertyAssignments != null) + { + final Map<String, Serializable> properties = ((IPropertiesHolder) entityObj).getProperties(); + for (final PropertyAssignment propertyAssignment : propertyAssignments) + { + System.out.println(selectedExportFields); + + final PropertyType propertyType = propertyAssignment.getPropertyType(); + final String propertyTypeCode = propertyType.getCode(); + final Object rawPropertyValue = properties.get(propertyTypeCode); + + if (rawPropertyValue != null && allowsValue(selectedExportProperties, propertyTypeCode)) + { + final String initialPropertyValue = String.valueOf(rawPropertyValue); + final String propertyValue; + + if (propertyType.getDataType() == DataType.MULTILINE_VARCHAR && + Objects.equals(propertyType.getMetaData().get("custom_widget"), "Word Processor")) + { + final StringBuilder propertyValueBuilder = new StringBuilder(initialPropertyValue); + final Document doc = Jsoup.parse(initialPropertyValue); + final Elements imageElements = doc.select("img"); + for (final Element imageElement : imageElements) + { + final String imageSrc = imageElement.attr("src"); + replaceAll(propertyValueBuilder, imageSrc, encodeImageContentToString(imageSrc)); + } + propertyValue = propertyValueBuilder.toString(); + } else if (propertyType.getDataType() == DataType.XML + && Objects.equals(propertyType.getMetaData().get("custom_widget"), "Spreadsheet") + && initialPropertyValue.toUpperCase().startsWith(DATA_TAG_START) && initialPropertyValue.toUpperCase() + .endsWith(DATA_TAG_END)) + { + final String subString = initialPropertyValue.substring(DATA_TAG_START_LENGTH, + initialPropertyValue.length() - DATA_TAG_END_LENGTH); + final String decodedString = new String(Base64.getDecoder().decode(subString), StandardCharsets.UTF_8); + final ObjectMapper objectMapper = new ObjectMapper(); + final JsonNode jsonNode = objectMapper.readTree(decodedString); + propertyValue = convertJsonToHtml(jsonNode); + } else + { + propertyValue = initialPropertyValue; + } + + if (!Objects.equals(propertyValue, "\uFFFD(undefined)")) + { + documentBuilder.addProperty(propertyType.getLabel(), propertyValue); + } + } + } + } + } + } + + return documentBuilder.getHtml(); + } + + private String encodeImageContentToString(final String imageSrc) throws IOException + { + final Base64.Encoder encoder = Base64.getEncoder(); + final String extension = imageSrc.substring(imageSrc.lastIndexOf('.')); + final String mediaType = MEDIA_TYPE_BY_EXTENSION.getOrDefault(extension, DEFAULT_MEDIA_TYPE); + final String dataPrefix = String.format(DATA_PREFIX_TEMPLATE, mediaType); + final String filePath = getFilesRepository().getCanonicalPath() + "/" + imageSrc; + + final StringBuilder result = new StringBuilder(dataPrefix); + final FileInputStream fileInputStream = new FileInputStream(filePath); + try (final BufferedInputStream in = new BufferedInputStream(fileInputStream, BUFFER_SIZE)) + { + byte[] chunk = new byte[BUFFER_SIZE]; + int len; + while ((len = in.read(chunk)) == BUFFER_SIZE) { + result.append(encoder.encodeToString(chunk)); + } + + if (len > 0) { + chunk = Arrays.copyOf(chunk, len); + result.append(encoder.encodeToString(chunk)); + } + } + + return result.toString(); + } + + /** + * Whether the set does not forbid a value. + * + * @param set the set to look in + * @param value the value to be found + * @return <code>true</code> if set is <code>null</code> or value is in the set + */ + private boolean allowsValue(final Set<String> set, final String value) + { + return set == null || set.contains(value); + } + + private static String convertJsonToHtml(final TreeNode node) throws IOException + { + final TreeNode data = node.get("data"); + final TreeNode styles = node.get("style"); + + final StringBuilder tableBody = new StringBuilder(); + for (int i = 0; i < data.size(); i++) + { + final TreeNode dataRow = data.get(i); + tableBody.append("<tr>\n"); + for (int j = 0; j < dataRow.size(); j++) + { + final String stylesKey = convertNumericToAlphanumeric(i, j); + final String style = ((TextNode) styles.get(stylesKey)).textValue(); + final TextNode cell = (TextNode) dataRow.get(j); + tableBody.append(" <td style='").append(COMMON_STYLE).append(" ").append(style).append("'> ").append(cell.textValue()) + .append(" </td>\n"); + } + tableBody.append("</tr>\n"); + } + return String.format("<table style='%s'>\n%s\n%s", TABLE_STYLE, tableBody, "</table>"); + } + + private static String convertNumericToAlphanumeric(final int row, final int col) + { + final int aCharCode = (int) 'A'; + final int ord0 = col % 26; + final int ord1 = col / 26; + final char char0 = (char) (aCharCode + ord0); + final char char1 = (char) (aCharCode + ord1 - 1); + return String.valueOf(ord1 > 0 ? char1 : "") + char0 + (row + 1); + } + + private static void replaceAll(final StringBuilder sb, final String target, final String replacement) + { + // Start index for the first search + int startIndex = sb.indexOf(target); + while (startIndex != -1) + { + final int endIndex = startIndex + target.length(); + sb.replace(startIndex, endIndex, replacement); + // Update the start index for the next search + startIndex = sb.indexOf(target, startIndex + replacement.length()); + } + } + + private File getActualFile(final String sessionToken, final String actualResultFilePath) + { + final File sessionWorkspace = sessionWorkspaceProvider.getSessionWorkspace(sessionToken); + final File[] files = sessionWorkspace.listFiles((FilenameFilter) new NameFileFilter(actualResultFilePath)); + + assertNotNull(files); + assertEquals(1, files.length, String.format("Session workspace should contain only one file with the download URL '%s'.", + actualResultFilePath)); + + final File file = files[0]; + + assertTrue(file.getName().startsWith(METADATA_FILE_PREFIX + ".")); + return file; + } + + private static Map<String, List<Map<String, String>>> findExportAttributes(final ExportableKind exportableKind, final SelectedFields selectedFields) + { + final List<Map<String, String>> attributes = selectedFields.getAttributes().stream() + .map(attribute -> Map.of(IExportFieldsFinder.TYPE, ATTRIBUTE.name(), IExportFieldsFinder.ID, attribute.name())) + .collect(Collectors.toList()); + return Map.of(exportableKind.name(), attributes); + } + + private File getFilesRepository() + { + return new File(configurer.getResolvedProps().getProperty(REPO_PATH_KEY, DEFAULT_REPO_PATH)); + } + + /** + * Safely tries to create a directory if it does not exist. If it could not be created throws an exception. + * + * @param dir the directory to be created. + */ + private static void mkdirs(final File dir) + { + if (!dir.isDirectory()) + { + final boolean created = dir.mkdirs(); + if (!created) + { + throw new RuntimeException(String.format("Cannot create directory '%s'.", dir.getPath())); + } + } + } + + private static void zipDirectory(final String sourceDirectory, final File targetZipFile) throws IOException { + final Path sourceDir = Paths.get(sourceDirectory); + try (final ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(targetZipFile))) + { + try(final Stream<Path> stream = Files.walk(sourceDir)) + { + stream.filter(path -> !path.equals(sourceDir) && !path.toFile().equals(targetZipFile)) + .forEach(path -> + { + final boolean isDirectory = Files.isDirectory(path); + final String entryName = sourceDir.relativize(path).toString(); + final ZipEntry zipEntry = new ZipEntry(entryName + (isDirectory ? "/" : "")); + try + { + zipOutputStream.putNextEntry(zipEntry); + if (!isDirectory) + { + Files.copy(path, zipOutputStream); + } + zipOutputStream.closeEntry(); + } catch (final IOException e) + { + throw new RuntimeException(e); + } + }); + } + } + } + + public static void deleteDirectory(final String directoryPath) throws IOException { + final Path path = Paths.get(directoryPath); + if (Files.exists(path)) + { + try (final Stream<Path> walkStream = Files.walk(path)) + { + walkStream + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + } + + private static class EntitiesVo + { + + private final String sessionToken; + + private final Map<ExportableKind, List<String>> groupedExportablePermIds; + + private Collection<Space> spaces; + + private Collection<Project> projects; + + private Collection<Experiment> experiments; + + private Collection<Sample> samples; + + private Collection<DataSet> dataSets; + + private EntitiesVo(final String sessionToken, final List<ExportablePermId> exportablePermIds) + { + this.sessionToken = sessionToken; + groupedExportablePermIds = getGroupedExportablePermIds(exportablePermIds); + } + + private static Map<ExportableKind, List<String>> getGroupedExportablePermIds(final List<ExportablePermId> exportablePermIds) + { + final Collector<ExportablePermId, List<String>, List<String>> downstreamCollector = Collector.of(ArrayList::new, + (stringPermIds, exportablePermId) -> stringPermIds.add(exportablePermId.getPermId().getPermId()), + (left, right) -> + { + left.addAll(right); + return left; + }); + + return exportablePermIds.stream().collect(Collectors.groupingBy(ExportablePermId::getExportableKind, downstreamCollector)); + } + + public Collection<Space> getSpaces() + { + if (spaces == null) + { + spaces = EntitiesFinder.getSpaces(sessionToken, groupedExportablePermIds.getOrDefault(ExportableKind.SPACE, List.of())); + } + return spaces; + } + + public Collection<Project> getProjects() + { + if (projects == null) + { + projects = EntitiesFinder.getProjects(sessionToken, groupedExportablePermIds.getOrDefault(ExportableKind.PROJECT, List.of())); + } + + return projects; + } + + public Collection<Experiment> getExperiments() + { + if (experiments == null) + { + experiments = EntitiesFinder.getExperiments(sessionToken, groupedExportablePermIds.getOrDefault(ExportableKind.EXPERIMENT, List.of())); + } + return experiments; + } + + public Collection<Sample> getSamples() + { + if (samples == null) + { + samples = EntitiesFinder.getSamples(sessionToken, groupedExportablePermIds.getOrDefault(ExportableKind.SAMPLE, List.of())); + } + return samples; + } + + public Collection<DataSet> getDataSets() + { + if (dataSets == null) + { + dataSets = EntitiesFinder.getDataSets(sessionToken, groupedExportablePermIds.getOrDefault(DATASET, List.of())); + } + return dataSets; + } + + } + +} diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/ExportOperationExecutor.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/ExportOperationExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..0ba9b6fc68aedb03fdbc668eeb2017e4da0e4c64 --- /dev/null +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/ExportOperationExecutor.java @@ -0,0 +1,50 @@ +/* + * 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.server.asapi.v3.executor.exporter; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.ExportOperation; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.ExportOperationResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.ExportResult; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.common.OperationExecutor; +import ch.ethz.sis.openbis.generic.server.xls.export.XLSExport; + +@Component +public class ExportOperationExecutor extends OperationExecutor<ExportOperation, ExportOperationResult> +{ + + @Autowired + private IExportExecutor executor; + + @Override + protected Class<? extends ExportOperation> getOperationClass() + { + return ExportOperation.class; + } + + @Override + protected ExportOperationResult doExecute(final IOperationContext context, final ExportOperation operation) + { + final XLSExport.ExportResult exportResult = executor.doExport(context, operation); + return new ExportOperationResult(new ExportResult(exportResult.getFileName(), exportResult.getWarnings())); + } + +} diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/IExportExecutor.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/IExportExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..b3088cbad138e8a34334e52a82915bf7e18f4969 --- /dev/null +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/IExportExecutor.java @@ -0,0 +1,29 @@ +/* + * 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.server.asapi.v3.executor.exporter; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.ExportOperation; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.IOperationContext; +import ch.ethz.sis.openbis.generic.server.xls.export.XLSExport.ExportResult; + +public interface IExportExecutor +{ + + ExportResult doExport(final IOperationContext context, final ExportOperation operation); + +} diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/IExportFieldsFinder.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/IExportFieldsFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..746c4521150731ee7312179008e1b1ee7a89ee53 --- /dev/null +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/IExportFieldsFinder.java @@ -0,0 +1,38 @@ +/* + * 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.server.asapi.v3.executor.exporter; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.exporter.data.SelectedFields; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.id.IPropertyTypeId; +import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi; + +public interface IExportFieldsFinder +{ + + String TYPE = "type"; + + String ID = "id"; + + Map<String, List<Map<String, String>>> findExportFields(Set<IPropertyTypeId> properties, + IApplicationServerInternalApi applicationServerApi, String sessionToken, SelectedFields selectedFields); + +} diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/SampleExportFieldsFinder.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/SampleExportFieldsFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..59ceb769f16d7587f960008d62774a22d385ad23 --- /dev/null +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/exporter/SampleExportFieldsFinder.java @@ -0,0 +1,51 @@ +/* + * 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.server.asapi.v3.executor.exporter; + +import java.util.Set; + +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.id.IPropertyTypeId; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.SampleType; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.fetchoptions.SampleTypeFetchOptions; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.search.SampleTypeSearchCriteria; +import ch.ethz.sis.openbis.generic.server.asapi.v3.IApplicationServerInternalApi; + +public class SampleExportFieldsFinder extends AbstractExportFieldsFinder<SampleType> +{ + + @Override + public SearchResult<SampleType> findEntityTypes(final Set<IPropertyTypeId> properties, + final IApplicationServerInternalApi applicationServerApi, final String sessionToken) + { + final SampleTypeSearchCriteria typeSearchCriteria = new SampleTypeSearchCriteria(); + typeSearchCriteria.withPropertyAssignments().withPropertyType().withIds().thatIn(properties); + + final SampleTypeFetchOptions fetchOptions = new SampleTypeFetchOptions(); + fetchOptions.withPropertyAssignments().withPropertyType(); + + return applicationServerApi.searchSampleTypes(sessionToken, typeSearchCriteria, fetchOptions); + } + + @Override + public String getPermId(final SampleType sampleType) + { + return sampleType.getPermId().getPermId(); + } + +} diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/OperationsExecutor.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/OperationsExecutor.java index 999b67987740760aca81c786b719edf9b3c75d00..397082f394c52143ff803e1dece0da93971b47b5 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/OperationsExecutor.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/executor/operation/OperationsExecutor.java @@ -70,6 +70,7 @@ import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.experiment.ISearchEx import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.experiment.IUpdateExperimentTypesOperationExecutor; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.experiment.IUpdateExperimentsOperationExecutor; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.experiment.IVerifyExperimentsOperationExecutor; +import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.exporter.ExportOperationExecutor; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.externaldms.ICreateExternalDmsOperationExecutor; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.externaldms.IDeleteExternalDmsOperationExecutor; import ch.ethz.sis.openbis.generic.server.asapi.v3.executor.externaldms.IGetExternalDmsOperationExecutor; @@ -655,6 +656,9 @@ public class OperationsExecutor implements IOperationsExecutor @Autowired private ImportOperationExecutor importOperationExecutor; + @Autowired + private ExportOperationExecutor exportOperationExecutor; + @Override public List<IOperationResult> execute(IOperationContext context, List<? extends IOperation> operations, IOperationExecutionOptions options) { @@ -686,6 +690,7 @@ public class OperationsExecutor implements IOperationsExecutor executeUpdates(operations, resultMap, context); resultMap.putAll(internalOperationExecutor.execute(context, operations)); resultMap.putAll(importOperationExecutor.execute(context, operations)); + resultMap.putAll(exportOperationExecutor.execute(context, operations)); flushCurrentSession(); clearCurrentSession(); diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/search/translator/condition/CollectionFieldSearchConditionTranslator.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/search/translator/condition/CollectionFieldSearchConditionTranslator.java index 0020b115aea999764416d2247e0cd205ac753027..369e4a2d4733be8f540edf420f4e5580996f5523 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/search/translator/condition/CollectionFieldSearchConditionTranslator.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/search/translator/condition/CollectionFieldSearchConditionTranslator.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.id.ObjectPermId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.CodesSearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.CollectionFieldSearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.IdsSearchCriteria; @@ -36,7 +37,7 @@ import static ch.ethz.sis.openbis.generic.server.asapi.v3.search.translator.SQLL public class CollectionFieldSearchConditionTranslator implements IConditionTranslator<CollectionFieldSearchCriteria<?>> { - private static final Map<Class, Object[]> ARRAY_CASTING = new HashMap<>(); + private static final Map<Class<?>, Object[]> ARRAY_CASTING = new HashMap<>(); static { @@ -87,10 +88,10 @@ public class CollectionFieldSearchConditionTranslator implements IConditionTrans { final Collection<?> fieldValue; if (!initialFieldValue.isEmpty() && initialFieldValue.stream().anyMatch( - (o) -> o instanceof EntityTypePermId)) + (o) -> o instanceof ObjectPermId)) { fieldValue = initialFieldValue.stream().map( - (o) -> ((EntityTypePermId) o).getPermId()).collect(Collectors.toList()); + (o) -> ((ObjectPermId) o).getPermId()).collect(Collectors.toList()); } else { fieldValue = initialFieldValue; diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/export/XLSExport.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/export/XLSExport.java index 5800293f841ad25ece6db81be44c66c69d01b12b..b5a6e47810f600a16b267da880e35c0a22beddc8 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/export/XLSExport.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/export/XLSExport.java @@ -24,6 +24,7 @@ import static ch.ethz.sis.openbis.generic.server.xls.export.ExportableKind.SPACE import static ch.ethz.sis.openbis.generic.server.xls.export.ExportableKind.VOCABULARY_TYPE; import java.io.BufferedOutputStream; +import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; @@ -42,7 +43,6 @@ import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.apache.commons.io.IOUtils; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; @@ -64,11 +64,31 @@ import ch.systemsx.cisd.openbis.generic.shared.ISessionWorkspaceProvider; public class XLSExport { - private static final String XLSX_EXTENSION = ".xlsx"; + public static final String XLSX_EXTENSION = ".xlsx"; - private static final String ZIP_EXTENSION = ".zip"; + public static final String ZIP_EXTENSION = ".zip"; - private static final String TYPE_KEY = "TYPE"; + public static final String SCRIPTS_DIRECTORY = "scripts"; + + private static final String TYPE_EXPORT_FIELD_KEY = "TYPE"; + + private XLSExport() + { + throw new UnsupportedOperationException("Instantiation of a utility class."); + } + + private static File createDirectory(final File parentDirectory, final String directoryName) throws IOException + { + final File scriptsDirectory = new File(parentDirectory, directoryName); + final boolean directoryCreated = scriptsDirectory.mkdir(); + + if (!directoryCreated) + { + throw new IOException(String.format("Failed create directory %s.", scriptsDirectory.getAbsolutePath())); + } + + return scriptsDirectory; + } public static ExportResult export(final String filePrefix, final IApplicationServerApi api, final String sessionToken, final List<ExportablePermId> exportablePermIds, @@ -84,9 +104,10 @@ public class XLSExport final String fullFileName = filePrefix + "." + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS").format(new Date()) + (scripts.isEmpty() ? XLSX_EXTENSION : ZIP_EXTENSION); - final FileOutputStream os = sessionWorkspaceProvider.getFileOutputStream(sessionToken, fullFileName); - writeToOutputStream(os, filePrefix, exportResult, scripts); - IOUtils.closeQuietly(os); + try (final FileOutputStream os = sessionWorkspaceProvider.getFileOutputStream(sessionToken, fullFileName)) + { + writeToOutputStream(os, filePrefix, exportResult, scripts); + } return new ExportResult(fullFileName, exportResult.getWarnings()); } @@ -114,7 +135,7 @@ public class XLSExport { for (final Map.Entry<String, String> script : scripts.entrySet()) { - zos.putNextEntry(new ZipEntry(String.format("scripts/%s.py", script.getKey()))); + zos.putNextEntry(new ZipEntry(String.format("%s/%s.py", SCRIPTS_DIRECTORY, script.getKey()))); bos.write(script.getValue().getBytes()); bos.flush(); zos.closeEntry(); @@ -126,7 +147,7 @@ public class XLSExport } } - static PrepareWorkbookResult prepareWorkbook(final IApplicationServerApi api, final String sessionToken, + public static PrepareWorkbookResult prepareWorkbook(final IApplicationServerApi api, final String sessionToken, List<ExportablePermId> exportablePermIds, final boolean exportReferredMasterData, final Map<String, Map<String, List<Map<String, String>>>> exportFields, final TextFormatting textFormatting, final boolean compatibleWithImport) @@ -161,10 +182,7 @@ public class XLSExport final List<String> permIds = exportablePermIdGroup.stream() .map(permId -> permId.getPermId().getPermId()).collect(Collectors.toList()); - final Map<String, List<Map<String, String>>> entityTypeExportFieldsMap = exportFields == null - ? null - : exportFields.get(MASTER_DATA_EXPORTABLE_KINDS.contains(exportableKind) || exportableKind == SPACE || exportableKind == PROJECT - ? TYPE_KEY : exportableKind.toString()); + final Map<String, List<Map<String, String>>> entityTypeExportFieldsMap = getEntityTypeExportFieldsMap(exportFields, exportableKind); final IXLSExportHelper.AdditionResult additionResult = helper.add(api, sessionToken, wb, permIds, rowNumber, entityTypeExportFieldsMap, textFormatting, compatibleWithImport); rowNumber = additionResult.getRowNumber(); @@ -194,6 +212,15 @@ public class XLSExport return new PrepareWorkbookResult(wb, scripts, warnings); } + private static Map<String, List<Map<String, String>>> getEntityTypeExportFieldsMap( + final Map<String, Map<String, List<Map<String, String>>>> exportFields, final ExportableKind exportableKind) + { + return exportFields == null + ? null + : exportFields.get(MASTER_DATA_EXPORTABLE_KINDS.contains(exportableKind) || exportableKind == SPACE || exportableKind == PROJECT + ? TYPE_EXPORT_FIELD_KEY : exportableKind.toString()); + } + private static List<ExportablePermId> expandReference(final IApplicationServerApi api, final String sessionToken, final List<ExportablePermId> exportablePermIds, final ExportHelperFactory exportHelperFactory) @@ -210,7 +237,7 @@ public class XLSExport final String sessionToken, final ExportablePermId exportablePermId, final Set<ExportablePermId> processedIds, final ExportHelperFactory exportHelperFactory) { - final IXLSExportHelper helper = exportHelperFactory.getHelper(exportablePermId.getExportableKind()); + final IXLSExportHelper<? extends IEntityType> helper = exportHelperFactory.getHelper(exportablePermId.getExportableKind()); if (helper != null) { final IPropertyAssignmentsHolder propertyAssignmentsHolder = helper diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/export/helper/XLSDataSetExportHelper.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/export/helper/XLSDataSetExportHelper.java index bf7ba57ec82ce9dff7bfa1e9837b3fbcdab98e2e..951afdeaaff348f51e785976746017fea4859c93 100644 --- a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/export/helper/XLSDataSetExportHelper.java +++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/xls/export/helper/XLSDataSetExportHelper.java @@ -153,7 +153,7 @@ public class XLSDataSetExportHelper extends AbstractXLSEntityExportHelper<DataSe case SIZE: { final PhysicalData physicalData = dataSet.getPhysicalData(); - return physicalData != null ? physicalData.getSize().toString() : null; + return physicalData != null && physicalData.getSize() != null ? physicalData.getSize().toString() : null; } case IDENTIFIER: case CODE: diff --git a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServiceProvider.java b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServiceProvider.java index 95ec68f22ddd29091379c8fb9ade5ea05dc73421..4dbede8a54b57d1100ceb7168c5568883b593c1c 100644 --- a/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServiceProvider.java +++ b/server-application-server/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServiceProvider.java @@ -55,7 +55,14 @@ public class CommonServiceProvider public static void setDataStoreServerApi(final String dssURL, final int timeoutInMinutes) { - dataStoreServerApi = HttpInvokerUtils.createServiceStub(IDataStoreServerApi.class, dssURL + IDataStoreServerApi.SERVICE_URL, timeoutInMinutes * 60 * 1000); + dataStoreServerApi = HttpInvokerUtils.createServiceStub(IDataStoreServerApi.class, dssURL + + "/datastore_server" + IDataStoreServerApi.SERVICE_URL, + timeoutInMinutes * 60 * 1000); + } + + public static void setDataStoreServerApi(final IDataStoreServerApi dataStoreServerApi) + { + CommonServiceProvider.dataStoreServerApi = dataStoreServerApi; } public static ICommonServerForInternalUse getCommonServer() diff --git a/ui-admin/src/core-plugins/admin/1/as/services/xls-export/xls-export.py b/ui-admin/src/core-plugins/admin/1/as/services/xls-export/xls-export.py index a49b4c060df61a3c9da8913f19ad6b1681dd555c..f9689079d48e18d0e8266a9f8f2c40affe0e7bb1 100644 --- a/ui-admin/src/core-plugins/admin/1/as/services/xls-export/xls-export.py +++ b/ui-admin/src/core-plugins/admin/1/as/services/xls-export/xls-export.py @@ -34,12 +34,12 @@ def export(context, parameters): "<SAMPLE_TYPE | EXPERIMENT_TYPE | DATASET_TYPE | VOCABULARY_TYPE | SPACE | PROJECT>": [ {"type": "ATTRIBUTE", "id": "<attribute name>"}, ... - ] - attribute for each type and entity without types to be exported, + ] - attributes for each type and entity without types to be exported, if the list is empty no attributes will be exported for the given one ... }, "SAMPLE": { - "<typePermID>": [ + "<sampleTypePermID>": [ {"type": "PROPERTY", "id": "<property code>"}, {"type": "ATTRIBUTE", "id": "<attribute name>"}, ... @@ -48,7 +48,7 @@ def export(context, parameters): for the sample type }, "EXPERIMENT": { - "<typePermID>": [ + "<experimentTypePermID>": [ {"type": "PROPERTY", "id": "<property code>"}, {"type": "ATTRIBUTE", "id": "<attribute name>"}, ... @@ -57,7 +57,7 @@ def export(context, parameters): for the experiment type }, "DATASET": { - "<typePermID>": [ + "<dataSetTypePermID>": [ {"type": "PROPERTY", "id": "<property code>"}, {"type": "ATTRIBUTE", "id": "<attribute name>"} , ...