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.config.ZenodoConfig; 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.env.Environment; 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.*; @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; private Environment environment; @Autowired public ZenodoDeposit(ConfigLoader configLoader, Environment environment){ this.configLoader = configLoader; this.environment = environment; } @Override public String deposit(DMPDepositModel dmpDepositModel, String zenodoToken) throws Exception { RepositoryDepositConfiguration conf = this.getConfiguration(); if(zenodoToken == null){ zenodoToken = conf.getAccessToken(); } 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"); } 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"); //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 (unpublishedUrl == null) { // Second step, add the file to the entry. File pdfFile = dmpDepositModel.getPdfFile(); String fileName = dmpDepositModel.getPdfFileName(); 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(this.environment.getProperty("storage.temp") + jsonFileUUID + ".json"); 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()); if(dmpDepositModel.getSupportingFilesZip() != null) { File supportinFilesZip = dmpDepositModel.getSupportingFilesZip(); String supportinFilesZipName = dmpDepositModel.getSupportingFilesZip().getName(); fileSystemResource = new FileSystemResource(supportinFilesZip); addFileMapRequest = new HttpEntity<>(fileSystemResource, null); addFileUrl = links.get("bucket") + "/" + supportinFilesZipName + "?access_token=" + zenodoToken; restTemplate.put(addFileUrl, addFileMapRequest); } // 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; } return this.publish(publishUrl); } catch (HttpClientErrorException | HttpServerErrorException ex) { Map parsedException = objectMapper.readValue(ex.getResponseBodyAsString(), HashMap.class); throw new IOException(parsedException.get("message"), ex); } } private String publish(String publishUrl){ RestTemplate restTemplate = new RestTemplate(); Map publishResponce = restTemplate.postForObject(publishUrl, "", Map.class); return (String) publishResponce.get("conceptdoi"); } @Override public RepositoryDepositConfiguration getConfiguration() { ZenodoConfig zenodoConfig = this.configLoader.getZenodoConfig(); return zenodoConfig.toRepoConfig(); } @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.getRepositoryClientId()); map.add("client_secret", conf.getRepositoryClientSecret()); map.add("grant_type", "authorization_code"); map.add("code", code); map.add("redirect_uri", conf.getRedirectUri()); HttpEntity> request = new HttpEntity<>(map, headers); try { Map values = restTemplate.postForObject(conf.getRepositoryAccessTokenUrl(), 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; } } }