From ab66a5f1dafd4bb9e926626b0efb869163376bfa Mon Sep 17 00:00:00 2001 From: Aldo Mihasi Date: Wed, 26 Oct 2022 13:50:01 +0300 Subject: [PATCH] [WIP] implement zenodo repository deposit, add authentication for depositions --- dmp-backend/depositinterface/pom.xml | 8 +- .../repository/RepositoryDeposit.java | 2 +- dmp-backend/pom.xml | 1 - dmp-backend/web/pom.xml | 7 + .../eudat/controllers/DepositController.java | 26 +- .../managers/DataManagementPlanManager.java | 58 +++++ .../eudat/logic/managers/DepositManager.java | 36 ++- .../eudat/models/data/doi/DepositRequest.java | 29 +++ dmp-backend/zenodoRepository/pom.xml | 39 +++ .../dummyrepository/TestDummy.java | 29 +++ .../zenodorepository/config/ConfigLoader.java | 7 + .../config/ConfigLoaderImpl.java | 50 ++++ .../zenodorepository/config/DOIFunder.java | 27 ++ .../interfaces/ZenodoDeposit.java | 244 ++++++++++++++++++ .../mapper/DMPToZenodoMapper.java | 119 +++++++++ .../models/ZenodoAccessRight.java | 18 ++ .../models/ZenodoComunity.java | 19 ++ .../models/ZenodoContributor.java | 45 ++++ .../models/ZenodoDeposit.java | 19 ++ .../models/ZenodoDepositMetadata.java | 158 ++++++++++++ .../zenodorepository/models/ZenodoGrant.java | 18 ++ .../models/ZenodoRelator.java | 28 ++ .../src/main/resources/DOI_Funder.json | 70 +++++ .../src/main/resources/application.properties | 1 + .../enum/deposit-configuration-status.ts | 5 + .../model/deposit/deposit-configuration.ts | 11 + .../app/core/model/deposit/deposit-request.ts | 5 + .../deposit-repositories.service.ts | 18 +- .../dmp-deposit-dialog.component.html | 16 +- .../dmp-deposit-dialog.component.ts | 94 ++++++- .../ui/dmp/overview/dmp-overview.component.ts | 29 ++- 31 files changed, 1196 insertions(+), 40 deletions(-) create mode 100644 dmp-backend/web/src/main/java/eu/eudat/models/data/doi/DepositRequest.java create mode 100644 dmp-backend/zenodoRepository/pom.xml create mode 100644 dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/dummyrepository/TestDummy.java create mode 100644 dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/config/ConfigLoader.java create mode 100644 dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/config/ConfigLoaderImpl.java create mode 100644 dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/config/DOIFunder.java create mode 100644 dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/interfaces/ZenodoDeposit.java create mode 100644 dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/mapper/DMPToZenodoMapper.java create mode 100644 dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoAccessRight.java create mode 100644 dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoComunity.java create mode 100644 dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoContributor.java create mode 100644 dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoDeposit.java create mode 100644 dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoDepositMetadata.java create mode 100644 dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoGrant.java create mode 100644 dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoRelator.java create mode 100644 dmp-backend/zenodoRepository/src/main/resources/DOI_Funder.json create mode 100644 dmp-backend/zenodoRepository/src/main/resources/application.properties create mode 100644 dmp-frontend/src/app/core/common/enum/deposit-configuration-status.ts create mode 100644 dmp-frontend/src/app/core/model/deposit/deposit-configuration.ts create mode 100644 dmp-frontend/src/app/core/model/deposit/deposit-request.ts diff --git a/dmp-backend/depositinterface/pom.xml b/dmp-backend/depositinterface/pom.xml index 8899237cf..bfdd74f3a 100644 --- a/dmp-backend/depositinterface/pom.xml +++ b/dmp-backend/depositinterface/pom.xml @@ -4,11 +4,13 @@ 4.0.0 - eu.eudat - dmp-backend - 1.0.0-SNAPSHOT + org.springframework.boot + spring-boot-starter-parent + 2.7.4 + + eu.eudat depositinterface 1.0-SNAPSHOT jar diff --git a/dmp-backend/depositinterface/src/main/java/eu/eudat/depositinterface/repository/RepositoryDeposit.java b/dmp-backend/depositinterface/src/main/java/eu/eudat/depositinterface/repository/RepositoryDeposit.java index 59f6b43c2..7563a9595 100644 --- a/dmp-backend/depositinterface/src/main/java/eu/eudat/depositinterface/repository/RepositoryDeposit.java +++ b/dmp-backend/depositinterface/src/main/java/eu/eudat/depositinterface/repository/RepositoryDeposit.java @@ -6,7 +6,7 @@ public interface RepositoryDeposit { String deposit(DMPDepositModel dmpDepositModel, boolean update, String repositoryAccessToken) throws Exception; - //authenticate(); + String authenticate(String code); RepositoryDepositConfiguration getConfiguration(); diff --git a/dmp-backend/pom.xml b/dmp-backend/pom.xml index 27be888d0..daf645f04 100644 --- a/dmp-backend/pom.xml +++ b/dmp-backend/pom.xml @@ -18,7 +18,6 @@ web data elastic - depositinterface diff --git a/dmp-backend/web/pom.xml b/dmp-backend/web/pom.xml index 8170c8cee..4295c6b12 100644 --- a/dmp-backend/web/pom.xml +++ b/dmp-backend/web/pom.xml @@ -225,6 +225,13 @@ ZIP + + + + repackage + + + diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/DepositController.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/DepositController.java index 599871b01..1687f560f 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/DepositController.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/DepositController.java @@ -1,8 +1,10 @@ package eu.eudat.controllers; +import eu.eudat.depositinterface.repository.RepositoryDepositConfiguration; import eu.eudat.logic.managers.DepositManager; import eu.eudat.logic.security.claims.ClaimedAuthorities; import eu.eudat.logic.services.ApiContext; +import eu.eudat.models.data.doi.DepositRequest; import eu.eudat.models.data.helpers.responses.ResponseItem; import eu.eudat.models.data.security.Principal; import eu.eudat.types.ApiMessageCode; @@ -32,8 +34,26 @@ public class DepositController extends BaseController { @RequestMapping(method = RequestMethod.GET, value = {"/repos"}) public @ResponseBody - ResponseEntity>> getAvailableRepos(@ClaimedAuthorities(claims = {Authorities.ADMIN, Authorities.MANAGER, Authorities.USER, Authorities.ANONYMOUS}) Principal principal) throws Exception { - List ids = this.depositManager.getAvailableRepos(); - return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem>().status(ApiMessageCode.NO_MESSAGE).payload(ids)); + ResponseEntity>> getAvailableRepos(@ClaimedAuthorities(claims = {Authorities.ADMIN, Authorities.MANAGER, Authorities.USER, Authorities.ANONYMOUS}) Principal principal) { + List ids = this.depositManager.getAvailableRepos(); + return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem>().status(ApiMessageCode.NO_MESSAGE).payload(ids)); + } + + @RequestMapping(method = RequestMethod.GET, value = {"/getAccessToken"}) + public @ResponseBody + ResponseEntity> getAccessToken(@RequestParam("repository") String repository, @RequestParam("code") String code, @ClaimedAuthorities(claims = {Authorities.ADMIN, Authorities.MANAGER, Authorities.USER, Authorities.ANONYMOUS}) Principal principal) throws Exception { + String accessToken = this.depositManager.authenticate(repository, code); + return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().status(ApiMessageCode.NO_MESSAGE).payload(accessToken)); + } + + @RequestMapping(method = RequestMethod.POST, value = {"/createDoi"}) + public ResponseEntity> createDoi(@RequestBody DepositRequest depositRequest, @ClaimedAuthorities(claims = {Authorities.ADMIN, Authorities.MANAGER, Authorities.USER, Authorities.ANONYMOUS}) Principal principal) { + try { + String doi = this.depositManager.deposit(depositRequest, principal); + return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().status(ApiMessageCode.SUCCESS_MESSAGE).message("Successfully created DOI for Data Datamanagement Plan in question.").payload(doi)); + } catch (Exception e) { + logger.error(e.getMessage(), e); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseItem().status(ApiMessageCode.ERROR_MESSAGE).message("Failed to create DOI for the Data Management Plan: " + e.getMessage())); + } } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataManagementPlanManager.java b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataManagementPlanManager.java index 7bc65f570..b6d734eb4 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataManagementPlanManager.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataManagementPlanManager.java @@ -48,6 +48,7 @@ import eu.eudat.models.data.datasetprofile.DatasetProfileListingModel; import eu.eudat.models.data.datasetwizard.DatasetWizardModel; import eu.eudat.models.data.datasetwizard.DatasetsToBeFinalized; import eu.eudat.models.data.dmp.*; +import eu.eudat.models.data.doi.DepositRequest; import eu.eudat.models.data.dynamicfields.DynamicFieldWithValue; import eu.eudat.models.data.entities.xmlmodels.dmpprofiledefinition.DataManagementPlanProfile; import eu.eudat.models.data.entities.xmlmodels.dmpprofiledefinition.Field; @@ -63,6 +64,7 @@ import eu.eudat.models.data.userinfo.UserListingModel; import eu.eudat.queryable.QueryableList; import eu.eudat.types.Authorities; import eu.eudat.types.MetricNames; +import org.apache.commons.io.IOUtils; import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFParagraph; import org.apache.poi.xwpf.usermodel.XWPFRun; @@ -84,6 +86,7 @@ import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import java.io.*; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -2101,6 +2104,61 @@ public class DataManagementPlanManager { return finalDoi; } + public String createDoi(DepositRequest depositRequest, Principal principal) throws Exception { + DMP dmp = this.apiContext.getOperationsContext().getDatabaseRepository().getDmpDao().find(UUID.fromString(depositRequest.getDmpId())); + if (!isUserOwnerOfDmp(dmp, principal)) + throw new Exception("User is not authorized to invoke this action"); + if (!dmp.getStatus().equals(DMP.DMPStatus.FINALISED.getValue())) + throw new Exception("DMP is not finalized"); + /*if (dmp.getDoi() != null) + throw new Exception("DMP already has a DOI");*/ + + FileEnvelope file = getWordDocument(depositRequest.getDmpId(), principal, configLoader); + String name = file.getFilename().substring(0, file.getFilename().length() - 5); + //File pdfFile = PDFUtils.convertToPDF(file, environment); + String uuid = UUID.randomUUID().toString(); + File pdfFile = new File(environment.getProperty("temp.temp") + uuid + ".pdf"); + FileOutputStream output = new FileOutputStream(pdfFile); + IOUtils.write("testing".getBytes(StandardCharsets.UTF_8), output); + String fileName = name + ".pdf"; + output.close(); + ResponseEntity jsonFile; + try { + jsonFile = getRDAJsonDocument(depositRequest.getDmpId(), principal); + } catch (Exception e) { + throw e; + } + String previousDOI = this.getPreviousDOI(dmp.getGroupId(), dmp.getId()); + + DMPDepositModel dmpDepositModel = DMPToDepositMapper.fromDMP(dmp, pdfFile, fileName, jsonFile, previousDOI); + +// String zenodoToken = ""; +// try { +// if (this.userManager.isDOITokenValid(principal)) { +// zenodoToken = principal.getZenodoToken(); +// } +// } catch (NonValidTokenException e) { +// zenodoToken = this.environment.getProperty("zenodo.access_token"); +// } + + Optional repo = this.repositoriesDeposit.stream().filter(x -> x.getConfiguration().getRepositoryId().equals(depositRequest.getRepositoryId())).findFirst(); + String finalDoi = repo.map(r -> { + try { + return r.deposit(dmpDepositModel, false, depositRequest.getAccessToken()); + } catch (Exception e) { + logger.error(e.getMessage(), e); + return null; + } + }).orElse(null); + if (finalDoi != null) { + //dmp.setDoi(finalDoi); + //apiContext.getOperationsContext().getDatabaseRepository().getDmpDao().createOrUpdate(dmp); + } + Files.deleteIfExists(file.getFile().toPath()); + + return finalDoi; + } + /* * Misc * */ diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DepositManager.java b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DepositManager.java index 80c4cf472..978c27db7 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DepositManager.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DepositManager.java @@ -1,40 +1,62 @@ package eu.eudat.logic.managers; +import eu.eudat.data.entities.DMP; +import eu.eudat.depositinterface.models.DMPDepositModel; import eu.eudat.depositinterface.repository.RepositoryDeposit; +import eu.eudat.depositinterface.repository.RepositoryDepositConfiguration; +import eu.eudat.exceptions.security.NonValidTokenException; +import eu.eudat.logic.security.repositorydeposit.mapper.DMPToDepositMapper; +import eu.eudat.logic.utilities.documents.helpers.FileEnvelope; +import eu.eudat.logic.utilities.documents.pdf.PDFUtils; +import eu.eudat.models.data.doi.DepositRequest; +import eu.eudat.models.data.security.Principal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; +import java.io.File; +import java.nio.file.Files; import java.util.ArrayList; import java.util.List; +import java.util.Optional; @Component public class DepositManager { private static final Logger logger = LoggerFactory.getLogger(DepositManager.class); private List repositories; + private DataManagementPlanManager dataManagementPlanManager; @Autowired - public DepositManager(List repositories){ + public DepositManager(List repositories, DataManagementPlanManager dataManagementPlanManager){ this.repositories = repositories; + this.dataManagementPlanManager = dataManagementPlanManager; } - public List getAvailableRepos() { - List repos = new ArrayList<>(); + public List getAvailableRepos() { + List repos = new ArrayList<>(); -// logger.info("\n\n-------REPOS------\n"); +// logger.info("-------------REPOS------------"); // for (RepositoryDeposit r: this.repositories) { // logger.info("...Loaded Class: " // + r.getClass()); // } -// logger.info("\n-------------------------------"); +// logger.info("-------------------------------"); for (RepositoryDeposit r: this.repositories) { - repos.add(r.getConfiguration().getRepositoryId()); + repos.add(r.getConfiguration()); } - //repos.add("dummyId"); return repos; } + public String authenticate(String id, String code) { + Optional repo = repositories.stream().filter(x -> x.getConfiguration().getRepositoryId().equals(id)).findFirst(); + return repo.map(repositoryDeposit -> repositoryDeposit.authenticate(code)).orElse(null); + } + + public String deposit(DepositRequest depositRequest, Principal principal) throws Exception { + return this.dataManagementPlanManager.createDoi(depositRequest, principal); + } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/models/data/doi/DepositRequest.java b/dmp-backend/web/src/main/java/eu/eudat/models/data/doi/DepositRequest.java new file mode 100644 index 000000000..b6ee1f4a3 --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/models/data/doi/DepositRequest.java @@ -0,0 +1,29 @@ +package eu.eudat.models.data.doi; + +public class DepositRequest { + + private String repositoryId; + private String dmpId; + private String accessToken; + + public String getRepositoryId() { + return repositoryId; + } + public void setRepositoryId(String repositoryId) { + this.repositoryId = repositoryId; + } + + public String getDmpId() { + return dmpId; + } + public void setDmpId(String dmpId) { + this.dmpId = dmpId; + } + + public String getAccessToken() { + return accessToken; + } + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } +} diff --git a/dmp-backend/zenodoRepository/pom.xml b/dmp-backend/zenodoRepository/pom.xml new file mode 100644 index 000000000..9acabc826 --- /dev/null +++ b/dmp-backend/zenodoRepository/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.4 + + + eu.eudat.depositinterface + zenodoRepository + 0.0.1-SNAPSHOT + zenodoRepository + + + 11 + + + + + org.springframework.boot + spring-boot-starter-web + + + + eu.eudat + depositinterface + 1.0-SNAPSHOT + + + + org.json + json + 20160810 + + + + diff --git a/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/dummyrepository/TestDummy.java b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/dummyrepository/TestDummy.java new file mode 100644 index 000000000..cc9087d61 --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/dummyrepository/TestDummy.java @@ -0,0 +1,29 @@ +package eu.eudat.depositinterface.dummyrepository; + +import eu.eudat.depositinterface.models.DMPDepositModel; +import eu.eudat.depositinterface.repository.RepositoryDeposit; +import eu.eudat.depositinterface.repository.RepositoryDepositConfiguration; +import org.springframework.stereotype.Component; + +import static eu.eudat.depositinterface.repository.RepositoryDepositConfiguration.DepositAccountStatus.BothWaysDeposit; + +@Component +public class TestDummy implements RepositoryDeposit { + @Override + public String deposit(DMPDepositModel dmpDepositModel, boolean update, String repositoryAccessToken) throws Exception { + return "test2"; + } + + @Override + public RepositoryDepositConfiguration getConfiguration() { + RepositoryDepositConfiguration conf = new RepositoryDepositConfiguration(); + conf.setRepositoryId("Dummy"); + conf.setDepositAccountStatus(BothWaysDeposit.getValue()); + return conf; + } + + @Override + public String authenticate(String code) { + return "accessToken"; + } +} \ No newline at end of file diff --git a/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/config/ConfigLoader.java b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/config/ConfigLoader.java new file mode 100644 index 000000000..100f94d06 --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/config/ConfigLoader.java @@ -0,0 +1,7 @@ +package eu.eudat.depositinterface.zenodorepository.config; + +import java.util.List; + +public interface ConfigLoader { + List getDOIFunders(); +} diff --git a/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/config/ConfigLoaderImpl.java b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/config/ConfigLoaderImpl.java new file mode 100644 index 000000000..52514bda2 --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/config/ConfigLoaderImpl.java @@ -0,0 +1,50 @@ +package eu.eudat.depositinterface.zenodorepository.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service("zenodoConfigLoader") +public class ConfigLoaderImpl implements ConfigLoader{ + private static final Logger logger = LoggerFactory.getLogger(ConfigLoaderImpl.class); + private static final ObjectMapper mapper = new ObjectMapper(); + + private List doiFunders = new ArrayList<>(); + + @Autowired + private Environment environment; + + @Override + public List getDOIFunders() { + if (doiFunders == null || doiFunders.isEmpty()) { + try { + List> tempdoiFunders = mapper.readValue(getStreamFromPath(environment.getProperty("configuration.doi_funder")), List.class); + doiFunders = tempdoiFunders.stream().map(map -> mapper.convertValue(map, DOIFunder.class) ).collect(Collectors.toList()); + } catch (IOException e) { + logger.error(e.getLocalizedMessage(), e); + } + } + return doiFunders; + } + + private InputStream getStreamFromPath(String filePath) { + try { + return new FileInputStream(filePath); + } catch (FileNotFoundException e) { + logger.info("loading from classpath"); + return getClass().getClassLoader().getResourceAsStream(filePath); + } + } +} diff --git a/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/config/DOIFunder.java b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/config/DOIFunder.java new file mode 100644 index 000000000..88c7090a4 --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/config/DOIFunder.java @@ -0,0 +1,27 @@ +package eu.eudat.depositinterface.zenodorepository.config; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class DOIFunder { + + @JsonProperty("Funder") + private String funder; + @JsonProperty("DOI") + private String DOI; + + public String getFunder() { + return funder; + } + + public void setFunder(String funder) { + this.funder = funder; + } + + public String getDOI() { + return DOI; + } + + public void setDOI(String DOI) { + this.DOI = DOI; + } +} diff --git a/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/interfaces/ZenodoDeposit.java b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/interfaces/ZenodoDeposit.java new file mode 100644 index 000000000..c3bdef1ad --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/interfaces/ZenodoDeposit.java @@ -0,0 +1,244 @@ +package eu.eudat.depositinterface.zenodorepository.interfaces; + +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.eudat.depositinterface.models.DMPDepositModel; +import eu.eudat.depositinterface.repository.RepositoryDeposit; +import eu.eudat.depositinterface.repository.RepositoryDepositConfiguration; +import eu.eudat.depositinterface.zenodorepository.config.ConfigLoader; +import eu.eudat.depositinterface.zenodorepository.mapper.DMPToZenodoMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestTemplate; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.*; + +import static eu.eudat.depositinterface.repository.RepositoryDepositConfiguration.DepositAccountStatus.BothWaysDeposit; + +@Component +public class ZenodoDeposit implements RepositoryDeposit { + private static final Logger logger = LoggerFactory.getLogger(ZenodoDeposit.class); + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private ConfigLoader configLoader; + + @Autowired + public ZenodoDeposit(ConfigLoader configLoader){ + this.configLoader = configLoader; + } + + @Override + public String deposit(DMPDepositModel dmpDepositModel, boolean update, String zenodoToken) throws Exception { + + RepositoryDepositConfiguration conf = this.getConfiguration(); + String zenodoUrl = conf.getRepositoryUrl(); + + // First step, post call to Zenodo, to create the entry. + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setContentType(MediaType.APPLICATION_JSON); + eu.eudat.depositinterface.zenodorepository.models.ZenodoDeposit deposit = DMPToZenodoMapper.fromDMP(dmpDepositModel, "argos", "ARGOS", "https://argos.openaire.eu/", this.configLoader.getDOIFunders()); + + HttpEntity request = new HttpEntity<>(deposit, headers); + Map createResponse; + LinkedHashMap links; + String previousDOI = dmpDepositModel.getPreviousDOI(); + String unpublishedUrl = null; + String publishUrl; + String finalDoi; + try { + + if (previousDOI == null) { + String createUrl = zenodoUrl + "deposit/depositions" + "?access_token=" + zenodoToken; + createResponse = restTemplate.postForEntity(createUrl, request, Map.class).getBody(); + links = (LinkedHashMap) createResponse.get("links"); + finalDoi = (String) createResponse.get("conceptdoi"); + } else { + unpublishedUrl = this.getUnpublishedDOI(zenodoUrl, previousDOI, zenodoToken, dmpDepositModel.getVersion()); + if (unpublishedUrl == null) { + //It requires more than one step to create a new version + //First, get the deposit related to the concept DOI + String listUrl = zenodoUrl + "deposit/depositions" + "?q=conceptdoi:\"" + previousDOI + "\"&access_token=" + zenodoToken; + ResponseEntity listResponses = restTemplate.getForEntity(listUrl, Map[].class); + createResponse = listResponses.getBody()[0]; + links = (LinkedHashMap) createResponse.get("links"); + //Second, make the new version (not in the links?) + String newVersionUrl = links.get("self") + "/actions/newversion" + "?access_token=" + zenodoToken; + createResponse = restTemplate.postForObject(newVersionUrl, null, Map.class); + links = (LinkedHashMap) createResponse.get("links"); + //Third, get the new deposit + String latestDraftUrl = links.get("latest_draft") + "?access_token=" + zenodoToken; + createResponse = restTemplate.getForObject(latestDraftUrl, Map.class); + links = (LinkedHashMap) createResponse.get("links"); + finalDoi = (String) createResponse.get("conceptdoi"); + //At this point it might fail to perform the next requests so enclose them with try catch + try { + //Forth, update the new deposit's metadata + String updateUrl = links.get("self") + "?access_token=" + zenodoToken; + restTemplate.put(updateUrl, request); + //And finally remove pre-existing files from it + String fileListUrl = links.get("self") + "/files" + "?access_token=" + zenodoToken; + ResponseEntity fileListResponse = restTemplate.getForEntity(fileListUrl, Map[].class); + for (Map file : fileListResponse.getBody()) { + String fileDeleteUrl = links.get("self") + "/files/" + file.get("id") + "?access_token=" + zenodoToken; + restTemplate.delete(fileDeleteUrl); + } + } catch (Exception e) { + //In case the last two steps fail delete the latest Deposit it in order to create a new one (only one at a time is allowed) + restTemplate.delete(latestDraftUrl); + throw e; + } + } + else { + String listUrl = zenodoUrl + "deposit/depositions" + "?q=conceptdoi:\"" + previousDOI + "\"&access_token=" + zenodoToken; + ResponseEntity listResponses = restTemplate.getForEntity(listUrl, Map[].class); + createResponse = listResponses.getBody()[0]; + links = (LinkedHashMap) createResponse.get("links"); + } + } + + if (!update) { + if (unpublishedUrl == null) { + // Second step, add the file to the entry. + File pdfFile = dmpDepositModel.getPdfFile(); + String name = dmpDepositModel.getPdfFileName(); + String fileName = name + ".pdf"; + FileSystemResource fileSystemResource = new FileSystemResource(pdfFile); + HttpEntity addFileMapRequest = new HttpEntity<>(fileSystemResource, null); + + String addFileUrl = links.get("bucket") + "/" + fileName + "?access_token=" + zenodoToken; + restTemplate.put(addFileUrl, addFileMapRequest); + + ResponseEntity jsonFile = dmpDepositModel.getRdaJson(); + UUID jsonFileUUID = UUID.randomUUID(); + File tempJsonFile = new File(jsonFileUUID + ".json"); // temp storage?? + try (FileOutputStream jsonFos = new FileOutputStream(tempJsonFile)) { + jsonFos.write(jsonFile.getBody()); + jsonFos.flush(); + } + fileSystemResource = new FileSystemResource(tempJsonFile); + HttpHeaders jsonHeaders = new HttpHeaders(); + jsonHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE); + addFileMapRequest = new HttpEntity<>(fileSystemResource, jsonHeaders); + String jsonFileName = jsonFile.getHeaders().get("Content-Disposition").get(0).substring(jsonFile.getHeaders().get("Content-Disposition").get(0).lastIndexOf('=') + 1); + addFileUrl = links.get("bucket") + "/" + jsonFileName + "?access_token=" + zenodoToken; + restTemplate.put(addFileUrl, addFileMapRequest); + Files.deleteIfExists(tempJsonFile.toPath()); + + + // Third post call to Zenodo to publish the entry and return the DOI. + publishUrl = links.get("publish") + "?access_token=" + zenodoToken; + } else { + publishUrl = unpublishedUrl + "?access_token=" + zenodoToken; + } +// if (dmp.isPublic()) { + Map publishResponce = restTemplate.postForObject(publishUrl, "", Map.class); + finalDoi = (String) publishResponce.get("conceptdoi"); +// } + } else { + Map editResponce = restTemplate.postForObject(links.get("edit") + "?access_token=" + zenodoToken, "", Map.class); + restTemplate.put(links.get("self") + "?access_token=" + zenodoToken, request); + Map publishResponce = restTemplate.postForObject(links.get("publish") + "?access_token=" + zenodoToken, "", Map.class); + finalDoi = (String) publishResponce.get("conceptdoi"); + } + + return finalDoi; + } catch (HttpClientErrorException | HttpServerErrorException ex) { + Map parsedException = objectMapper.readValue(ex.getResponseBodyAsString(), HashMap.class); + throw new IOException(parsedException.get("message"), ex); + } + + } + + @Override + public RepositoryDepositConfiguration getConfiguration() { + RepositoryDepositConfiguration conf = new RepositoryDepositConfiguration(); + conf.setDepositAccountStatus(BothWaysDeposit.getValue()); + conf.setRepositoryId("Zenodo"); + conf.setAccessToken("pcqw4LGLrhp17FF5GoXNCXakkEi82ThchZw7Tk4qh74VrDE3EmliG3UlhYtd"); + conf.setRepositoryUrl("https://sandbox.zenodo.org/api/"); + conf.setRepositoryLoginAccessTokenUrl("https://sandbox.zenodo.org/oauth/token"); + conf.setRepositoryLoginClientId("hEmVRNc1OzRmWyi2GDR3XVKbhG3OtfJXLXkkOGXx"); + conf.setRepositoryLoginClientSecret("7VSU0NjiAg0P3mv14wemMYy2XhvlmV6F7xoszxPH4ZDx98v8FdMpBbxlncqr"); + conf.setRepositoryLoginRedirectUri("http://localhost:4200/login/external/zenodo"); + return conf; + } + + @Override + public String authenticate(String code){ + + RepositoryDepositConfiguration conf = this.getConfiguration(); + + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("client_id", conf.getRepositoryLoginClientId()); + map.add("client_secret", conf.getRepositoryLoginClientSecret()); + map.add("grant_type", "authorization_code"); + map.add("code", code); + map.add("redirect_uri", conf.getRepositoryLoginRedirectUri()); + HttpEntity> request = new HttpEntity<>(map, headers); + + try { + Map values = restTemplate.postForObject(conf.getRepositoryLoginAccessTokenUrl(), request, Map.class); + //ZenodoResponseToken zenodoResponseToken = new ZenodoResponseToken(); + Map user = (Map) values.get("user"); +// zenodoResponseToken.setUserId((String) user.get("id")); +// zenodoResponseToken.setEmail((String) user.get("email")); +// zenodoResponseToken.setExpiresIn((Integer) values.get("expires_in")); +// zenodoResponseToken.setAccessToken((String) values.get("access_token")); +// zenodoResponseToken.setRefreshToken((String) values.get("refresh_token")); + + //return zenodoResponseToken; + return (String) values.get("access_token"); + } catch (HttpClientErrorException ex) { + logger.error(ex.getResponseBodyAsString(), ex); + } + + return null; + } + + private String getUnpublishedDOI(String zenodoUrl, String DOI, String token, Integer version) { + try { + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setContentType(MediaType.APPLICATION_JSON); + Map createResponse = null; + LinkedHashMap links = null; + LinkedHashMap metadata = null; + String listUrl = zenodoUrl + "deposit/depositions" + "?q=conceptdoi:\"" + DOI + "\"&access_token=" + token; + ResponseEntity listResponses = restTemplate.getForEntity(listUrl, Map[].class); + createResponse = listResponses.getBody()[0]; + metadata = (LinkedHashMap) createResponse.get("metadata"); + links = (LinkedHashMap) createResponse.get("links"); + + if (metadata.get("version").equals(version.toString())) { + return links.get("publish"); + } else { + return null; + } + }catch (Exception e) { + logger.warn(e.getMessage(), e); + return null; + } + } +} diff --git a/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/mapper/DMPToZenodoMapper.java b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/mapper/DMPToZenodoMapper.java new file mode 100644 index 000000000..efc037599 --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/mapper/DMPToZenodoMapper.java @@ -0,0 +1,119 @@ +package eu.eudat.depositinterface.zenodorepository.mapper; + +import eu.eudat.depositinterface.models.DMPDepositModel; +import eu.eudat.depositinterface.models.OrganisationDepositModel; +import eu.eudat.depositinterface.models.UserDMPDepositModel; +import eu.eudat.depositinterface.zenodorepository.config.DOIFunder; +import eu.eudat.depositinterface.zenodorepository.models.*; + +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; + +public class DMPToZenodoMapper { + + public static ZenodoDeposit fromDMP(DMPDepositModel dmp, String zenodoCommunity, String zenodoAffiliation, String domain, List doiFunders) { + Map extraProperties = dmp.getExtraProperties() != null ? new org.json.JSONObject(dmp.getExtraProperties()).toMap() : new HashMap<>(); + ZenodoDeposit deposit = new ZenodoDeposit(); + deposit.setMetadata(new ZenodoDepositMetadata()); + deposit.getMetadata().setTitle(dmp.getLabel()); + deposit.getMetadata().setUploadType("publication"); + deposit.getMetadata().setPublicationType("datamanagementplan"); + deposit.getMetadata().setDescription((dmp.getDescription() != null && !dmp.getDescription().isEmpty() ? dmp.getDescription() : "

")); + deposit.getMetadata().setVersion(String.valueOf(dmp.getVersion())); + if(zenodoCommunity != null && !zenodoAffiliation.isEmpty()) { + ZenodoComunity community = new ZenodoComunity(); + community.setIdentifier(zenodoCommunity); + deposit.getMetadata().setCommunities(Collections.singletonList(community)); + } + if (extraProperties.get("visible") == null) { + deposit.getMetadata().setAccessRight(ZenodoAccessRight.RESTRICTED); + deposit.getMetadata().setAccessConditions(""); + } else { + if (((Boolean) extraProperties.get("visible"))) { + Instant publicationDate = Instant.parse(extraProperties.get("publicDate").toString()); + if (publicationDate.isBefore(Instant.now())) { + deposit.getMetadata().setAccessRight(ZenodoAccessRight.OPEN); + } else { + deposit.getMetadata().setAccessRight(ZenodoAccessRight.EMBARGOED); + deposit.getMetadata().setEmbargoDate(publicationDate.toString()); + } + + if (extraProperties.get("license") != null) { + deposit.getMetadata().setLicense(((Map) extraProperties.get("license")).get("pid").toString()); + } + } else { + deposit.getMetadata().setAccessRight(ZenodoAccessRight.RESTRICTED); + deposit.getMetadata().setAccessConditions(""); + } + } + if (dmp.isPublic()) { + ZenodoRelator relator = new ZenodoRelator(); + relator.setIdentifier(domain + "/external/zenodo/" + dmp.getId().toString()); + relator.setRelation("isIdenticalTo"); + deposit.getMetadata().setRelatedIdentifiers(Collections.singletonList(relator)); + } + deposit.getMetadata().setContributors(new LinkedList<>()); + List contributors = dmp.getUsers().stream().map(userDMP -> { + ZenodoContributor contributor = new ZenodoContributor(); + contributor.setName(userDMP.getUser().getName()); + contributor.setType("ProjectMember"); + if (dmp.getOrganisations() != null && !dmp.getOrganisations().isEmpty()) { + contributor.setAffiliation(dmp.getOrganisations() + .stream().map(OrganisationDepositModel::getLabel).collect(Collectors.joining(", "))); + } else { + if(zenodoAffiliation != null && !zenodoAffiliation.isEmpty()) { + contributor.setAffiliation(zenodoAffiliation); + } + } + return contributor; + }).collect(Collectors.toList()); + + List researchers = dmp.getResearchers().stream().map(researcher -> { + ZenodoContributor contributor = new ZenodoContributor(); + contributor.setName(researcher.getLabel()); + contributor.setType("Researcher"); + String referenceHead = researcher.getReference().split(":")[0]; + String referenceTail = researcher.getReference().replace(referenceHead + ":", ""); + contributor.setAffiliation(referenceHead); + if (referenceHead.equalsIgnoreCase("ORCID")) { + contributor.setOrcid(referenceTail); + } + return contributor; + }).collect(Collectors.toList()); + + deposit.getMetadata().getContributors().addAll(contributors); + deposit.getMetadata().getContributors().addAll(researchers); + + if (dmp.getGrant().getReference() == null) { + dmp.getGrant().setReference("dmp:" + dmp.getGrant().getId()); + } + String grantReferenceHead = dmp.getGrant().getReference().split(":")[0]; + if (grantReferenceHead.equals("openaire")) { + String grantReferenceTail = dmp.getGrant().getReference().split(":")[3]; + DOIFunder doiFunder = doiFunders.stream() + .filter(doiFunder1 -> dmp.getGrant().getFunder().getLabel().contains(doiFunder1.getFunder()) || doiFunder1.getFunder().contains(dmp.getGrant().getFunder().getLabel())) + .findFirst().orElse(null); + if (doiFunder != null) { + String finalId = doiFunder.getDOI() + "::" + grantReferenceTail; + ZenodoGrant grant = new ZenodoGrant(); + grant.setId(finalId); + deposit.getMetadata().setGrants(Collections.singletonList(grant)); + } + } + ZenodoContributor creator = new ZenodoContributor(); + creator.setName(dmp.getUsers().stream().filter(userDMP -> userDMP.getRole().equals(UserDMPDepositModel.UserDMPRoles.OWNER.getValue())).findFirst().get().getUser().getName()); + if (dmp.getOrganisations() != null && !dmp.getOrganisations().isEmpty()) { + creator.setAffiliation(dmp.getOrganisations() + .stream().map(OrganisationDepositModel::getLabel).collect(Collectors.joining(", "))); + } else { + if(zenodoAffiliation != null && !zenodoAffiliation.isEmpty()) { + creator.setAffiliation(zenodoAffiliation); + } + } + deposit.getMetadata().setCreators(Collections.singletonList(creator)); + + return deposit; + } +} + diff --git a/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoAccessRight.java b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoAccessRight.java new file mode 100644 index 000000000..0cf6cd4b6 --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoAccessRight.java @@ -0,0 +1,18 @@ +package eu.eudat.depositinterface.zenodorepository.models; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum ZenodoAccessRight { + RESTRICTED("restricted"), EMBARGOED("embargoed"), OPEN("open"); + + private final String value; + + ZenodoAccessRight(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoComunity.java b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoComunity.java new file mode 100644 index 000000000..a04a19c17 --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoComunity.java @@ -0,0 +1,19 @@ +package eu.eudat.depositinterface.zenodorepository.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ZenodoComunity { + + private String identifier; + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } +} diff --git a/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoContributor.java b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoContributor.java new file mode 100644 index 000000000..c172d5220 --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoContributor.java @@ -0,0 +1,45 @@ +package eu.eudat.depositinterface.zenodorepository.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ZenodoContributor { + private String name; + private String type; + private String affiliation; + private String orcid; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getAffiliation() { + return affiliation; + } + + public void setAffiliation(String affiliation) { + this.affiliation = affiliation; + } + + public String getOrcid() { + return orcid; + } + + public void setOrcid(String orcid) { + this.orcid = orcid; + } +} diff --git a/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoDeposit.java b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoDeposit.java new file mode 100644 index 000000000..c27c2530f --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoDeposit.java @@ -0,0 +1,19 @@ +package eu.eudat.depositinterface.zenodorepository.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ZenodoDeposit { + + private ZenodoDepositMetadata metadata; + + public ZenodoDepositMetadata getMetadata() { + return metadata; + } + + public void setMetadata(ZenodoDepositMetadata metadata) { + this.metadata = metadata; + } +} diff --git a/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoDepositMetadata.java b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoDepositMetadata.java new file mode 100644 index 000000000..041f9623e --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoDepositMetadata.java @@ -0,0 +1,158 @@ +package eu.eudat.depositinterface.zenodorepository.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ZenodoDepositMetadata { + + private String title; + + @JsonProperty("upload_type") + private String uploadType; + + @JsonProperty("publication_type") + private String publicationType; + + private String description; + + private String version; + + private List communities; + + @JsonProperty("access_right") + private ZenodoAccessRight accessRight; + + @JsonProperty("access_conditions") + private String accessConditions; + + @JsonProperty("embargo_date") + private String embargoDate; + + private String license; + + @JsonProperty("related_identifiers") + private List relatedIdentifiers; + + private List contributors; + + private List grants; + + private List creators; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getUploadType() { + return uploadType; + } + + public void setUploadType(String uploadType) { + this.uploadType = uploadType; + } + + public String getPublicationType() { + return publicationType; + } + + public void setPublicationType(String publicationType) { + this.publicationType = publicationType; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public List getCommunities() { + return communities; + } + + public void setCommunities(List communities) { + this.communities = communities; + } + + public ZenodoAccessRight getAccessRight() { + return accessRight; + } + + public void setAccessRight(ZenodoAccessRight accessRight) { + this.accessRight = accessRight; + } + + public String getAccessConditions() { + return accessConditions; + } + + public void setAccessConditions(String accessConditions) { + this.accessConditions = accessConditions; + } + + public String getEmbargoDate() { + return embargoDate; + } + + public void setEmbargoDate(String embargoDate) { + this.embargoDate = embargoDate; + } + + public String getLicense() { + return license; + } + + public void setLicense(String license) { + this.license = license; + } + + public List getRelatedIdentifiers() { + return relatedIdentifiers; + } + + public void setRelatedIdentifiers(List relatedIdentifiers) { + this.relatedIdentifiers = relatedIdentifiers; + } + + public List getContributors() { + return contributors; + } + + public void setContributors(List contributors) { + this.contributors = contributors; + } + + public List getGrants() { + return grants; + } + + public void setGrants(List grants) { + this.grants = grants; + } + + public List getCreators() { + return creators; + } + + public void setCreators(List creators) { + this.creators = creators; + } +} diff --git a/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoGrant.java b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoGrant.java new file mode 100644 index 000000000..d37c6575e --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoGrant.java @@ -0,0 +1,18 @@ +package eu.eudat.depositinterface.zenodorepository.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ZenodoGrant { + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoRelator.java b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoRelator.java new file mode 100644 index 000000000..8196f63a3 --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/java/eu/eudat/depositinterface/zenodorepository/models/ZenodoRelator.java @@ -0,0 +1,28 @@ +package eu.eudat.depositinterface.zenodorepository.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ZenodoRelator { + + private String identifier; + private String relation; + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public String getRelation() { + return relation; + } + + public void setRelation(String relation) { + this.relation = relation; + } +} diff --git a/dmp-backend/zenodoRepository/src/main/resources/DOI_Funder.json b/dmp-backend/zenodoRepository/src/main/resources/DOI_Funder.json new file mode 100644 index 000000000..9f1c4856d --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/resources/DOI_Funder.json @@ -0,0 +1,70 @@ +[ + { + "Funder": "Australian Research Council", + "DOI": "10.13039/501100000923" + }, + { + "Funder": "Austrian Science Fund", + "DOI": "10.13039/501100002428" + }, + { + "Funder": "European Commission", + "DOI": "10.13039/501100000780" + }, + { + "Funder": "European Environment Agency", + "DOI": "10.13039/501100000806" + }, + { + "Funder": "Academy of Finland", + "DOI": "10.13039/501100002341" + }, + { + "Funder": "Hrvatska Zaklada za Znanost", + "DOI": "10.13039/501100004488" + }, + { + "Funder": "Fundação para a Ciência e a Tecnologia", + "DOI": "10.13039/501100001871" + }, + { + "Funder": "Ministarstvo Prosvete, Nauke i Tehnološkog Razvoja", + "DOI": "10.13039/501100004564" + }, + { + "Funder": "Ministarstvo Znanosti, Obrazovanja i Sporta", + "DOI": "10.13039/501100006588" + }, + { + "Funder": "National Health and Medical Research Council", + "DOI": "10.13039/501100000925" + }, + { + "Funder": "National Institutes of Health", + "DOI": "10.13039/100000002" + }, + { + "Funder": "National Science Foundation", + "DOI": "10.13039/100000001" + }, + { + "Funder": "Nederlandse Organisatie voor Wetenschappelijk Onderzoek", + "DOI": "10.13039/501100003246" + }, + { + "Funder": "Research Councils", + "DOI": "10.13039/501100000690" + }, + { + "Funder": "Schweizerischer Nationalfonds zur Förderung der wissenschaftlichen Forschung", + "DOI": "10.13039/501100001711" + }, + { + "Funder": "Science Foundation Ireland", + "DOI": "10.13039/501100001602" + }, + { + "Funder": "Wellcome Trust", + "DOI": "10.13039/100004440" + } +] \ No newline at end of file diff --git a/dmp-backend/zenodoRepository/src/main/resources/application.properties b/dmp-backend/zenodoRepository/src/main/resources/application.properties new file mode 100644 index 000000000..51594e272 --- /dev/null +++ b/dmp-backend/zenodoRepository/src/main/resources/application.properties @@ -0,0 +1 @@ +configuration.doi_funder=DOI_Funder.json diff --git a/dmp-frontend/src/app/core/common/enum/deposit-configuration-status.ts b/dmp-frontend/src/app/core/common/enum/deposit-configuration-status.ts new file mode 100644 index 000000000..fc5787572 --- /dev/null +++ b/dmp-frontend/src/app/core/common/enum/deposit-configuration-status.ts @@ -0,0 +1,5 @@ +export enum DepositConfigurationStatus { + System = 0, + User = 1, + BothSystemAndUser = 2 +} \ No newline at end of file diff --git a/dmp-frontend/src/app/core/model/deposit/deposit-configuration.ts b/dmp-frontend/src/app/core/model/deposit/deposit-configuration.ts new file mode 100644 index 000000000..1e2ef02e1 --- /dev/null +++ b/dmp-frontend/src/app/core/model/deposit/deposit-configuration.ts @@ -0,0 +1,11 @@ +import { DepositConfigurationStatus } from "@app/core/common/enum/deposit-configuration-status"; + +export class DepositConfigurationModel { + depositAccountStatus: DepositConfigurationStatus; + repositoryId: string; + accessToken: string; + repositoryUrl: string; + repositoryLoginAccessTokenUrl: string; + repositoryLoginClientId: string; + repositoryLoginClientSecret: string; +} diff --git a/dmp-frontend/src/app/core/model/deposit/deposit-request.ts b/dmp-frontend/src/app/core/model/deposit/deposit-request.ts new file mode 100644 index 000000000..daf62d32c --- /dev/null +++ b/dmp-frontend/src/app/core/model/deposit/deposit-request.ts @@ -0,0 +1,5 @@ +export class DepositRequest { + repositoryId: string; + dmpId: string; + accessToken: string; +} \ No newline at end of file diff --git a/dmp-frontend/src/app/core/services/deposit-repositories/deposit-repositories.service.ts b/dmp-frontend/src/app/core/services/deposit-repositories/deposit-repositories.service.ts index dd6ebfde2..2c9becc6c 100644 --- a/dmp-frontend/src/app/core/services/deposit-repositories/deposit-repositories.service.ts +++ b/dmp-frontend/src/app/core/services/deposit-repositories/deposit-repositories.service.ts @@ -1,5 +1,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { DepositConfigurationModel } from '@app/core/model/deposit/deposit-configuration'; +import { DepositRequest } from '@app/core/model/deposit/deposit-request'; import { Observable } from 'rxjs'; import { ConfigurationService } from '../configuration/configuration.service'; import { BaseHttpService } from '../http/base-http.service'; @@ -14,7 +16,19 @@ export class DepositRepositoriesService { this.actionUrl = configurationService.server + 'deposit/'; } - getAvailableRepos(): Observable { - return this.http.get(this.actionUrl + 'repos/', { headers: this.headers }); + getAvailableRepos(): Observable { + return this.http.get(this.actionUrl + 'repos', { headers: this.headers }); + } + + getAccessToken(repository: string, code: string): Observable { + return this.http.get(this.actionUrl + 'getAccessToken?repository=' + repository + '&code=' + code, { headers: this.headers }); + } + + createDoi(repositoryId: string, dmpId: string, accessToken: string): Observable { + const depositRequest = new DepositRequest(); + depositRequest.repositoryId = repositoryId; + depositRequest.dmpId = dmpId; + depositRequest.accessToken = accessToken; + return this.http.post(this.actionUrl + 'createDoi', depositRequest, { headers: this.headers }); } } diff --git a/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.html b/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.html index 8dbb6dcd3..407452047 100644 --- a/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.html +++ b/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.html @@ -5,21 +5,17 @@
- - - - {{ repo }} - - - + + +
-
{{ 'DMP-FINALISE-DIALOG.EMPTY' | translate }}
+
No publishing repositories so far
-
+
\ No newline at end of file diff --git a/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.ts b/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.ts index 04247b533..2fb0eac2d 100644 --- a/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.ts +++ b/dmp-frontend/src/app/ui/dmp/editor/dmp-deposit-dialog/dmp-deposit-dialog.component.ts @@ -1,6 +1,17 @@ import { Component, Inject, OnInit } from '@angular/core'; import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { DepositConfigurationStatus } from '@app/core/common/enum/deposit-configuration-status'; +import { DepositConfigurationModel } from '@app/core/model/deposit/deposit-configuration'; +import { DmpOverviewModel } from '@app/core/model/dmp/dmp-overview'; +import { DepositRepositoriesService } from '@app/core/services/deposit-repositories/deposit-repositories.service'; +import { DmpService } from '@app/core/services/dmp/dmp.service'; +import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service'; +import { Oauth2DialogService } from '@app/ui/misc/oauth2-dialog/service/oauth2-dialog.service'; import { BaseComponent } from '@common/base/base.component'; +import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component'; +import { MultipleChoiceDialogComponent } from '@common/modules/multiple-choice-dialog/multiple-choice-dialog.component'; +import { TranslateService } from '@ngx-translate/core'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-dmp-deposit-dialog', @@ -9,15 +20,23 @@ import { BaseComponent } from '@common/base/base.component'; }) export class DmpDepositDialogComponent extends BaseComponent implements OnInit { - inputRepos: string[]; + inputRepos: DepositConfigurationModel[]; outputRepos: string[]; + dmp: DmpOverviewModel; constructor( + private dmpService: DmpService, + private depositRepositoriesService: DepositRepositoriesService, public dialogRef: MatDialogRef, + private dialog: MatDialog, + private language: TranslateService, + private uiNotificationService: UiNotificationService, + private oauth2DialogService: Oauth2DialogService, @Inject(MAT_DIALOG_DATA) public data: any ) { super(); - this.inputRepos = data['depositRepos']; + this.inputRepos = data['depositRepos'][0]; + this.dmp = data['depositRepos'][1]; this.outputRepos = []; } @@ -32,4 +51,75 @@ export class DmpDepositDialogComponent extends BaseComponent implements OnInit { this.dialogRef.close(this.outputRepos); } + deposit(repo: DepositConfigurationModel) { + + if(repo.depositAccountStatus == DepositConfigurationStatus.BothSystemAndUser){ + + const dialogRef = this.dialog.open(MultipleChoiceDialogComponent, { + maxWidth: '600px', + restoreFocus: false, + data: { + message: 'Which account would you like to use?',//this.language.instant('GENERAL.ERRORS.HTTP-REQUEST-ERROR'), + titles: ['Login with ' + repo.repositoryId/*this.language.instant('DMP-OVERVIEW.MULTIPLE-DIALOG.ZENODO-LOGIN')*/, this.language.instant('DMP-OVERVIEW.MULTIPLE-DIALOG.USE-DEFAULT')] + } + }); + dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { + switch (result) { + case 0: + this.showOauth2Dialog('https://sandbox.zenodo.org/oauth/authorize'+ '?client_id=' + repo.repositoryLoginClientId + + '&response_type=code&scope=deposit:write+deposit:actions+user:email&state=astate&redirect_uri=' + + 'http://localhost:4200/login/external/zenodo', repo, this.dmp); + break; + case 1: + // this.dmpService.getDoi(dmp.id) + // .pipe(takeUntil(this._destroyed)) + // .subscribe( + // complete => { + // this.onDOICallbackSuccess(); + // this.dmp.doi = complete; + // }, + // error => this.onDeleteCallbackError(error) + // ); + break; + } + }); + + } + } + + onDOICallbackSuccess(): void { + this.uiNotificationService.snackBarNotification(this.language.instant('DMP-EDITOR.SNACK-BAR.SUCCESSFUL-DOI'), SnackBarNotificationLevel.Success); + } + + onDeleteCallbackError(error) { + this.uiNotificationService.snackBarNotification(error.error.message ? this.language.instant(error.error.message) : this.language.instant('GENERAL.SNACK-BAR.UNSUCCESSFUL-DELETE'), SnackBarNotificationLevel.Error); + } + + showOauth2Dialog(url: string, repo: DepositConfigurationModel, dmp: DmpOverviewModel) { + this.oauth2DialogService.login(url) + .pipe(takeUntil(this._destroyed)) + .subscribe(result => { + if (result !== undefined) { + if (result.oauthCode !== undefined && result.oauthCode !== null) { + this.depositRepositoriesService.getAccessToken(repo.repositoryId, result.oauthCode) + .pipe(takeUntil(this._destroyed)) + .subscribe(token => { + console.log(token); + this.depositRepositoriesService.createDoi(repo.repositoryId, dmp.id, token) + .pipe(takeUntil(this._destroyed)) + .subscribe(doi =>{ + console.log(doi); + }); + }); + // this.userService.registerDOIToken(result.oauthCode, this.configurationService.app + 'oauth2') + // .pipe(takeUntil(this._destroyed)) + // .subscribe(() => { + // this.hasDOIToken = true; + // this.showConfirmationDOIDialog(dmp); + // }); + } + } + }); + } + } diff --git a/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.ts b/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.ts index 63c3a8231..bf561abbc 100644 --- a/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.ts +++ b/dmp-frontend/src/app/ui/dmp/overview/dmp-overview.component.ts @@ -43,6 +43,7 @@ import { MatomoService } from '@app/core/services/matomo/matomo-service'; import { PopupNotificationDialogComponent } from '@app/library/notification/popup/popup-notification.component'; import { DepositRepositoriesService } from '@app/core/services/deposit-repositories/deposit-repositories.service'; import { DmpDepositDialogComponent } from '../editor/dmp-deposit-dialog/dmp-deposit-dialog.component'; +import { DepositConfigurationModel } from '@app/core/model/deposit/deposit-configuration'; @Component({ selector: 'app-dmp-overview', @@ -70,7 +71,7 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { @ViewChild('doi') doi: ElementRef; - depositRepos: String[]; + depositRepos: DepositConfigurationModel[]; formGroup: FormGroup; @@ -589,18 +590,24 @@ export class DmpOverviewComponent extends BaseComponent implements OnInit { .subscribe( repos => { this.depositRepos = repos; + const dialogRef = this.dialog.open(DmpDepositDialogComponent, { + maxWidth: '600px', + restoreFocus: false, + autoFocus: false, + data: { + depositRepos: [this.depositRepos, this.dmp], + message: "Select repositories to deposit",//this.language.instant('GENERAL.CONFIRMATION-DIALOG.FINALIZE-ITEM'), + confirmButton: "Proceed to authentication",//this.language.instant('DMP-FINALISE-DIALOG.SUBMIT'), + cancelButton: "Cancel",//this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL'), + } + }); + dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe((result: string[]) => { + if (result) { + this.dmp.doi = result[0]; + } + }); } ); - const dialogRef = this.dialog.open(DmpDepositDialogComponent, { - restoreFocus: false, - autoFocus: false, - data: { - depositRepos: this.depositRepos, - message: "Select repositories to deposit",//this.language.instant('GENERAL.CONFIRMATION-DIALOG.FINALIZE-ITEM'), - confirmButton: "Proceed to authentication",//this.language.instant('DMP-FINALISE-DIALOG.SUBMIT'), - cancelButton: "Cancel",//this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL'), - } - }); } finalize(dmp: DmpOverviewModel) {