diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls index 9f7724cae233674fc63335ad063636d9b015b00e..8d7784125bad3226f67a0cf35266de503b5a48cb 100644 Binary files a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls and b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls differ diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/plugin.properties b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/plugin.properties new file mode 100644 index 0000000000000000000000000000000000000000..76d5aa52bab6f89a78a2e2bd11542e728466b26d --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/plugin.properties @@ -0,0 +1,2 @@ +class = ch.ethz.sis.openbis.generic.server.asapi.v3.helper.service.JythonBasedCustomASServiceExecutor +script-path = publication-api.py \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/publication-api.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/publication-api.py new file mode 100644 index 0000000000000000000000000000000000000000..cc5c7e99fe2eb0518d370c82b61ccc04bee4cc2c --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/publication-api.py @@ -0,0 +1,93 @@ +import traceback + +from ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset import DataSetKind +from ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.create import DataSetCreation +from ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions import DataSetFetchOptions +from ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.id import DataSetPermId +from ch.ethz.sis.openbis.generic.asapi.v3.dto.datastore.id import DataStorePermId +from ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype import EntityKind +from ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id import EntityTypePermId +from ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id import ExperimentIdentifier +from ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.create import SampleCreation +from ch.ethz.sis.openbis.generic.asapi.v3.dto.space.id import SpacePermId +from ch.systemsx.cisd.common.logging import LogCategory +from ch.systemsx.cisd.openbis.generic.client.web.client.exception import UserFailureException +from java.util import ArrayList +from org.apache.log4j import Logger + +operationLog = Logger.getLogger(str(LogCategory.OPERATION) + '.publication-api.py') + + +def process(context, parameters): + method = parameters.get('method') + + try: + if method == 'insertPublication': + result = insertPublication(context, parameters) + else: + raise UserFailureException('Unknown method: "%s"' % str(method if method is not None else 'None')) + except Exception as e: + operationLog.error('Exception at: ' + traceback.format_exc()) + operationLog.error('Exception: ' + str(e)) + result = { + 'status': 'FAILED', + 'error': str(e) + } + return result + + +def insertPublication(context, parameters): + transaction = context.applicationService + sessionToken = transaction.loginAsSystem() + + v3 = context.applicationService + + name = parameters.get('name') + if name is None: + raise UserFailureException('name parameter missing') + + sampleId = createPublicationSample(parameters, sessionToken, v3).get(0) + createDataSet(parameters, sessionToken, v3, sampleId) + + return { + 'status': 'OK', + } + + +def createDataSet(parameters, sessionToken, v3, sampleId): + openBISRelatedIdentifiers = parameters.get('openBISRelatedIdentifiers').split(',') + identifiers = ArrayList(len(openBISRelatedIdentifiers)) + for identifier in openBISRelatedIdentifiers: + identifiers.add(DataSetPermId(identifier)) + + dataSetIds = v3.getDataSets(sessionToken, identifiers, DataSetFetchOptions()).keys() + operationLog.debug('Found %d data sets.' % len(dataSetIds)) + + dataSetCreation = DataSetCreation() + dataSetCreation.setAutoGeneratedCode(True) + dataSetCreation.setTypeId(EntityTypePermId('PUBLICATION_DATA', EntityKind.DATA_SET)) + dataSetCreation.setSampleId(sampleId) + dataSetCreation.setDataSetKind(DataSetKind.CONTAINER) + dataSetCreation.setComponentIds(dataSetIds) + dataSetCreation.setDataStoreId(DataStorePermId('STANDARD')) + v3.createDataSets(sessionToken, [dataSetCreation]) + + +def createPublicationSample(parameters, sessionToken, v3): + publicationOrganization = parameters.get('publicationOrganization') + publicationType = parameters.get('publicationType') # The only valid value for now is "Public Repository" + publicationDescription = parameters.get('publicationDescription') # Can be empty + publicationURL = parameters.get('publicationURL') + publicationIdentifier = parameters.get('publicationIdentifier') + + sampleCreation = SampleCreation() + sampleCreation.setTypeId(EntityTypePermId('PUBLICATION')) + sampleCreation.setExperimentId(ExperimentIdentifier('/PUBLICATIONS/PUBLIC_REPOSITORIES/PUBLICATIONS_COLLECTION')) + sampleCreation.setSpaceId(SpacePermId('PUBLICATIONS')) + sampleCreation.setProperty('$NAME', 'TEST NAME') + sampleCreation.setProperty('$PUBLICATION.ORGANIZATION', publicationOrganization) + sampleCreation.setProperty('$PUBLICATION.TYPE', publicationType) + sampleCreation.setProperty('$PUBLICATION.IDENTIFIER', publicationIdentifier) + sampleCreation.setProperty('$PUBLICATION.URL', publicationURL) + sampleCreation.setProperty('$PUBLICATION.DESCRIPTION', publicationDescription) + return v3.createSamples(sessionToken, [sampleCreation]) diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/img/research-collection-icon.png b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/img/research-collection-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..67c8597c90c2d1b3274d4c9639ae83b25db711ce Binary files /dev/null and b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/img/research-collection-icon.png differ diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html index 65763885d61646b93a15fae61886af4146c58921..65c0c67df06fa966f423605b46a31aa994ca4165 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html @@ -249,6 +249,10 @@ <script type="text/javascript" src="./js/views/Export/ExportTreeModel.js"></script> <script type="text/javascript" src="./js/views/Export/ExportTreeView.js"></script> + <script type="text/javascript" src="./js/views/ResearchCollectionExport/ResearchCollectionExportController.js"></script> + <script type="text/javascript" src="./js/views/ResearchCollectionExport/ResearchCollectionExportModel.js"></script> + <script type="text/javascript" src="./js/views/ResearchCollectionExport/ResearchCollectionExportView.js"></script> + <script type="text/javascript" src="./js/views/DrawingBoards/DrawingBoardsController.js"></script> <script type="text/javascript" src="./js/views/DrawingBoards/DrawingBoardsModel.js"></script> <script type="text/javascript" src="./js/views/DrawingBoards/DrawingBoardsView.js"></script> diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js index 09690f4f1d524512a4448e616812151b1f5894ee..142198f6bab65d4da0dce1cf78171022e97001b4 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js @@ -376,12 +376,19 @@ function MainController(profile) { break; case "showExportTreePage": document.title = "Export Builder"; - var newView = new ExportTreeController(this); - var views = this._getNewViewModel(true, true, false); - newView.init(views); - this.currentView = newView; + var newExportView = new ExportTreeController(this); + var exportViews = this._getNewViewModel(true, true, false); + newExportView.init(exportViews); + this.currentView = newExportView; //window.scrollTo(0,0); break; + case "showResearchCollectionExportPage": + document.title = "Research Collection Export Builder"; + var newResearchCollectionExportView = new ResearchCollectionExportController(this); + var researchCollectionExportViews = this._getNewViewModel(true, true, false); + newResearchCollectionExportView.init(researchCollectionExportViews); + this.currentView = newResearchCollectionExportView; + break; case "showLabNotebookPage": document.title = "Lab Notebook"; var newView = new LabNotebookController(this); diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js index 5f6bde3add7d0ba6131ff0b4de743f8ada69e26c..b9f816840a1cef0a7b3966db54cb871d96a7c496 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js @@ -302,7 +302,65 @@ function ServerFacade(openbisServer) { "entities" : entities, "metadataOnly" : metadataOnly, }, callbackFunction, "exports-api"); - } + }; + + // + // Research collection export + // + this.exportRc = function(entities, includeRoot, metadataOnly, submissionUrl, submissionType, userInformation, callbackFunction) { + this.asyncExportRc({ + "method": "exportAll", + "includeRoot": includeRoot, + "entities": entities, + "metadataOnly": metadataOnly, + "submissionUrl": submissionUrl, + "submissionType": submissionType, + "userInformation": userInformation, + "originUrl": window.location.origin, + "sessionToken": this.openbisServer.getSession(), + }, callbackFunction, "rc-exports-api"); + }; + + this.asyncExportRc = function(parameters, callbackFunction, serviceId) { + require(["as/dto/service/execute/ExecuteAggregationServiceOperation", + "as/dto/operation/AsynchronousOperationExecutionOptions", "as/dto/service/id/DssServicePermId", + "as/dto/datastore/id/DataStorePermId", "as/dto/service/execute/AggregationServiceExecutionOptions"], + function(ExecuteAggregationServiceOperation, AsynchronousOperationExecutionOptions, DssServicePermId, DataStorePermId, + AggregationServiceExecutionOptions) { + var dataStoreId = new DataStorePermId("STANDARD"); + var dssServicePermId = new DssServicePermId(serviceId, dataStoreId); + var options = new AggregationServiceExecutionOptions(); + + options.withParameter("sessionToken", parameters["sessionToken"]); + + options.withParameter("entities", parameters["entities"]); + options.withParameter("includeRoot", parameters["includeRoot"]); + options.withParameter("metadataOnly", parameters["metadataOnly"]); + options.withParameter("method", parameters["method"]); + options.withParameter("originUrl", parameters["originUrl"]); + options.withParameter("submissionType", parameters["submissionType"]); + options.withParameter("submissionUrl", parameters["submissionUrl"]); + options.withParameter("entities", parameters["entities"]); + options.withParameter("userId", parameters["userInformation"]["id"]); + options.withParameter("userEmail", parameters["userInformation"]["email"]); + options.withParameter("userFirstName", parameters["userInformation"]["firstName"]); + options.withParameter("userLastName", parameters["userInformation"]["lastName"]); + + var operation = new ExecuteAggregationServiceOperation(dssServicePermId, options); + mainController.openbisV3.executeOperations([operation], new AsynchronousOperationExecutionOptions()).done(function(results) { + callbackFunction(results.executionId.permId); + }); + }); + }; + + // + // Gets submission types + // + this.listSubmissionTypes = function(callbackFunction) { + this.customELNApi({ + "method": "getSubmissionTypes", + }, callbackFunction, "rc-exports-api"); + }; // // Metadata Related Functions @@ -659,7 +717,6 @@ function ServerFacade(openbisServer) { this._callPasswordResetService = function(parameters, callbackFunction) { var _this = this; this.getOpenbisV3(function(openbisV3) { - openbisV3.loginAsAnonymousUser().done(function(sessionToken) { _this.openbisServer._internal.sessionToken = sessionToken; @@ -682,6 +739,7 @@ function ServerFacade(openbisServer) { } this.customELNApi = function(parameters, callbackFunction, service) { + var _this = this; if(!service) { service = "eln-lims-api"; } @@ -693,24 +751,28 @@ function ServerFacade(openbisServer) { var dataStoreCode = profile.getDefaultDataStoreCode(); this.openbisServer.createReportFromAggregationService(dataStoreCode, service, parameters, function(data) { - var error = null; - var result = {}; - if(data.error) { //Error Case 1 - error = data.error.message; - } else if (data.result.columns[1].title === "Error") { //Error Case 2 - error = data.result.rows[0][1].value; - } else if (data.result.columns[0].title === "STATUS" && data.result.rows[0][0].value === "OK") { //Success Case - result.message = data.result.rows[0][1].value; - result.data = data.result.rows[0][2].value; - if(result.data) { - result.data = JSON.parse(result.data); - } - } else { - error = "Unknown Error."; - } - callbackFunction(error, result); + _this.customELNApiCallbackHandler(data, callbackFunction); }); - } + }; + + this.customELNApiCallbackHandler = function(data, callbackFunction) { + var error = null; + var result = {}; + if (data && data.error) { //Error Case 1 + error = data.error.message; + } else if (data && data.result.columns[1].title === "Error") { //Error Case 2 + error = data.result.rows[0][1].value; + } else if (data && data.result.columns[0].title === "STATUS" && data.result.rows[0][0].value === "OK") { //Success Case + result.message = data.result.rows[0][1].value; + result.data = data.result.rows[0][2].value; + if(result.data) { + result.data = JSON.parse(result.data); + } + } else { + error = "Unknown Error."; + } + callbackFunction(error, result); + }; this.customELNASAPI = function(parameters, callbackFunction) { this.customASService(parameters, callbackFunction, "as-eln-lims-api"); @@ -2563,10 +2625,10 @@ function ServerFacade(openbisServer) { this.getSessionInformation = function(callbackFunction) { mainController.openbisV3.getSessionInformation().done(function(sessionInfo) { -Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â callbackFunction(sessionInfo); + callbackFunction(sessionInfo); Â Â Â Â Â Â Â Â }).fail(function(result) { - Util.showFailedServerCallError(result); - callbackFunction(false); + Util.showFailedServerCallError(result); + callbackFunction(false); }); } diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js index e501e670936df304c7e03f164f910f7fbb453845..9658d70d42f076133974dd2075ce5654b55951de 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js @@ -322,25 +322,28 @@ var FormUtil = new function() { Select2Manager.add($dropdown); return $dropdown; } - - this.getPlainDropdown = function(mapVals, placeHolder) { - var $component = $("<select>", {class : 'form-control'}); - if(placeHolder) { - $component.append($("<option>").attr('value', '').attr('selected', '').attr('disabled', '').text(placeHolder)); - } - for(var mIdx = 0; mIdx < mapVals.length; mIdx++) { + this.setValuesToComponent = function ($component, mapVals) { + for (var mIdx = 0; mIdx < mapVals.length; mIdx++) { var $option = $("<option>").attr('value', mapVals[mIdx].value).text(mapVals[mIdx].label); - if(mapVals[mIdx].disabled) { + if (mapVals[mIdx].disabled) { $option.attr('disabled', ''); } - if(mapVals[mIdx].selected) { + if (mapVals[mIdx].selected) { $option.attr('selected', ''); } $component.append($option); } + }; + + this.getPlainDropdown = function(mapVals, placeHolder) { + var $component = $("<select>", {class : 'form-control'}); + if (placeHolder) { + $component.append($("<option>").attr('value', '').attr('selected', '').attr('disabled', '').text(placeHolder)); + } + this.setValuesToComponent($component, mapVals); return $component; - } + }; this.getDataSetsDropDown = function(code, dataSetTypes) { var $component = $("<select>", { class : 'form-control ' }); @@ -350,11 +353,11 @@ var FormUtil = new function() { $component.append($("<option>").attr('value', '').attr('selected', '').attr('disabled', '').text('Select a dataset type')); - for(var i = 0; i < dataSetTypes.length; i++) { + for (var i = 0; i < dataSetTypes.length; i++) { var datasetType = dataSetTypes[i]; var label = Util.getDisplayNameFromCode(datasetType.code); var description = Util.getEmptyIfNull(datasetType.description); - if(description !== "") { + if (description !== "") { label += " (" + description + ")"; } diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/Export/ExportTreeView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/Export/ExportTreeView.js index f09bd1a1cf9355bcbac788de1f49c8f923ad59f8..7bbbd3de8e2eb73f3e5c18fc7f186fdeb7dd50fb 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/Export/ExportTreeView.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/Export/ExportTreeView.js @@ -39,7 +39,7 @@ function ExportTreeView(exportTreeController, exportTreeModel) { 'onClick': '$("form[name=\'exportTreeForm\']").submit()' }); $header.append($exportButton); - var $infoBox = FormUtil.getInfoBox("You can select any parts of the accesible openBIS structure to export:", [ + var $infoBox = FormUtil.getInfoBox("You can select any parts of the accessible openBIS structure to export:", [ "If you select a tree node and do not expand it, everything below this node will be exported by default.", "To export selectively only parts of a tree, open the nodes and select what to export." ]); diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js new file mode 100644 index 0000000000000000000000000000000000000000..514b6a15d2dcc03336b6c499ac7ad13528fbe454 --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js @@ -0,0 +1,127 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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. + */ + +function ResearchCollectionExportController(parentController) { + var researchCollectionExportModel = new ResearchCollectionExportModel(); + var researchCollectionExportView = new ResearchCollectionExportView(this, researchCollectionExportModel); + + this.init = function(views) { + researchCollectionExportView.repaint(views); + }; + + this.initialiseSubmissionTypesDropdown = function(callback) { + Util.blockUI(); + mainController.serverFacade.listSubmissionTypes(function(error, result) { + Util.unblockUI(); + if (error) { + Util.showError(error); + } else { + researchCollectionExportModel.submissionTypes = result.data.map(function (resultItem) { + return { + value: resultItem.url, + label: resultItem.title + }; + }); + researchCollectionExportView.refreshSubmissionTypeDropdown(); + } + }); + }; + + this.exportSelected = function() { + var _this = this; + var selectedNodes = $(researchCollectionExportModel.tree).fancytree('getTree').getSelectedNodes(); + + var selectedOption = researchCollectionExportView.$submissionTypeDropdown.find(":selected"); + var submissionUrl = selectedOption.val(); + var submissionType = selectedOption.text(); + + var toExport = []; + for (var eIdx = 0; eIdx < selectedNodes.length; eIdx++) { + var node = selectedNodes[eIdx]; + toExport.push({type: node.data.entityType, permId: node.key, expand: !node.expanded}); + } + + if (toExport.length === 0) { + Util.showInfo('First select something to export.'); + } else if (!submissionUrl) { + Util.showInfo('First select submission type.'); + } else { + Util.blockUI(); + this.getUserInformation(function(userInformation) { + mainController.serverFacade.exportRc(toExport, true, false, submissionUrl, submissionType, userInformation, + function(operationExecutionPermId) { + _this.waitForOpExecutionResponse(operationExecutionPermId, function(error, result) { + Util.unblockUI(); + if (result && result.data && result.data.url) { + var win = window.open(result.data.url, '_blank'); + win.focus(); + mainController.refreshView(); + } else { + if (error) { + Util.showError(error); + } else { + Util.showError('Returned result format is not correct.'); + } + } + }); + }); + }); + } + }; + + this.waitForOpExecutionResponse = function(operationExecutionPermIdString, callbackFunction) { + var _this = this; + require(["as/dto/operation/id/OperationExecutionPermId", + "as/dto/operation/fetchoptions/OperationExecutionFetchOptions"], + function(OperationExecutionPermId, OperationExecutionFetchOptions) { + var operationExecutionPermId = new OperationExecutionPermId(operationExecutionPermIdString); + var fetchOptions = new OperationExecutionFetchOptions(); + var fetchOptionsDetails = fetchOptions.withDetails(); + fetchOptionsDetails.withResults(); + fetchOptionsDetails.withError(); + mainController.openbisV3.getOperationExecutions([operationExecutionPermId], fetchOptions).done(function(results) { + var result = results[operationExecutionPermIdString]; + var v2Result = null; + if (result && result.details && result.details.results) { + v2Result = result.details.results[0]; + } + + if (result && result.state === 'FINISHED') { + mainController.serverFacade.customELNApiCallbackHandler(v2Result, callbackFunction); + } else if (!result || result.state === 'FAILED') { + mainController.serverFacade.customELNApiCallbackHandler(v2Result, callbackFunction); + } else { + setTimeout(function() { + _this.waitForOpExecutionResponse(operationExecutionPermIdString, callbackFunction); + }, 3000); + } + }); + }); + }; + + this.getUserInformation = function(callback) { + var userId = mainController.serverFacade.getUserId(); + mainController.serverFacade.getSessionInformation(function(sessionInfo) { + var userInformation = { + firstName: sessionInfo.person.firstName, + lastName: sessionInfo.person.lastName, + email: sessionInfo.person.email, + id: userId, + }; + callback(userInformation); + }); + }; +} \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js new file mode 100644 index 0000000000000000000000000000000000000000..7099e83d87552bbebcdff0b2788f00a34d18d6e1 --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js @@ -0,0 +1,23 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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. + */ + +function ResearchCollectionExportModel() { + this.submissionTypes = []; + + this.addSubmissionType = function(submissionType) { + submissionTypes.push(submissionType); + }; +} \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportView.js new file mode 100644 index 0000000000000000000000000000000000000000..7945f200cb3b4bdf5591f784dbeff1a6335ce1f0 --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportView.js @@ -0,0 +1,75 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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. + */ + +function ResearchCollectionExportView(researchCollectionExportController, researchCollectionExportModel) { + var exportTreeView = new ExportTreeView(researchCollectionExportController, researchCollectionExportModel); + + this.repaint = function(views) { + researchCollectionExportController.initialiseSubmissionTypesDropdown(); + + var $header = views.header; + var $container = views.content; + + var $form = $("<div>"); + var $formColumn = $("<form>", { + 'name': 'rcExportForm', + 'role': 'form', + 'action': 'javascript:void(0);', + 'onsubmit': 'mainController.currentView.exportSelected();' + }); + $form.append($formColumn); + + var $infoBox = FormUtil.getInfoBox('You can select any parts of the accessible openBIS structure to export:', [ + 'If you select a tree node and do not expand it, everything below this node will be exported by default.', + 'To export selectively only parts of a tree, open the nodes and select what to export.' + ]); + $infoBox.css('border', 'none'); + $container.append($infoBox); + + var $tree = $('<div>', { 'id' : 'exportsTree' }); + $formColumn.append($('<br>')); + $formColumn.append(FormUtil.getBox().append($tree)); + + $container.append($form); + + this.paintSubmissionTypeDropdown($container); + + researchCollectionExportModel.tree = TreeUtil.getCompleteTree($tree); + + var $formTitle = $('<h2>').append('Research Collection Export Builder'); + $header.append($formTitle); + + var $exportButton = $('<input>', { 'type': 'submit', 'class': 'btn btn-primary', 'value': 'Export Selected', + 'onClick': '$("form[name=\'rcExportForm\']").submit()'}); + $header.append($exportButton); + }; + + this.paintSubmissionTypeDropdown = function($container) { + this.$submissionTypeDropdown = this.getSubmissionTypeDropdown(); + var entityTypeDropdownFormGroup = FormUtil.getFieldForComponentWithLabel(this.$submissionTypeDropdown, 'Submission Type', null, true); + entityTypeDropdownFormGroup.css('width', '50%'); + $container.append(entityTypeDropdownFormGroup); + }; + + this.getSubmissionTypeDropdown = function() { + return FormUtil.getDropdown(researchCollectionExportModel.submissionTypes, 'Select a submission type'); + }; + + this.refreshSubmissionTypeDropdown = function() { + FormUtil.setValuesToComponent(this.$submissionTypeDropdown, researchCollectionExportModel.submissionTypes); + } + +} \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js index da2bc285601d7306d4be29f219144abf0b3b7233..41e50655fc351eb9bf72cbfea1b646c4e204c208 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js @@ -267,7 +267,14 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) { if(profile.mainMenu.showExports) { Â Â Â Â var exportBuilderLink = _this.getLinkForNode("Export Builder", "EXPORT_BUILDER", "showExportTreePage", null); - Â Â Â Â treeModelUtils.push({ displayName: "Export Builder", title : exportBuilderLink, entityType: "EXPORT_BUILDER", key : "EXPORT_BUILDER", folder : false, lazy : false, view : "showExportTreePage", icon : "glyphicon glyphicon-export" }); + Â Â Â Â treeModelUtils.push({ displayName: "Export Builder", title : exportBuilderLink, entityType: "EXPORT_BUILDER", key : "EXPORT_BUILDER", + folder : false, lazy : false, view : "showExportTreePage", icon : "glyphicon glyphicon-export" }); + + var researchCollectionExportBuilderLink = _this.getLinkForNode("Research Collection Export Builder", "RESEARCH_COLLECTION_EXPORT_BUILDER", + "showResearchCollectionExportPage", null); + treeModelUtils.push({ displayName: "Research Collection Export Builder", title: researchCollectionExportBuilderLink, + entityType: "RESEARCH_COLLECTION_EXPORT_BUILDER", key: "RESEARCH_COLLECTION_EXPORT_BUILDER", folder: false, lazy: false, + view: "showResearchCollectionExportPage" }); } if(profile.mainMenu.showStorageManager) { @@ -811,6 +818,7 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) { Â Â Â Â Â Â Â Â Â Â Â Â stock.setExpanded(true); Â Â Â Â Â Â Â Â } Â Â Â Â Â Â Â Â + setCustomIcon($tree, "RESEARCH_COLLECTION_EXPORT_BUILDER", "./img/research-collection-icon.png"); Â Â Â Â Â Â Â Â setCustomIcon($tree, "JUPYTER_WORKSPACE", "./img/jupyter-icon.png"); Â Â Â Â Â Â Â Â setCustomIcon($tree, "NEW_JUPYTER_NOTEBOOK", "./img/jupyter-icon.png"); } diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exports-api.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py similarity index 80% rename from openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exports-api.py rename to openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py index 1494b24e95d21316992c058d0e90a9e6ee9f148b..932c0fa302302c30bd0dc8174b5fd680c62a0707 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exports-api.py +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py @@ -13,42 +13,28 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from collections import deque -import time import jarray -import threading - -#Java Core -from java.io import ByteArrayInputStream -from org.apache.commons.io import IOUtils -from org.apache.commons.io import FileUtils - -from java.lang import String -from java.lang import StringBuilder - +# To obtain the openBIS URL +from ch.systemsx.cisd.openbis.dss.generic.server import DataStoreServer; from ch.systemsx.cisd.openbis.generic.client.web.client.exception import UserFailureException - -#Zip Format +from collections import deque +# Zip Format from java.io import File; from java.io import FileInputStream; -from java.io import FileNotFoundException; from java.io import FileOutputStream; +from java.lang import String +from java.lang import StringBuilder from java.util.zip import ZipEntry; from java.util.zip import ZipOutputStream; -from ch.systemsx.cisd.common.mail import EMailAddress; +from org.apache.commons.io import FileUtils +# Java Core +from org.apache.commons.io import IOUtils -#To obtain the openBIS URL -from ch.systemsx.cisd.openbis.dss.generic.server import DataStoreServer; -from ch.systemsx.cisd.openbis.dss.generic.server import SessionTokenManager -from ch.systemsx.cisd.openbis.generic.shared.api.v1.dto import SearchCriteria OPENBISURL = DataStoreServer.getConfigParameters().getServerURL() + "/openbis/openbis"; V3_DSS_BEAN = "data-store-server_INTERNAL"; #V3 API - Metadata -from ch.systemsx.cisd.common.spring import HttpInvokerUtils; -from ch.ethz.sis.openbis.generic.asapi.v3 import IApplicationServerApi; -from ch.ethz.sis.openbis.generic.asapi.v3.dto.property import DataType; from HTMLParser import HTMLParser from ch.ethz.sis.openbis.generic.asapi.v3.dto.space.search import SpaceSearchCriteria; @@ -75,7 +61,6 @@ from ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions import DataSe from ch.ethz.sis.openbis.generic.asapi.v3.dto.property import DataType #V3 API - Files -from ch.ethz.sis.openbis.generic.dssapi.v3 import IDataStoreServerApi; from ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.search import DataSetFileSearchCriteria; from ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.fetchoptions import DataSetFileFetchOptions; from ch.systemsx.cisd.openbis.dss.generic.shared import ServiceProvider; @@ -94,7 +79,7 @@ from ch.systemsx.cisd.openbis.dss.client.api.v1 import DssComponentFactory #Logging from ch.systemsx.cisd.common.logging import LogCategory; from org.apache.log4j import Logger; -operationLog = Logger.getLogger(str(LogCategory.OPERATION) + ".exports-api.py"); +operationLog = Logger.getLogger(str(LogCategory.OPERATION) + ".exportsApi.py"); #AVI API - DTO from ch.ethz.sis.openbis.generic.asapi.v3.dto.project import Project @@ -107,9 +92,6 @@ from ch.ethz.sis import DOCXBuilder #Images export for word from org.jsoup import Jsoup; -from org.jsoup.nodes import Document; -from org.jsoup.nodes import Element; -from org.jsoup.select import Elements; class MLStripper(HTMLParser): def __init__(self): @@ -125,32 +107,23 @@ def strip_tags(html): s.feed(html) return s.get_data() -def process(tr, params, tableBuilder): - method = params.get("method"); - isOk = False; - result = None; - - # Set user using the service - - tr.setUserId(userId); - if method == "exportAll": - isOk = expandAndexport(tr, params); +def displayResult(isOk, tableBuilder, result=None): if isOk: tableBuilder.addHeader("STATUS"); tableBuilder.addHeader("MESSAGE"); tableBuilder.addHeader("RESULT"); row = tableBuilder.addRow(); - row.setCell("STATUS","OK"); + row.setCell("STATUS", "OK"); row.setCell("MESSAGE", "Operation Successful"); row.setCell("RESULT", result); - else : + else: tableBuilder.addHeader("STATUS"); tableBuilder.addHeader("MESSAGE"); row = tableBuilder.addRow(); - row.setCell("STATUS","FAIL"); + row.setCell("STATUS", "FAIL"); row.setCell("MESSAGE", "Operation Failed"); - + def addToExportWithoutRepeating(entitiesToExport, entityFound): found = False; @@ -161,38 +134,56 @@ def addToExportWithoutRepeating(entitiesToExport, entityFound): if not found: entitiesToExport.append(entityFound); -def expandAndexport(tr, params): - #Services used during the export process - # TO-DO Login on the services as ETL server but on behalf of the user that makes the call + +def validateDataSize(entitiesToExport, tr): + limitDataSizeInMegabytes = getConfigurationProperty(tr, 'limit-data-size-megabytes') + if limitDataSizeInMegabytes is None: + limitDataSizeInMegabytes = 500; + else: + limitDataSizeInMegabytes = int(limitDataSizeInMegabytes); + limitDataSizeInBytes = 1000000 * limitDataSizeInMegabytes; + estimatedSizeInBytes = 0; + for entityToExport in entitiesToExport: + if entityToExport["type"] == "FILE" and entityToExport["isDirectory"] == False: + estimatedSizeInBytes += entityToExport["length"]; + elif entityToExport["type"] != "FILE": + estimatedSizeInBytes += 12000; # AVG File Metadata size + estimatedSizeInMegabytes = estimatedSizeInBytes / 1000000; + operationLog.info( + "Size Limit check - limitDataSizeInBytes: " + str(limitDataSizeInBytes) + " > " + " estimatedSizeInBytes: " + str(estimatedSizeInBytes)); + if estimatedSizeInBytes > limitDataSizeInBytes: + raise UserFailureException("The selected data is " + str(estimatedSizeInMegabytes) + " MB that is bigger than the configured limit of " + str( + limitDataSizeInMegabytes) + " MB"); + + +def findEntitiesToExport(params): sessionToken = params.get("sessionToken"); v3 = ServiceProvider.getV3ApplicationService(); v3d = ServiceProvider.getApplicationContext().getBean(V3_DSS_BEAN); - mailClient = tr.getGlobalState().getMailClient(); metadataOnly = params.get("metadataOnly"); - entitiesToExport = []; entitiesToExpand = deque([]); - + entitiesToExport = []; entities = params.get("entities"); - includeRoot = params.get("includeRoot"); - userEmail = v3.getSessionInformation(sessionToken).getPerson().getEmail(); + for entity in entities: entityAsPythonMap = { "type" : entity.get("type"), "permId" : entity.get("permId"), "expand" : entity.get("expand") }; - addToExportWithoutRepeating(entitiesToExport, entityAsPythonMap); + entitiesToExport.append(entityAsPythonMap); if entity.get("expand"): entitiesToExpand.append(entityAsPythonMap); - + + operationLog.info("Found %d entities to expand." % len(entitiesToExpand)) while entitiesToExpand: entityToExpand = entitiesToExpand.popleft(); - type = entityToExpand["type"]; + type = entityToExpand["type"]; permId = entityToExpand["permId"]; operationLog.info("Expanding type: " + str(type) + " permId: " + str(permId)); - + if type == "ROOT": criteria = SpaceSearchCriteria(); results = v3.searchSpaces(sessionToken, criteria, SpaceFetchOptions()); operationLog.info("Found: " + str(results.getTotalCount()) + " spaces"); for space in results.getObjects(): - entityFound = { "type" : "SPACE", "permId" : space.getCode() }; + entityFound = {"type": "SPACE", "permId": space.getCode(), "registrationDate": space.getRegistrationDate()}; addToExportWithoutRepeating(entitiesToExport, entityFound); entitiesToExpand.append(entityFound); if type == "SPACE": @@ -201,7 +192,7 @@ def expandAndexport(tr, params): results = v3.searchProjects(sessionToken, criteria, ProjectFetchOptions()); operationLog.info("Found: " + str(results.getTotalCount()) + " projects"); for project in results.getObjects(): - entityFound = { "type" : "PROJECT", "permId" : project.getPermId().getPermId() }; + entityFound = {"type": "PROJECT", "permId": project.getPermId().getPermId(), "registrationDate": project.getRegistrationDate()}; addToExportWithoutRepeating(entitiesToExport, entityFound); entitiesToExpand.append(entityFound); if type == "PROJECT": @@ -210,7 +201,8 @@ def expandAndexport(tr, params): results = v3.searchExperiments(sessionToken, criteria, ExperimentFetchOptions()); operationLog.info("Found: " + str(results.getTotalCount()) + " experiments"); for experiment in results.getObjects(): - entityFound = { "type" : "EXPERIMENT", "permId" : experiment.getPermId().getPermId() }; + entityFound = {"type": "EXPERIMENT", "permId": experiment.getPermId().getPermId(), + "registrationDate": experiment.getRegistrationDate()}; addToExportWithoutRepeating(entitiesToExport, entityFound); entitiesToExpand.append(entityFound); if type == "EXPERIMENT": @@ -218,29 +210,33 @@ def expandAndexport(tr, params): criteria.withExperiment().withPermId().thatEquals(permId); results = v3.searchSamples(sessionToken, criteria, SampleFetchOptions()); operationLog.info("Found: " + str(results.getTotalCount()) + " samples"); - + dCriteria = DataSetSearchCriteria(); dCriteria.withExperiment().withPermId().thatEquals(permId); dCriteria.withoutSample(); - dResults = v3.searchDataSets(sessionToken, dCriteria, DataSetFetchOptions()); + fetchOptions = DataSetFetchOptions() + fetchOptions.withDataStore() + dResults = v3.searchDataSets(sessionToken, dCriteria, fetchOptions); operationLog.info("Found: " + str(dResults.getTotalCount()) + " datasets"); for dataset in dResults.getObjects(): - entityFound = { "type" : "DATASET", "permId" : dataset.getPermId().getPermId() }; + entityFound = {"type": "DATASET", "permId": dataset.getPermId().getPermId(), "registrationDate": dataset.getRegistrationDate()}; addToExportWithoutRepeating(entitiesToExport, entityFound); entitiesToExpand.append(entityFound); - + operationLog.info("Found: " + str(results.getTotalCount()) + " samples"); for sample in results.getObjects(): - entityFound = { "type" : "SAMPLE", "permId" : sample.getPermId().getPermId() }; + entityFound = {"type": "SAMPLE", "permId": sample.getPermId().getPermId(), "registrationDate": sample.getRegistrationDate()}; addToExportWithoutRepeating(entitiesToExport, entityFound); entitiesToExpand.append(entityFound); if type == "SAMPLE": criteria = DataSetSearchCriteria(); criteria.withSample().withPermId().thatEquals(permId); - results = v3.searchDataSets(sessionToken, criteria, DataSetFetchOptions()); + fetchOptions = DataSetFetchOptions() + fetchOptions.withDataStore() + results = v3.searchDataSets(sessionToken, criteria, fetchOptions); operationLog.info("Found: " + str(results.getTotalCount()) + " datasets"); for dataset in results.getObjects(): - entityFound = { "type" : "DATASET", "permId" : dataset.getPermId().getPermId() }; + entityFound = {"type": "DATASET", "permId": dataset.getPermId().getPermId(), "registrationDate": dataset.getRegistrationDate()}; addToExportWithoutRepeating(entitiesToExport, entityFound); entitiesToExpand.append(entityFound); if type == "DATASET" and not metadataOnly: @@ -249,70 +245,51 @@ def expandAndexport(tr, params): results = v3d.searchFiles(sessionToken, criteria, DataSetFileFetchOptions()); operationLog.info("Found: " + str(results.getTotalCount()) + " files"); for file in results.getObjects(): - entityFound = { "type" : "FILE", "permId" : permId, "path" : file.getPath(), "isDirectory" : file.isDirectory(), "length" : file.getFileLength() }; + entityFound = {"type": "FILE", "permId": permId, "path": file.getPath(), "isDirectory": file.isDirectory(), + "length": file.getFileLength()}; addToExportWithoutRepeating(entitiesToExport, entityFound); - - - limitDataSizeInMegabytes = getConfigurationProperty(tr, 'limit-data-size-megabytes'); - if limitDataSizeInMegabytes is None: - limitDataSizeInMegabytes = 500; - else: - limitDataSizeInMegabytes = int(limitDataSizeInMegabytes); - - limitDataSizeInBytes = 1000000 * limitDataSizeInMegabytes; - estimatedSizeInBytes = 0; - for entityToExport in entitiesToExport: - if entityToExport["type"] == "FILE" and entityToExport["isDirectory"] == False: - estimatedSizeInBytes += entityToExport["length"]; - elif entityToExport["type"] != "FILE": - estimatedSizeInBytes += 12000; #AVG File Metadata size - estimatedSizeInMegabytes = estimatedSizeInBytes / 1000000; - - operationLog.info("Size Limit check - limitDataSizeInBytes: " + str(limitDataSizeInBytes) + " > " + " estimatedSizeInBytes: " + str(estimatedSizeInBytes)); - if estimatedSizeInBytes > limitDataSizeInBytes: - raise UserFailureException("The selected data is " + str(estimatedSizeInMegabytes) + " MB that is bigger than the configured limit of " + str(limitDataSizeInMegabytes) + " MB"); - - operationLog.info("Found " + str(len(entitiesToExport)) + " entities to export, export thread will start"); - thread = threading.Thread(target=export, args=(sessionToken, entitiesToExport, includeRoot, userEmail, mailClient)); - thread.daemon = True; - thread.start(); - - return True; + return entitiesToExport + + +# Removes temporal folder and zip +def cleanUp(tempDirPath, tempZipFilePath): + FileUtils.forceDelete(File(tempDirPath)); + FileUtils.forceDelete(File(tempZipFilePath)); + + +# Generates ZIP file and stores it in workspace +def generateZipFile(entities, includeRoot, sessionToken, tempDirPath, tempZipFilePath): + # Create Zip File + fos = FileOutputStream(tempZipFilePath); + zos = ZipOutputStream(fos); + + generateFilesInZip(zos, entities, includeRoot, sessionToken, tempDirPath) -def export(sessionToken, entities, includeRoot, userEmail, mailClient): - #Services used during the export process + zos.close(); + fos.close(); + + +def generateFilesInZip(zos, entities, includeRoot, sessionToken, tempDirPath): + # Services used during the export process v3 = ServiceProvider.getV3ApplicationService(); v3d = ServiceProvider.getApplicationContext().getBean(V3_DSS_BEAN); - dssComponent = DssComponentFactory.tryCreate(sessionToken, OPENBISURL); - objectCache = {}; objectMapper = GenericObjectMapper(); objectMapper.enable(SerializationFeature.INDENT_OUTPUT); - - #Create temporal folder - tempDirName = "export_" + str(time.time()); - tempDirPathFile = File.createTempFile(tempDirName, None); - tempDirPathFile.delete(); - tempDirPathFile.mkdir(); - tempDirPath = tempDirPathFile.getCanonicalPath(); - #To avoid empty directories on the zip file, it makes the first found entity the base directory + # To avoid empty directories on the zip file, it makes the first found entity the base directory baseDirToCut = None; - - #Create Zip File - tempZipFileName = tempDirName + ".zip"; - tempZipFilePath = tempDirPath + ".zip"; - fos = FileOutputStream(tempZipFilePath); - zos = ZipOutputStream(fos); - + fileMetadata = [] + emptyZip = True + for entity in entities: - type = entity["type"]; + type = entity["type"]; permId = entity["permId"]; operationLog.info("exporting type: " + str(type) + " permId: " + str(permId)); entityObj = None; entityFilePath = None; - + if type == "SPACE": - pass #Do nothing + pass # Do nothing if type == "PROJECT": criteria = ProjectSearchCriteria(); criteria.withPermId().thatEquals(permId); @@ -333,7 +310,8 @@ def export(sessionToken, entities, includeRoot, userEmail, mailClient): fetchOps.withProperties(); fetchOps.withTags(); entityObj = v3.searchExperiments(sessionToken, criteria, fetchOps).getObjects().get(0); - entityFilePath = getFilePath(entityObj.getProject().getSpace().getCode(), entityObj.getProject().getCode(), entityObj.getCode(), None, None); + entityFilePath = getFilePath(entityObj.getProject().getSpace().getCode(), entityObj.getProject().getCode(), entityObj.getCode(), None, + None); if type == "SAMPLE": criteria = SampleSearchCriteria(); criteria.withPermId().thatEquals(permId); @@ -347,7 +325,9 @@ def export(sessionToken, entities, includeRoot, userEmail, mailClient): fetchOps.withParents().withProperties(); fetchOps.withChildren().withProperties(); entityObj = v3.searchSamples(sessionToken, criteria, fetchOps).getObjects().get(0); - entityFilePath = getFilePath(entityObj.getExperiment().getProject().getSpace().getCode(), entityObj.getExperiment().getProject().getCode(), entityObj.getExperiment().getCode(), entityObj.getCode(), None); + entityFilePath = getFilePath(entityObj.getExperiment().getProject().getSpace().getCode(), + entityObj.getExperiment().getProject().getCode(), entityObj.getExperiment().getCode(), entityObj.getCode(), + None); if type == "DATASET": criteria = DataSetSearchCriteria(); criteria.withPermId().thatEquals(permId); @@ -362,71 +342,84 @@ def export(sessionToken, entities, includeRoot, userEmail, mailClient): fetchOps.withParents().withProperties(); fetchOps.withChildren().withProperties(); entityObj = v3.searchDataSets(sessionToken, criteria, fetchOps).getObjects().get(0); - + sampleCode = None - if(entityObj.getSample() is not None): + if (entityObj.getSample() is not None): sampleCode = entityObj.getSample().getCode(); - - entityFilePath = getFilePath(entityObj.getExperiment().getProject().getSpace().getCode(), entityObj.getExperiment().getProject().getCode(), entityObj.getExperiment().getCode(), sampleCode, entityObj.getCode()); + + entityFilePath = getFilePath(entityObj.getExperiment().getProject().getSpace().getCode(), + entityObj.getExperiment().getProject().getCode(), entityObj.getExperiment().getCode(), sampleCode, + entityObj.getCode()); if type == "FILE" and not entity["isDirectory"]: datasetEntityObj = objectCache[entity["permId"]]; sampleCode = None - if(datasetEntityObj.getSample() is not None): + if (datasetEntityObj.getSample() is not None): sampleCode = datasetEntityObj.getSample().getCode(); - - datasetEntityFilePath = getFilePath(datasetEntityObj.getExperiment().getProject().getSpace().getCode(), datasetEntityObj.getExperiment().getProject().getCode(), datasetEntityObj.getExperiment().getCode(), sampleCode, datasetEntityObj.getCode()); + + datasetEntityFilePath = getFilePath(datasetEntityObj.getExperiment().getProject().getSpace().getCode(), + datasetEntityObj.getExperiment().getProject().getCode(), datasetEntityObj.getExperiment().getCode(), + sampleCode, datasetEntityObj.getCode()); filePath = datasetEntityFilePath + "/" + entity["path"]; - + if not includeRoot: - filePath = filePath[len(baseDirToCut):] #To avoid empty directories on the zip file, it makes the first found entity the base directory - - rawFileInputStream = DataSetFileDownloadReader(v3d.downloadFiles(sessionToken, [DataSetFilePermId(DataSetPermId(permId), entity["path"])], DataSetFileDownloadOptions())).read().getInputStream(); + filePath = filePath[ + len(baseDirToCut):] # To avoid empty directories on the zip file, it makes the first found entity the base directory + + rawFileInputStream = DataSetFileDownloadReader(v3d.downloadFiles(sessionToken, [DataSetFilePermId(DataSetPermId(permId), entity["path"])], + DataSetFileDownloadOptions())).read().getInputStream(); rawFile = File(tempDirPath + filePath + ".json"); rawFile.getParentFile().mkdirs(); IOUtils.copyLarge(rawFileInputStream, FileOutputStream(rawFile)); addToZipFile(filePath, rawFile, zos); - - #To avoid empty directories on the zip file, it makes the first found entity the base directory + emptyZip = False + + # To avoid empty directories on the zip file, it makes the first found entity the base directory if not includeRoot: if baseDirToCut is None and entityFilePath is not None: baseDirToCut = entityFilePath[:entityFilePath.rfind('/')]; if entityFilePath is not None: entityFilePath = entityFilePath[len(baseDirToCut):] # - + if entityObj is not None: objectCache[permId] = entityObj; - - operationLog.info("--> Entity type: " + type + " permId: " + permId + " obj: " + str(entityObj is not None) + " path: " + str(entityFilePath) + " before files."); + + operationLog.info("--> Entity type: " + type + " permId: " + permId + " obj: " + str(entityObj is not None) + " path: " + str( + entityFilePath) + " before files."); if entityObj is not None and entityFilePath is not None: - #JSON + # JSON entityJson = String(objectMapper.writeValueAsString(entityObj)); - addFile(tempDirPath, entityFilePath, "json", entityJson.getBytes(), zos); - #TEXT + fileMetadatum = addFile(tempDirPath, entityFilePath, "json", entityJson.getBytes(), zos); + fileMetadata.append(fileMetadatum) + emptyZip = False + # TEXT entityTXT = String(getTXT(entityObj, v3, sessionToken, False)); - addFile(tempDirPath, entityFilePath, "txt", entityTXT.getBytes(), zos); - #DOCX + fileMetadatum = addFile(tempDirPath, entityFilePath, "txt", entityTXT.getBytes(), zos); + fileMetadata.append(fileMetadatum) + # DOCX entityDOCX = getDOCX(entityObj, v3, sessionToken, False); - addFile(tempDirPath, entityFilePath, "docx", entityDOCX, zos); - #HTML + fileMetadatum = addFile(tempDirPath, entityFilePath, "docx", entityDOCX, zos); + fileMetadata.append(fileMetadatum) + # HTML entityHTML = getDOCX(entityObj, v3, sessionToken, True); - addFile(tempDirPath, entityFilePath, "html", entityHTML, zos); + fileMetadatum = addFile(tempDirPath, entityFilePath, "html", entityHTML, zos); + fileMetadata.append(fileMetadatum) operationLog.info("--> Entity type: " + type + " permId: " + permId + " post html."); - - zos.close(); - fos.close(); - - #Store on workspace to be able to generate a download link + if emptyZip: + raise IOError('Nothing added to ZIP file.') + return fileMetadata + + +def generateDownloadUrl(sessionToken, tempZipFileName, tempZipFilePath): + dssComponent = DssComponentFactory.tryCreate(sessionToken, OPENBISURL); + + # Store on workspace to be able to generate a download link operationLog.info("Zip file can be found on the temporal directory: " + tempZipFilePath); dssComponent.putFileToSessionWorkspace(tempZipFileName, FileInputStream(File(tempZipFilePath))); tempZipFileWorkspaceURL = DataStoreServer.getConfigParameters().getDownloadURL() + "/datastore_server/session_workspace_file_download?sessionID=" + sessionToken + "&filePath=" + tempZipFileName; operationLog.info("Zip file can be downloaded from the workspace: " + tempZipFileWorkspaceURL); - #Send Email - sendMail(mailClient, userEmail, tempZipFileWorkspaceURL); - #Remove temporal folder and zip - FileUtils.forceDelete(File(tempDirPath)); - FileUtils.forceDelete(File(tempZipFilePath)); - return True + return tempZipFileWorkspaceURL + def getDOCX(entityObj, v3, sessionToken, isHTML): docxBuilder = DOCXBuilder(); @@ -600,12 +593,25 @@ def getTXT(entityObj, v3, sessionToken, isRichText): return txtBuilder.toString(); def addFile(tempDirPath, entityFilePath, extension, fileContent, zos): - entityFile = File(tempDirPath + entityFilePath + "." + extension); + entityFileNameWithExtension = entityFilePath + "." + extension + entityFile = File(tempDirPath + entityFileNameWithExtension); entityFile.getParentFile().mkdirs(); IOUtils.write(fileContent, FileOutputStream(entityFile)); - addToZipFile(entityFilePath + "." + extension, entityFile, zos); + addToZipFile(entityFileNameWithExtension, entityFile, zos); FileUtils.forceDelete(entityFile); + extensionToMimeType = { + 'json': 'application/json', + 'txt': 'text/plain', + 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'html': 'text/html', + } + + return { + 'fileName': entityFileNameWithExtension[1:], + 'mimeType': extensionToMimeType.get(extension, 'application/octet-stream') + } + def getFilePath(spaceCode, projCode, expCode, sampCode, dataCode): fileName = ""; if spaceCode is not None: @@ -621,27 +627,18 @@ def getFilePath(spaceCode, projCode, expCode, sampCode, dataCode): return fileName; def addToZipFile(path, file, zos): - fis = FileInputStream(file); - zipEntry = ZipEntry(path[1:]); # Making paths relative to make them compatible with Windows zip implementation - zos.putNextEntry(zipEntry); - - bytes = jarray.zeros(1024, "b"); + fis = FileInputStream(file); + zipEntry = ZipEntry(path[1:]); # Making paths relative to make them compatible with Windows zip implementation + zos.putNextEntry(zipEntry); + + bytes = jarray.zeros(1024, "b"); + length = fis.read(bytes); + while length >= 0: + zos.write(bytes, 0, length); length = fis.read(bytes); - while length >= 0: - zos.write(bytes, 0, length); - length = fis.read(bytes); - - zos.closeEntry(); - fis.close(); - -def sendMail(mailClient, userEmail, downloadURL): - replyTo = None; - fromAddress = None; - recipient1 = EMailAddress(userEmail); - topic = "Export Ready"; - message = "Download a zip file with your exported data at: " + downloadURL; - mailClient.sendEmailMessage(topic, message, replyTo, fromAddress, recipient1); - operationLog.info("--- MAIL ---" + " Recipient: " + userEmail + " Topic: " + topic + " Message: " + message); + + zos.closeEntry(); + fis.close(); def getConfigurationProperty(transaction, propertyName): threadProperties = transaction.getGlobalState().getThreadParameters().getThreadProperties(); diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/generalExports.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/generalExports.py new file mode 100644 index 0000000000000000000000000000000000000000..4a5845d984bb3b241d4f8487d8b7096699110372 --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/generalExports.py @@ -0,0 +1,87 @@ +# +# Copyright 2016 ETH Zuerich, Scientific IT Services +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import threading +import time +from ch.systemsx.cisd.common.logging import LogCategory +from ch.systemsx.cisd.common.mail import EMailAddress +from ch.systemsx.cisd.openbis.dss.generic.shared import ServiceProvider +from java.io import File +from org.apache.log4j import Logger + +from exportsApi import generateZipFile, cleanUp, displayResult, findEntitiesToExport, validateDataSize, generateDownloadUrl + +operationLog = Logger.getLogger(str(LogCategory.OPERATION) + ".generalExports.py") + +def process(tr, params, tableBuilder): + method = params.get("method") + isOk = False + + # Set user using the service + + tr.setUserId(userId) + if method == "exportAll": + isOk = expandAndExport(tr, params) + + displayResult(isOk, tableBuilder) + +def expandAndExport(tr, params): + #Services used during the export process + # TO-DO Login on the services as ETL server but on behalf of the user that makes the call + sessionToken = params.get("sessionToken") + v3 = ServiceProvider.getV3ApplicationService() + userEmail = v3.getSessionInformation(sessionToken).getPerson().getEmail() + + entitiesToExport = findEntitiesToExport(params) + validateDataSize(entitiesToExport, tr) + + mailClient = tr.getGlobalState().getMailClient() + includeRoot = params.get("includeRoot") + + operationLog.info("Found " + str(len(entitiesToExport)) + " entities to export, export thread will start") + thread = threading.Thread(target=export, args=(sessionToken, entitiesToExport, includeRoot, userEmail, mailClient)) + thread.daemon = True + thread.start() + + return True + +def export(sessionToken, entities, includeRoot, userEmail, mailClient): + #Create temporal folder + tempDirName = "export_" + str(time.time()) + tempDirPathFile = File.createTempFile(tempDirName, None) + tempDirPathFile.delete() + tempDirPathFile.mkdir() + tempDirPath = tempDirPathFile.getCanonicalPath() + tempZipFileName = tempDirName + ".zip" + tempZipFilePath = tempDirPath + ".zip" + + generateZipFile(entities, includeRoot, sessionToken, tempDirPath, tempZipFilePath) + tempZipFileWorkspaceURL = generateDownloadUrl(sessionToken, tempZipFileName, tempZipFilePath) + + #Send Email + sendMail(mailClient, userEmail, tempZipFileWorkspaceURL) + + cleanUp(tempDirPath, tempZipFilePath) + return True + +def sendMail(mailClient, userEmail, downloadURL): + replyTo = None + fromAddress = None + recipient1 = EMailAddress(userEmail) + topic = "Export Ready" + message = "Download a zip file with your exported data at: " + downloadURL + mailClient.sendEmailMessage(topic, message, replyTo, fromAddress, recipient1) + operationLog.info("--- MAIL ---" + " Recipient: " + userEmail + " Topic: " + topic + " Message: " + message) \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/plugin.properties b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/plugin.properties index 223af21c0cf556733441fecc10d0da8bd512f52e..e44c1b3b476482436d6533c898d5f1edc5f0689c 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/plugin.properties +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/plugin.properties @@ -1,4 +1,4 @@ label = Exports API class = ch.systemsx.cisd.openbis.dss.generic.server.plugins.jython.JythonIngestionService -script-path = exports-api.py +script-path = generalExports.py limit-data-size-megabytes = 10000 \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/exportsApi.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/exportsApi.py new file mode 120000 index 0000000000000000000000000000000000000000..ccb8a49feb33c9712b493e377365bd0b7dad5f8d --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/exportsApi.py @@ -0,0 +1 @@ +../exports-api/exportsApi.py \ No newline at end of file diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/plugin.properties b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/plugin.properties new file mode 100644 index 0000000000000000000000000000000000000000..eabafa539e8a00741e999e6ce7b45bbb7eb62ed9 --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/plugin.properties @@ -0,0 +1,8 @@ +label = Research Collection Exports API +class = ch.systemsx.cisd.openbis.dss.generic.server.plugins.jython.JythonIngestionService +script-path = rcExports.py +limit-data-size-megabytes = 4000 +service-document-url=https://test.research-collection.ethz.ch/swordv2/servicedocument +realm=SWORD2 +user=youruser +password=yourpassword diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py new file mode 100644 index 0000000000000000000000000000000000000000..e354f38dcdd770f6a8b98b765556fc936ad5dfab --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py @@ -0,0 +1,384 @@ +# +# Copyright 2016 ETH Zuerich, Scientific IT Services +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import json +import os +import traceback +import xml.etree.ElementTree as ET + +import datetime +import time +from ch.systemsx.cisd.common.logging import LogCategory +from java.io import File +from java.io import FileOutputStream +from java.net import URI +from java.nio.file import Paths +from java.text import SimpleDateFormat +from java.util import UUID +from java.util.zip import ZipOutputStream +from org.apache.commons.io import FileUtils +from org.apache.log4j import Logger +from org.eclipse.jetty.client import HttpClient +from org.eclipse.jetty.client.util import BasicAuthentication +from org.eclipse.jetty.http import HttpMethod +from org.eclipse.jetty.util.ssl import SslContextFactory + +from exportsApi import displayResult, findEntitiesToExport, validateDataSize, getConfigurationProperty, generateFilesInZip, addToZipFile, cleanUp + +operationLog = Logger.getLogger(str(LogCategory.OPERATION) + '.rcExports.py') + + +def process(tr, params, tableBuilder): + method = params.get('method') + + # Set user using the service + tr.setUserId(userId) + + if method == 'exportAll': + resultUrl = expandAndExport(tr, params) + displayResult(resultUrl is not None, tableBuilder, '{"url": "' + resultUrl + '"}' if resultUrl is not None else None) + elif method == 'getSubmissionTypes': + collectionUrls = getSubmissionTypes(tr) + displayResult(collectionUrls is not None, tableBuilder, collectionUrls) + + +def getSubmissionTypes(tr): + url = getConfigurationProperty(tr, 'service-document-url') + + httpClient = None + try: + httpClient = authenticateUserJava(url, tr) + httpClient.setFollowRedirects(True) + httpClient.start() + + collections = fetchServiceDocument(url, httpClient) + finally: + if httpClient is not None: + httpClient.stop() + return collections + + +def expandAndExport(tr, params): + entitiesToExport = findEntitiesToExport(params) + validateDataSize(entitiesToExport, tr) + + userInformation = { + 'firstName': params.get('userFirstName'), + 'lastName': params.get('userLastName'), + 'email': params.get('userEmail'), + } + + operationLog.info('Found ' + str(len(entitiesToExport)) + ' entities to export') + return export(entities=entitiesToExport, tr=tr, params=params, userInformation=userInformation) + + +def export(entities, tr, params, userInformation): + #Create temporal folder + timeNow = time.time() + + exportDirName = 'export_' + str(timeNow) + exportDir = File.createTempFile(exportDirName, None) + exportDirPath = exportDir.getCanonicalPath() + exportDir.delete() + exportDir.mkdir() + + contentZipFileName = 'content.zip' + contentDirName = 'content_' + str(timeNow) + contentDir = File.createTempFile(contentDirName, None, exportDir) + contentDirPath = contentDir.getCanonicalPath() + contentDir.delete() + contentDir.mkdir() + + contentZipFilePath = exportDirPath + '/' + contentZipFileName + + exportZipFilePath = exportDirPath + '.zip' + exportZipFileName = exportDirName + '.zip' + + generateInternalZipFile(entities, params, contentDirPath, contentZipFilePath) + FileUtils.forceDelete(File(contentDirPath)) + + generateExternalZipFile(params=params, exportDirPath=exportDirPath, contentZipFilePath=contentZipFilePath, contentZipFileName=contentZipFileName, + exportZipFileName=exportZipFilePath, userInformation=userInformation, entities=entities) + resultUrl = sendToDSpace(params=params, tr=tr, tempZipFileName=exportZipFileName, tempZipFilePath=exportZipFilePath) + cleanUp(exportDirPath, exportZipFilePath) + return resultUrl + + +def sendToDSpace(params, tr, tempZipFileName, tempZipFilePath): + depositUrl = str(params.get('submissionUrl')) + + headers = { + 'In-Progress': 'true', + 'Content-Disposition': 'filename=' + tempZipFileName, + 'Content-Type': 'application/zip', + 'Content-Length': os.stat(tempZipFilePath).st_size, + 'Content-Transfer-Encoding': 'binary', + 'Packaging': 'http://purl.org/net/sword/package/METSDSpaceSIP', + 'On-Behalf-Of': str(params.get('userId')), + } + + httpClient = None + try: + httpClient = authenticateUserJava(depositUrl, tr) + + httpClient.setFollowRedirects(True) + httpClient.start() + + request = httpClient.newRequest(depositUrl) + for key, value in headers.iteritems(): + request.header(key, str(value)) + response = request.method(HttpMethod.POST).file(Paths.get(tempZipFilePath), 'application/zip').send() + status = response.getStatus() + if status >= 300: + reason = response.getReason() + raise ValueError('Unsuccessful response from the server: %s %s' % (status, reason)) + + xmlText = response.getContentAsString().encode('utf-8') + xmlRoot = ET.fromstring(xmlText) + linkElement = xmlRoot.find('xmlns:link[@rel="alternate"]', namespaces=dict(xmlns='http://www.w3.org/2005/Atom')) + if linkElement is None: + raise ValueError('No redirection URL is found in the response.') + + href = linkElement.attrib['href'] + + return href + except Exception as e: + operationLog.error('Exception at: ' + traceback.format_exc()) + operationLog.error('Exception: ' + str(e)) + raise e + finally: + if httpClient is not None: + httpClient.stop() + + +def authenticateUserJava(url, tr): + sslContextFactory = SslContextFactory() + httpClient = HttpClient(sslContextFactory) + uri = URI(url) + user = getConfigurationProperty(tr, 'user') + password = getConfigurationProperty(tr, 'password') + realm = getConfigurationProperty(tr, 'realm') + auth = httpClient.getAuthenticationStore() + auth.addAuthentication(BasicAuthentication(uri, realm, user, password)) + return httpClient + + +def fetchServiceDocument(url, httpClient): + response = httpClient.newRequest(url).method(HttpMethod.GET).send() + + xmlText = response.getContentAsString().encode('utf-8') + xmlRoot = ET.fromstring(xmlText) + collections = xmlRoot.findall('./xmlns:workspace/xmlns:collection[@href]', namespaces=dict(xmlns='http://www.w3.org/2007/app')) + + def collectionToDictionaryMapper(collection): + return { + 'title': collection.find('./atom:title', namespaces=dict(atom='http://www.w3.org/2005/Atom')).text, + 'url': collection.attrib['href'], + } + + return json.dumps(map(collectionToDictionaryMapper, collections)) + + +def generateInternalZipFile(entities, params, tempDirPath, tempZipFilePath): + # Generates ZIP file with selected item for export + + sessionToken = params.get('sessionToken') + includeRoot = params.get('includeRoot') + + fos = None + zos = None + try: + fos = FileOutputStream(tempZipFilePath) + zos = ZipOutputStream(fos) + + fileMetadata = generateFilesInZip(zos, entities, includeRoot, sessionToken, tempDirPath) + finally: + if zos is not None: + zos.close() + if fos is not None: + fos.close() + + return fileMetadata + + +def generateExternalZipFile(params, exportDirPath, contentZipFilePath, contentZipFileName, exportZipFileName, userInformation, entities): + # Generates ZIP file which will go to the research collection server + + originUrl=params.get('originUrl') + submissionType = str(params.get('submissionType')) + + fileMetadata = [ + { + 'fileName': contentZipFileName, + 'mimeType': 'application/zip' + } + ] + + fos = None + zos = None + try: + fos = FileOutputStream(exportZipFileName) + zos = ZipOutputStream(fos) + + addToZipFile(' ' + contentZipFileName, File(contentZipFilePath), zos) + + generateXML(zipOutputStream=zos, fileMetadata=fileMetadata, exportDirPath=exportDirPath, submissionType=submissionType, + userInformation=userInformation, entities=entities, originUrl=originUrl) + except Exception as e: + operationLog.error('Exception at: ' + traceback.format_exc()) + operationLog.error('Exception: ' + str(e)) + raise e + finally: + if zos is not None: + zos.close() + if fos is not None: + fos.close() + + +def generateXML(zipOutputStream, fileMetadata, exportDirPath, submissionType, userInformation, entities, originUrl): + ns = { + 'mets': 'http://www.loc.gov/METS/', + 'xlink': 'http://www.w3.org/1999/xlink', + 'dim': 'http://www.dspace.org/xmlns/dspace/dim' + } + + entityPermIds = map(lambda entity: entity['permId'], entities) + permIdsStr = reduce(lambda str, permId: str + ',' + permId, entityPermIds) + + withRegistrationDates = filter(lambda entity: 'registrationDate' in entity, entities) + registrationDates = map(lambda entity: entity['registrationDate'], withRegistrationDates) + + if len(registrationDates) > 0: + minDateStr = javaDateToStr(min(registrationDates, key=lambda date: date.getTime())) + maxDateStr = javaDateToStr(max(registrationDates, key=lambda date: date.getTime())) + else: + minDateStr = None + maxDateStr = None + + metsNS = ns['mets'] + xlinkNS = ns['xlink'] + dimNS = ns['dim'] + ET.register_namespace('mets', metsNS) + ET.register_namespace('xlink', xlinkNS) + ET.register_namespace('dim', dimNS) + + root = ET.Element(ET.QName(metsNS, 'METS')) + root.set('LABEL', 'DSpace Item') + root.set('ID', UUID.randomUUID().toString()) + + dmdSec = ET.SubElement(root, ET.QName(metsNS, 'dmdSec')) + dmdSec.set('GROUPID', 'group_dmd_0') + dmdSec.set('ID', 'dmd_1') + + mdWrap = ET.SubElement(dmdSec, ET.QName(metsNS, 'mdWrap')) + mdWrap.set('MDTYPE', 'OTHER') + mdWrap.set('OTHERMDTYPE', 'DIM') + + xmlData = ET.SubElement(mdWrap, ET.QName(metsNS, 'xmlData')) + + dim = ET.SubElement(xmlData, ET.QName(dimNS, 'dim')) + dim.set('dspaceType', 'ITEM') + + titleField = ET.SubElement(dim, ET.QName(dimNS, 'field')) + titleField.set('mdschema', 'dc') + titleField.set('element', 'title') + titleField.text = str(time.time()) + + typeField = ET.SubElement(dim, ET.QName(dimNS, 'field')) + typeField.set('mdschema', 'dc') + typeField.set('element', 'type') + typeField.text = submissionType + + userIdField = ET.SubElement(dim, ET.QName(dimNS, 'field')) + userIdField.set('mdschema', 'ethz') + userIdField.set('element', 'identifier') + userIdField.set('qualifier', 'openbis') + userIdField.text = permIdsStr + + userInfoField = ET.SubElement(dim, ET.QName(dimNS, 'field')) + userInfoField.set('mdschema', 'dc') + userInfoField.set('element', 'contributor') + userInfoField.set('qualifier', 'author') + userInfoField.text = userInformation['lastName'] + ', ' + userInformation['firstName'] + + publicationDateField = ET.SubElement(dim, ET.QName(dimNS, 'field')) + publicationDateField.set('mdschema', 'dc') + publicationDateField.set('element', 'date') + publicationDateField.set('qualifier', 'issued') + publicationDateField.text = datetime.date.today().strftime('%Y-%m-%d') + + elnLimsURLPattern = '/openbis-test/webapp/eln-lims/?menuUniqueId=null&viewName=' + + for entity in entities: + type = entity['type'] + viewName = '' + if type == 'SPACE': + viewName = 'showSpacePage' + if type == 'PROJECT': + viewName = 'showProjectPageFromPermId' + if type == 'EXPERIMENT': + viewName = 'showExperimentPageFromPermId' + if type == 'SAMPLE': + viewName = 'showViewSamplePageFromPermId' + if type == 'DATASET': + viewName = 'showViewDataSetPageFromPermId' + if type != 'FILE': + urlField = ET.SubElement(dim, ET.QName(dimNS, 'field')) + urlField.set('mdschema', 'ethz') + urlField.set('element', 'identifier') + urlField.set('qualifier', 'url') + urlField.text = originUrl + elnLimsURLPattern + viewName + '&viewData=' + entity['permId'] + + if minDateStr is not None and maxDateStr is not None: + creationDateField = ET.SubElement(dim, ET.QName(dimNS, 'field')) + creationDateField.set('mdschema', 'dc') + creationDateField.set('element', 'date') + creationDateField.set('qualifier', 'created') + creationDateField.text = minDateStr + '/' + maxDateStr if minDateStr != maxDateStr else minDateStr + + fileSec = ET.SubElement(root, ET.QName(metsNS, 'fileSec')) + fileGrp = ET.SubElement(fileSec, ET.QName(metsNS, 'fileGrp')) + fileGrp.set('USE', 'CONTENT') + + i = 0 + for fileMetadatum in fileMetadata: + i += 1 + file = ET.SubElement(fileGrp, ET.QName(metsNS, 'file')) + file.set('ID', 'file_' + str(i)) + fLocat = ET.SubElement(file, ET.QName(metsNS, 'FLocat')) + fLocat.set('LOCTYPE', 'URL') + fLocat.set('MIMETYPE', fileMetadatum.get('mimeType')) + fLocat.set('RETENTIONPERIOD', '10 years') + fLocat.set(ET.QName(xlinkNS, 'href'), fileMetadatum.get('fileName')) + + structMap = ET.SubElement(root, ET.QName(metsNS, 'structMap')) + structMap.set('LABEL', 'DSpace') + structMap.set('TYPE', 'LOGICAL') + div1 = ET.SubElement(structMap, ET.QName(metsNS, 'div')) + div1.set('DMDID', 'dmd_1') + div1.set('TYPE', 'DSpace Item') + + xmlFileName = 'mets.xml' + xmlFilePath = exportDirPath + '/' + xmlFileName + ET.ElementTree(root).write(xmlFilePath) + + xmlFile = File(xmlFilePath) + addToZipFile(' ' + xmlFileName, xmlFile, zipOutputStream) + # Space is added to the file name because the method chops out the first character + + +def javaDateToStr(javaDate): + dateFormat = SimpleDateFormat('yyyy-MM-dd') + return dateFormat.format(javaDate)