package eu.eudat.depositinterface.zenodorepository.interfaces; import com.fasterxml.jackson.databind.ObjectMapper; import eu.eudat.depositinterface.models.DMPDepositModel; import eu.eudat.depositinterface.models.FileEnvelope; 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.*; 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.*; import java.nio.file.Files; import java.util.*; import java.util.stream.Collectors; @Component public class ZenodoDeposit implements RepositoryDeposit { private static final Logger logger = LoggerFactory.getLogger(ZenodoDeposit.class); private static final ObjectMapper objectMapper = new ObjectMapper(); private final ConfigLoader configLoader; private final Environment environment; @Autowired public ZenodoDeposit(ConfigLoader configLoader, Environment environment){ this.configLoader = configLoader; this.environment = environment; } @Override public String deposit(String repositoryId, DMPDepositModel dmpDepositModel, String zenodoToken) throws Exception { RepositoryDepositConfiguration conf = this.getConfiguration().stream().filter(x -> x.getRepositoryId().equals(repositoryId)).findFirst().orElse(null); if(conf != null) { 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); ZenodoConfig zenodoConfig = this.configLoader.getZenodoConfig().stream().filter(x -> x.getRepositoryId().equals(repositoryId)).findFirst().orElse(null); eu.eudat.depositinterface.zenodorepository.models.ZenodoDeposit deposit = DMPToZenodoMapper.fromDMP(dmpDepositModel, zenodoConfig); 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; logger.debug("listUrl = " + listUrl); ResponseEntity listResponses = restTemplate.getForEntity(listUrl, Map[].class); createResponse = listResponses.getBody()[0]; logger.debug("createResponse-previousDoi:"); logger.debug(objectMapper.writeValueAsString(createResponse)); links = (LinkedHashMap) createResponse.get("links"); //Second, make the new version (not in the links?) String newVersionUrl = links.get("latest_draft") + "/actions/newversion" + "?access_token=" + zenodoToken; logger.debug("new version url: " + newVersionUrl); createResponse = restTemplate.postForObject(newVersionUrl, null, Map.class); logger.debug("createResponse-newVersion:"); logger.debug(objectMapper.writeValueAsString(createResponse)); 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); logger.debug("createResponse-latestDraft:"); logger.debug(objectMapper.writeValueAsString(createResponse)); 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. FileEnvelope pdfEnvelope = dmpDepositModel.getPdfFile(); FileSystemResource fileSystemResource = new FileSystemResource(pdfEnvelope.getFile()); HttpHeaders fileHeaders = new HttpHeaders(); fileHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); HttpEntity addFileMapRequest = new HttpEntity<>(fileSystemResource, fileHeaders); String addFileUrl = links.get("bucket") + "/" + pdfEnvelope.getFilename() + "?access_token=" + zenodoToken; restTemplate.put(addFileUrl, addFileMapRequest); FileEnvelope rdaJsonEnvelope = dmpDepositModel.getRdaJsonFile(); HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.setContentLength(rdaJsonEnvelope.getFile().length()); responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); responseHeaders.set("Content-Disposition", "attachment;filename=" + rdaJsonEnvelope.getFilename()); responseHeaders.set("Access-Control-Expose-Headers", "Content-Disposition"); responseHeaders.get("Access-Control-Expose-Headers").add("Content-Type"); byte[] content = Files.readAllBytes(rdaJsonEnvelope.getFile().toPath()); ResponseEntity jsonFile = new ResponseEntity<>(content, responseHeaders, HttpStatus.OK); UUID jsonFileUUID = UUID.randomUUID(); File tempJsonFile = new File(this.environment.getProperty("zenodo_plugin.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); } } return null; } private String publish(String publishUrl){ RestTemplate restTemplate = new RestTemplate(); Map publishResponce = restTemplate.postForObject(publishUrl, "", Map.class); return (String) publishResponce.get("conceptdoi"); } @Override public List getConfiguration() { List zenodoConfigs = this.configLoader.getZenodoConfig(); return (zenodoConfigs != null) ? zenodoConfigs.stream().map(ZenodoConfig::toRepoConfig).collect(Collectors.toList()) : null; } @Override public String authenticate(String repositoryId, String code){ RepositoryDepositConfiguration conf = this.getConfiguration().stream().filter(x -> x.getRepositoryId().equals(repositoryId)).findFirst().orElse(null); if(conf != null) { 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; } return null; } @Override public String getLogo(String repositoryId) { RepositoryDepositConfiguration conf = this.getConfiguration().stream().filter(x -> x.getRepositoryId().equals(repositoryId)).findFirst().orElse(null); if(conf != null) { if(conf.isHasLogo()){ byte[] logo = this.configLoader.getLogo(repositoryId); return (logo != null && logo.length != 0) ? Base64.getEncoder().encodeToString(logo) : null; } } 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; } } }