From 0c1d0a149bfd3fd116860e53b73be208f85b7580 Mon Sep 17 00:00:00 2001
From: Nicolas Rod <Nicolas.Rod@unige.ch>
Date: Thu, 23 Jan 2025 09:32:01 +0100
Subject: [PATCH 1/4] feat(ORCID): solidify OrcidSynchronization

---
 .../unige/solidify/auth/model/OrcidInfo.java  |  10 +-
 .../solidify/model/OrcidSynchronization.java  | 120 ++++++++++++
 .../unige/solidify/model/PersonWithOrcid.java |   9 +-
 .../business/OrcidSynchronizationService.java |  64 +++++++
 .../solidify/controller/OrcidController.java  |  18 +-
 .../OrcidSynchronizationRepository.java       |  43 +++++
 .../solidify/service/OrcidClientService.java  | 179 ++++++++++++++++++
 ...cidService.java => OrcidOAuthService.java} | 141 +-------------
 .../OrcidSynchronizationSpecification.java    |  44 +++++
 9 files changed, 476 insertions(+), 152 deletions(-)
 create mode 100644 solidify-orcid-model/src/main/java/ch/unige/solidify/model/OrcidSynchronization.java
 create mode 100644 solidify-orcid/src/main/java/ch/unige/solidify/business/OrcidSynchronizationService.java
 create mode 100644 solidify-orcid/src/main/java/ch/unige/solidify/repository/OrcidSynchronizationRepository.java
 create mode 100644 solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidClientService.java
 rename solidify-orcid/src/main/java/ch/unige/solidify/service/{OrcidService.java => OrcidOAuthService.java} (56%)
 create mode 100644 solidify-orcid/src/main/java/ch/unige/solidify/specification/OrcidSynchronizationSpecification.java

diff --git a/solidify-authorization-model/src/main/java/ch/unige/solidify/auth/model/OrcidInfo.java b/solidify-authorization-model/src/main/java/ch/unige/solidify/auth/model/OrcidInfo.java
index b645d7369..2399435d6 100644
--- a/solidify-authorization-model/src/main/java/ch/unige/solidify/auth/model/OrcidInfo.java
+++ b/solidify-authorization-model/src/main/java/ch/unige/solidify/auth/model/OrcidInfo.java
@@ -9,12 +9,12 @@
  * it under the terms of the GNU General Public License as
  * published by the Free Software Foundation, either version 2 of the
  * License, or (at your option) any later version.
- * 
+ *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public
  * License along with this program.  If not, see
  * <http://www.gnu.org/licenses/gpl-2.0.html>.
@@ -25,12 +25,12 @@ package ch.unige.solidify.auth.model;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 
-@Schema(description = "ORCID information identify inddividuals.")
+@Schema(description = "ORCID information identify individuals.")
 public interface OrcidInfo {
 
   // ORCID
-  public static final String ORCID_PATTERN = "|[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}|[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{3}X";
-  public static final String ORCID_MESSAGE = "The ORCID should be of the form 1234-1234-1234-1234 or 1234-1234-1234-123X";
+  String ORCID_PATTERN = "|[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}|[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{3}X";
+  String ORCID_MESSAGE = "The ORCID should be of the form 1234-1234-1234-1234 or 1234-1234-1234-123X";
 
   @Schema(description = "The ORCID of the person (Format: xxxx-xxxx-xxxx-xxxx).")
   String getOrcid();
diff --git a/solidify-orcid-model/src/main/java/ch/unige/solidify/model/OrcidSynchronization.java b/solidify-orcid-model/src/main/java/ch/unige/solidify/model/OrcidSynchronization.java
new file mode 100644
index 000000000..1b3234c1d
--- /dev/null
+++ b/solidify-orcid-model/src/main/java/ch/unige/solidify/model/OrcidSynchronization.java
@@ -0,0 +1,120 @@
+/*-
+ * %%----------------------------------------------------------------------------------------------
+ * Solidify Framework - Solidify ORCID Model - OrcidSynchronization.java
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * %----------------------------------------------------------------------------------------------%
+ * Copyright (C) 2017 - 2024 University of Geneva
+ * %----------------------------------------------------------------------------------------------%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-2.0.html>.
+ * ----------------------------------------------------------------------------------------------%%
+ */
+package ch.unige.solidify.model;
+
+import static ch.unige.solidify.SolidifyConstants.DB_ID_LENGTH;
+
+import java.math.BigInteger;
+import java.time.OffsetDateTime;
+import java.util.Objects;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Inheritance;
+import jakarta.persistence.InheritanceType;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+
+import ch.unige.solidify.rest.ResourceNormalized;
+import ch.unige.solidify.util.StringTool;
+
+@Entity
+@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
+public abstract class OrcidSynchronization extends ResourceNormalized {
+
+  @Schema(description = "The resId of the object synchronized with ORCID")
+  @NotNull
+  @Size(max = DB_ID_LENGTH)
+  @Column(length = DB_ID_LENGTH)
+  protected String objectId;
+
+  @Schema(description = "The identifier of the person for whom the object is synchronised")
+  @NotNull
+  @Size(max = DB_ID_LENGTH)
+  @Column(length = DB_ID_LENGTH)
+  protected String personId;
+
+  @Schema(description = "Date on which the item was exported to ORCID profile")
+  @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = StringTool.DATE_TIME_FORMAT)
+  private OffsetDateTime uploadDate;
+
+  @Schema(description = "Date on which the item was imported from ORCID profile")
+  @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = StringTool.DATE_TIME_FORMAT)
+  private OffsetDateTime downloadDate;
+
+  @Schema(description = "The ORCID's work put code")
+  @NotNull
+  private BigInteger putCode;
+
+  public abstract String getPersonId();
+
+  public abstract String getObjectId();
+
+  public OffsetDateTime getUploadDate() {
+    return this.uploadDate;
+  }
+
+  public void setUploadDate(OffsetDateTime uploadDate) {
+    this.uploadDate = uploadDate;
+  }
+
+  public OffsetDateTime getDownloadDate() {
+    return this.downloadDate;
+  }
+
+  public void setDownloadDate(OffsetDateTime downloadDate) {
+    this.downloadDate = downloadDate;
+  }
+
+  public BigInteger getPutCode() {
+    return this.putCode;
+  }
+
+  public void setPutCode(BigInteger putCode) {
+    this.putCode = putCode;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || this.getClass() != o.getClass()) {
+      return false;
+    }
+    if (!super.equals(o)) {
+      return false;
+    }
+    OrcidSynchronization that = (OrcidSynchronization) o;
+    return Objects.equals(this.objectId, that.objectId) && Objects.equals(this.personId, that.personId) && Objects.equals(
+            this.uploadDate, that.uploadDate) && Objects.equals(this.downloadDate, that.downloadDate) && Objects.equals(this.putCode,
+            that.putCode);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(super.hashCode(), this.objectId, this.personId, this.uploadDate, this.downloadDate, this.putCode);
+  }
+}
diff --git a/solidify-orcid-model/src/main/java/ch/unige/solidify/model/PersonWithOrcid.java b/solidify-orcid-model/src/main/java/ch/unige/solidify/model/PersonWithOrcid.java
index eee8ce020..c60ffebad 100644
--- a/solidify-orcid-model/src/main/java/ch/unige/solidify/model/PersonWithOrcid.java
+++ b/solidify-orcid-model/src/main/java/ch/unige/solidify/model/PersonWithOrcid.java
@@ -9,12 +9,12 @@
  * it under the terms of the GNU General Public License as
  * published by the Free Software Foundation, either version 2 of the
  * License, or (at your option) any later version.
- * 
+ *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public
  * License along with this program.  If not, see
  * <http://www.gnu.org/licenses/gpl-2.0.html>.
@@ -38,4 +38,9 @@ public interface PersonWithOrcid extends OrcidInfo {
   void setVerifiedOrcid(Boolean verifiedOrcid);
 
   void setOrcidToken(OrcidToken orcidToken);
+
+  OrcidToken getOrcidToken();
+
+  @Schema(description = "The full name of the person", accessMode = Schema.AccessMode.READ_ONLY)
+  String getFullName();
 }
diff --git a/solidify-orcid/src/main/java/ch/unige/solidify/business/OrcidSynchronizationService.java b/solidify-orcid/src/main/java/ch/unige/solidify/business/OrcidSynchronizationService.java
new file mode 100644
index 000000000..a01d04448
--- /dev/null
+++ b/solidify-orcid/src/main/java/ch/unige/solidify/business/OrcidSynchronizationService.java
@@ -0,0 +1,64 @@
+/*-
+ * %%----------------------------------------------------------------------------------------------
+ * Solidify Framework - Solidify ORCID - OrcidSynchronizationService.java
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * %----------------------------------------------------------------------------------------------%
+ * Copyright (C) 2017 - 2024 University of Geneva
+ * %----------------------------------------------------------------------------------------------%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-2.0.html>.
+ * ----------------------------------------------------------------------------------------------%%
+ */
+package ch.unige.solidify.business;
+
+import java.math.BigInteger;
+import java.time.OffsetDateTime;
+import java.util.List;
+import java.util.Optional;
+
+import ch.unige.solidify.model.OrcidSynchronization;
+import ch.unige.solidify.repository.OrcidSynchronizationRepository;
+import ch.unige.solidify.service.OrcidOAuthService;
+import ch.unige.solidify.service.ResourceService;
+import ch.unige.solidify.specification.OrcidSynchronizationSpecification;
+import ch.unige.solidify.specification.SolidifySpecification;
+
+public abstract class OrcidSynchronizationService extends ResourceService<OrcidSynchronization> {
+
+  protected OrcidOAuthService orcidOAuthService;
+
+  public OrcidSynchronizationService(OrcidOAuthService orcidOAuthService) {
+    this.orcidOAuthService = orcidOAuthService;
+  }
+
+  public List<OrcidSynchronization> findByObjectId(String aipId) {
+    return ((OrcidSynchronizationRepository) this.itemRepository).findByObjectId(aipId);
+  }
+
+  public List<OrcidSynchronization> findByPersonIdAndObjectId(String personId, String aipId) {
+    return ((OrcidSynchronizationRepository) this.itemRepository).findByPersonIdAndObjectId(personId, aipId);
+  }
+
+  public Optional<OrcidSynchronization> findByPersonIdAndPutCode(String personId, BigInteger putCode) {
+    return ((OrcidSynchronizationRepository) this.itemRepository).findByPersonIdAndPutCode(personId, putCode);
+  }
+
+  protected abstract OrcidSynchronization storeOrcidSynchronization(String personId, BigInteger putCode, String objectId,
+          OffsetDateTime uploadDate);
+
+  @Override
+  public SolidifySpecification<OrcidSynchronization> getSpecification(OrcidSynchronization orcidSynchronization) {
+    return new OrcidSynchronizationSpecification(orcidSynchronization);
+  }
+}
diff --git a/solidify-orcid/src/main/java/ch/unige/solidify/controller/OrcidController.java b/solidify-orcid/src/main/java/ch/unige/solidify/controller/OrcidController.java
index 718294832..14640df54 100644
--- a/solidify-orcid/src/main/java/ch/unige/solidify/controller/OrcidController.java
+++ b/solidify-orcid/src/main/java/ch/unige/solidify/controller/OrcidController.java
@@ -9,12 +9,12 @@
  * it under the terms of the GNU General Public License as
  * published by the Free Software Foundation, either version 2 of the
  * License, or (at your option) any later version.
- * 
+ *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public
  * License along with this program.  If not, see
  * <http://www.gnu.org/licenses/gpl-2.0.html>.
@@ -38,7 +38,7 @@ import ch.unige.solidify.SolidifyConstants;
 import ch.unige.solidify.rest.ActionName;
 import ch.unige.solidify.security.EveryonePermissions;
 import ch.unige.solidify.security.UserPermissions;
-import ch.unige.solidify.service.OrcidService;
+import ch.unige.solidify.service.OrcidOAuthService;
 import ch.unige.solidify.util.StringTool;
 
 @RestController
@@ -47,10 +47,10 @@ import ch.unige.solidify.util.StringTool;
 public class OrcidController {
   private static final String LOCATION_HEADER = "Location";
 
-  private final OrcidService orcidService;
+  private final OrcidOAuthService orcidOAuthService;
 
-  public OrcidController(OrcidService orcidService) {
-    this.orcidService = orcidService;
+  public OrcidController(OrcidOAuthService orcidOAuthService) {
+    this.orcidOAuthService = orcidOAuthService;
   }
 
   @EveryonePermissions
@@ -61,9 +61,9 @@ public class OrcidController {
           @RequestParam String authTransactionId) throws JsonProcessingException {
     String redirectUrl;
     if (!StringTool.isNullOrEmpty(code)) {
-      redirectUrl = this.orcidService.getOrcidAndTokensFromOrcidWebsite(code, authTransactionId);
+      redirectUrl = this.orcidOAuthService.getOrcidAndTokensFromOrcidWebsite(code, authTransactionId);
     } else {
-      redirectUrl = this.orcidService.getAndRemoveOriginUrl(authTransactionId, error, errorDescription);
+      redirectUrl = this.orcidOAuthService.getAndRemoveOriginUrl(authTransactionId, error, errorDescription);
     }
 
     return ResponseEntity.status(HttpStatus.FOUND).header(LOCATION_HEADER, redirectUrl).build();
@@ -72,7 +72,7 @@ public class OrcidController {
   @UserPermissions
   @GetMapping("/" + ActionName.START_ORCID_AUTH)
   public ResponseEntity<String> startOrcidAuth(@RequestParam String originUrl) {
-    final JSONObject jsonObject = this.orcidService.startOrcidAuth(originUrl);
+    final JSONObject jsonObject = this.orcidOAuthService.startOrcidAuth(originUrl);
     return ResponseEntity.ok().body(jsonObject.toString());
   }
 }
diff --git a/solidify-orcid/src/main/java/ch/unige/solidify/repository/OrcidSynchronizationRepository.java b/solidify-orcid/src/main/java/ch/unige/solidify/repository/OrcidSynchronizationRepository.java
new file mode 100644
index 000000000..20f0476a0
--- /dev/null
+++ b/solidify-orcid/src/main/java/ch/unige/solidify/repository/OrcidSynchronizationRepository.java
@@ -0,0 +1,43 @@
+/*-
+ * %%----------------------------------------------------------------------------------------------
+ * Solidify Framework - Solidify ORCID - OrcidSynchronizationRepository.java
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * %----------------------------------------------------------------------------------------------%
+ * Copyright (C) 2017 - 2024 University of Geneva
+ * %----------------------------------------------------------------------------------------------%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-2.0.html>.
+ * ----------------------------------------------------------------------------------------------%%
+ */
+package ch.unige.solidify.repository;
+
+import java.math.BigInteger;
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.stereotype.Repository;
+
+import ch.unige.solidify.controller.OrcidController;
+import ch.unige.solidify.model.OrcidSynchronization;
+
+@Repository
+@ConditionalOnBean(OrcidController.class)
+public interface OrcidSynchronizationRepository extends SolidifyRepository<OrcidSynchronization> {
+  List<OrcidSynchronization> findByObjectId(String aipId);
+
+  List<OrcidSynchronization> findByPersonIdAndObjectId(String personId, String aipId);
+
+  Optional<OrcidSynchronization> findByPersonIdAndPutCode(String personId, BigInteger putCode);
+}
diff --git a/solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidClientService.java b/solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidClientService.java
new file mode 100644
index 000000000..a56271c70
--- /dev/null
+++ b/solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidClientService.java
@@ -0,0 +1,179 @@
+/*-
+ * %%----------------------------------------------------------------------------------------------
+ * Solidify Framework - Solidify ORCID - OrcidClientService.java
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * %----------------------------------------------------------------------------------------------%
+ * Copyright (C) 2017 - 2024 University of Geneva
+ * %----------------------------------------------------------------------------------------------%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-2.0.html>.
+ * ----------------------------------------------------------------------------------------------%%
+ */
+package ch.unige.solidify.service;
+
+import java.io.StringWriter;
+import java.math.BigInteger;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+
+import jakarta.xml.bind.JAXBContext;
+import jakarta.xml.bind.JAXBException;
+import jakarta.xml.bind.Marshaller;
+
+import ch.unige.solidify.config.SolidifyProperties;
+import ch.unige.solidify.controller.OrcidController;
+import ch.unige.solidify.exception.SolidifyResourceNotFoundException;
+import ch.unige.solidify.exception.SolidifyRuntimeException;
+import ch.unige.solidify.model.OrcidToken;
+import ch.unige.solidify.model.xml.orcid.v3_0.activities.Works;
+import ch.unige.solidify.model.xml.orcid.v3_0.bulk.Bulk;
+import ch.unige.solidify.model.xml.orcid.v3_0.work.Work;
+
+@Service
+@ConditionalOnBean(OrcidController.class)
+public class OrcidClientService {
+
+  private static final Logger log = LoggerFactory.getLogger(OrcidClientService.class);
+
+  private static final String BEARER = "Bearer";
+  private static final String PUT_CODE_PARAMETER = "put_code";
+  private static final String ORCID_PARAMETER = "orcid";
+
+  private final String apiUrl;
+
+  public OrcidClientService(SolidifyProperties solidifyProperties) {
+    this.apiUrl = solidifyProperties.getOrcid().getApiBaseUrl();
+  }
+
+  public Works getWorks(OrcidToken orcidToken) {
+    try {
+      RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
+      RestTemplate client = restTemplateBuilder.rootUri(this.apiUrl).build();
+
+      Map<String, String> parameters = new HashMap<>();
+      parameters.put(ORCID_PARAMETER, orcidToken.getOrcid());
+
+      HttpHeaders headers = new HttpHeaders();
+      headers.add(HttpHeaders.AUTHORIZATION, BEARER + " " + orcidToken.getAccessToken());
+      HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(headers);
+
+      return client.exchange(this.apiUrl + "{orcid}/works", HttpMethod.GET, entity, Works.class, parameters).getBody();
+    } catch (RuntimeException e) {
+      log.error("An error occurred while getting list of Works from ORCID", e);
+      return null;
+    }
+  }
+
+  public Work getWork(BigInteger putCode, OrcidToken orcidToken) {
+    try {
+      RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
+      RestTemplate client = restTemplateBuilder.rootUri(this.apiUrl).build();
+
+      Map<String, String> parameters = new HashMap<>();
+      parameters.put(ORCID_PARAMETER, orcidToken.getOrcid());
+      parameters.put(PUT_CODE_PARAMETER, putCode.toString());
+
+      HttpHeaders headers = new HttpHeaders();
+      headers.add(HttpHeaders.AUTHORIZATION, BEARER + " " + orcidToken.getAccessToken());
+      HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(headers);
+
+      final Bulk bulk = client.exchange(this.apiUrl + "{orcid}/works/{put_code}", HttpMethod.GET, entity, Bulk.class,
+              parameters).getBody();
+      if (bulk != null && !bulk.getWorkOrError().isEmpty() && bulk.getWorkOrError().get(0) instanceof Work work) {
+        return work;
+      }
+      throw new SolidifyResourceNotFoundException("Work with putCode " + putCode + " could not be found on ORCID API");
+    } catch (SolidifyResourceNotFoundException e) {
+      throw e;
+    } catch (Exception e) {
+      log.error("An error occurred while getting a Work from ORCID", e);
+      return null;
+    }
+  }
+
+  public BigInteger uploadWork(Work work, OrcidToken orcidToken) {
+    return this.uploadWork(work, orcidToken, null);
+  }
+
+  public BigInteger uploadWork(Work work, OrcidToken orcidToken, BigInteger putCode) {
+    try {
+      final RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
+      final RestTemplate client = restTemplateBuilder.rootUri(this.apiUrl).build();
+      final HttpHeaders headers = new HttpHeaders();
+      headers.add(HttpHeaders.AUTHORIZATION, BEARER + " " + orcidToken.getAccessToken());
+      headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML + ";charset=UTF-8");
+      if (putCode != null) {
+        return this.updateOrcidRecord(work, putCode, client, headers, orcidToken);
+      } else {
+        return this.createOrcidRecord(work, client, headers, orcidToken);
+      }
+    } catch (RuntimeException e) {
+      throw new SolidifyRuntimeException("An error occurred while uploading a Work to ORCID", e);
+    }
+  }
+
+  private BigInteger updateOrcidRecord(Work work, BigInteger putCode, RestTemplate client, HttpHeaders headers, OrcidToken orcidToken) {
+    work.setPutCode(putCode);
+    final String workAsXml = this.serializeWorkToXml(work);
+    final HttpEntity<String> request = new HttpEntity<>(workAsXml, headers);
+    try {
+      client.put(this.apiUrl + orcidToken.getOrcid() + "/work/" + putCode, request, String.class);
+    } catch (HttpClientErrorException.NotFound e) {
+      // ORCID record has been removed from the ORCID servers, create a new one
+      work.setPutCode(null);
+      return this.createOrcidRecord(work, client, headers, orcidToken);
+    }
+    return putCode;
+  }
+
+  private BigInteger createOrcidRecord(Work work, RestTemplate client, HttpHeaders headers, OrcidToken orcidToken) {
+    // Create ORCID record
+    final String workAsXml = this.serializeWorkToXml(work);
+    final HttpEntity<String> request = new HttpEntity<>(workAsXml, headers);
+    URI locationUri = client.postForEntity(this.apiUrl + orcidToken.getOrcid() + "/work", request, String.class).getHeaders().getLocation();
+    if (locationUri != null) {
+      String path = locationUri.getPath();
+      return BigInteger.valueOf(Long.parseLong(path.substring(path.lastIndexOf("/") + 1)));
+    } else {
+      return null;
+    }
+  }
+
+  private String serializeWorkToXml(Work work) {
+    try {
+      final JAXBContext jaxbContext = JAXBContext.newInstance(Work.class);
+      final Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
+      jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+      StringWriter sw = new StringWriter();
+      jaxbMarshaller.marshal(work, sw);
+      return sw.toString();
+    } catch (RuntimeException | JAXBException e) {
+      throw new SolidifyRuntimeException("Unable to serialize ORCID Work to XML", e);
+    }
+  }
+}
diff --git a/solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidService.java b/solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidOAuthService.java
similarity index 56%
rename from solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidService.java
rename to solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidOAuthService.java
index 9d6fa6a6a..6701219da 100644
--- a/solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidService.java
+++ b/solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidOAuthService.java
@@ -1,6 +1,6 @@
 /*-
  * %%----------------------------------------------------------------------------------------------
- * Solidify Framework - Solidify ORCID - OrcidService.java
+ * Solidify Framework - Solidify ORCID - OrcidOAuthService.java
  * SPDX-License-Identifier: GPL-2.0-or-later
  * %----------------------------------------------------------------------------------------------%
  * Copyright (C) 2017 - 2024 University of Geneva
@@ -23,9 +23,6 @@
 
 package ch.unige.solidify.service;
 
-import java.io.StringWriter;
-import java.math.BigInteger;
-import java.net.URI;
 import java.time.OffsetDateTime;
 import java.util.HashMap;
 import java.util.Map;
@@ -35,17 +32,9 @@ import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.MediaType;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Service;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.client.HttpClientErrorException;
-import org.springframework.web.client.RestTemplate;
 import org.springframework.web.reactive.function.BodyInserters;
 import org.springframework.web.reactive.function.client.WebClient;
 import org.springframework.web.util.UriComponentsBuilder;
@@ -53,34 +42,24 @@ import org.springframework.web.util.UriComponentsBuilder;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import jakarta.xml.bind.JAXBContext;
-import jakarta.xml.bind.JAXBException;
-import jakarta.xml.bind.Marshaller;
 import reactor.core.publisher.Mono;
 
 import ch.unige.solidify.config.SolidifyProperties;
 import ch.unige.solidify.controller.OrcidController;
-import ch.unige.solidify.exception.SolidifyResourceNotFoundException;
-import ch.unige.solidify.exception.SolidifyRuntimeException;
 import ch.unige.solidify.model.OrcidToken;
 import ch.unige.solidify.model.PersonWithOrcid;
-import ch.unige.solidify.model.xml.orcid.v3_0.activities.Works;
-import ch.unige.solidify.model.xml.orcid.v3_0.bulk.Bulk;
-import ch.unige.solidify.model.xml.orcid.v3_0.work.Work;
 
 @Service
 @ConditionalOnBean(OrcidController.class)
-public class OrcidService {
+public class OrcidOAuthService {
 
-  private static final Logger log = LoggerFactory.getLogger(OrcidService.class);
+  private static final Logger log = LoggerFactory.getLogger(OrcidOAuthService.class);
 
   private static final String ORCID_PARAMETER = "orcid";
-  private static final String PUT_CODE_PARAMETER = "put_code";
+
   private static final String ORCID_ERROR_PARAMETER = "orcid_error";
   private static final String ORCID_ERROR_DESCRIPTION_PARAMETER = "orcid_error_description";
 
-  private static final String BEARER = "Bearer";
-
   private final PersonWithOrcidService personWithOrcidService;
 
   private final String grantType;
@@ -89,17 +68,14 @@ public class OrcidService {
   private final String clientId;
   private final String clientSecret;
 
-  private final String apiUrl;
-
   private final Map<String, PersonOrigin> mapPersonOrigin = new HashMap<>();
 
-  public OrcidService(PersonWithOrcidService personWithOrcidService, SolidifyProperties solidifyProperties) {
+  public OrcidOAuthService(PersonWithOrcidService personWithOrcidService, SolidifyProperties solidifyProperties) {
     this.personWithOrcidService = personWithOrcidService;
     this.clientId = solidifyProperties.getOrcid().getClientId();
     this.clientSecret = solidifyProperties.getOrcid().getClientSecret();
     this.oAuthTokenUrl = solidifyProperties.getOrcid().getTokenUrl();
     this.grantType = solidifyProperties.getOrcid().getGrantType();
-    this.apiUrl = solidifyProperties.getOrcid().getApiBaseUrl();
   }
 
   private PersonWithOrcid getCurrentPerson() {
@@ -190,113 +166,6 @@ public class OrcidService {
     return uriBuilder.toUriString();
   }
 
-  public Works getWorks(OrcidToken orcidToken) {
-    try {
-      RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
-      RestTemplate client = restTemplateBuilder.rootUri(this.apiUrl).build();
-
-      Map<String, String> parameters = new HashMap<>();
-      parameters.put(ORCID_PARAMETER, orcidToken.getOrcid());
-
-      HttpHeaders headers = new HttpHeaders();
-      headers.add(HttpHeaders.AUTHORIZATION, BEARER + " " + orcidToken.getAccessToken());
-      HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(headers);
-
-      return client.exchange(this.apiUrl + "{orcid}/works", HttpMethod.GET, entity, Works.class, parameters).getBody();
-    } catch (RuntimeException e) {
-      log.error("An error occurred while getting list of Works from ORCID", e);
-      return null;
-    }
-  }
-
-  public Work getWork(BigInteger putCode, OrcidToken orcidToken) {
-    try {
-      RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
-      RestTemplate client = restTemplateBuilder.rootUri(this.apiUrl).build();
-
-      Map<String, String> parameters = new HashMap<>();
-      parameters.put(ORCID_PARAMETER, orcidToken.getOrcid());
-      parameters.put(PUT_CODE_PARAMETER, putCode.toString());
-
-      HttpHeaders headers = new HttpHeaders();
-      headers.add(HttpHeaders.AUTHORIZATION, BEARER + " " + orcidToken.getAccessToken());
-      HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(headers);
-
-      final Bulk bulk = client.exchange(this.apiUrl + "{orcid}/works/{put_code}", HttpMethod.GET, entity, Bulk.class,
-              parameters).getBody();
-      if (bulk != null && !bulk.getWorkOrError().isEmpty() && bulk.getWorkOrError().get(0) instanceof Work work) {
-        return work;
-      }
-      throw new SolidifyResourceNotFoundException("Work with putCode " + putCode + " could not be found on ORCID API");
-    } catch (SolidifyResourceNotFoundException e) {
-      throw e;
-    } catch (Exception e) {
-      log.error("An error occurred while getting a Work from ORCID", e);
-      return null;
-    }
-  }
-
-  public BigInteger uploadWork(Work work, OrcidToken orcidToken) {
-    return uploadWork(work, orcidToken, null);
-  }
-
-  public BigInteger uploadWork(Work work, OrcidToken orcidToken, BigInteger putCode) {
-    try {
-      final RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
-      final RestTemplate client = restTemplateBuilder.rootUri(this.apiUrl).build();
-      final HttpHeaders headers = new HttpHeaders();
-      headers.add(HttpHeaders.AUTHORIZATION, BEARER + " " + orcidToken.getAccessToken());
-      headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML + ";charset=UTF-8");
-      if (putCode != null) {
-        return this.updateOrcidRecord(work, putCode, client, headers, orcidToken);
-      } else {
-        return this.createOrcidRecord(work, client, headers, orcidToken);
-      }
-    } catch (RuntimeException e) {
-      throw new SolidifyRuntimeException("An error occurred while uploading a Work to ORCID", e);
-    }
-  }
-
-  private BigInteger updateOrcidRecord(Work work, BigInteger putCode, RestTemplate client, HttpHeaders headers, OrcidToken orcidToken) {
-    work.setPutCode(putCode);
-    final String workAsXml = this.serializeWorkToXml(work);
-    final HttpEntity<String> request = new HttpEntity<>(workAsXml, headers);
-    try {
-      client.put(this.apiUrl + orcidToken.getOrcid() + "/work/" + putCode, request, String.class);
-    } catch (HttpClientErrorException.NotFound e) {
-      // ORCID record has been removed from the ORCID servers, create a new one
-      work.setPutCode(null);
-      return this.createOrcidRecord(work, client, headers, orcidToken);
-    }
-    return putCode;
-  }
-
-  private BigInteger createOrcidRecord(Work work, RestTemplate client, HttpHeaders headers, OrcidToken orcidToken) {
-    // Create ORCID record
-    final String workAsXml = this.serializeWorkToXml(work);
-    final HttpEntity<String> request = new HttpEntity<>(workAsXml, headers);
-    URI locationUri = client.postForEntity(this.apiUrl + orcidToken.getOrcid() + "/work", request, String.class).getHeaders().getLocation();
-    if (locationUri != null) {
-      String path = locationUri.getPath();
-      return BigInteger.valueOf(Long.parseLong(path.substring(path.lastIndexOf("/") + 1)));
-    } else {
-      return null;
-    }
-  }
-
-  private String serializeWorkToXml(Work work) {
-    try {
-      final JAXBContext jaxbContext = JAXBContext.newInstance(Work.class);
-      final Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
-      jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
-      StringWriter sw = new StringWriter();
-      jaxbMarshaller.marshal(work, sw);
-      return sw.toString();
-    } catch (RuntimeException | JAXBException e) {
-      throw new SolidifyRuntimeException("Unable to serialize ORCID Work to XML", e);
-    }
-  }
-
   private static class PersonOrigin {
     private final String originUrl;
     private final String personId;
diff --git a/solidify-orcid/src/main/java/ch/unige/solidify/specification/OrcidSynchronizationSpecification.java b/solidify-orcid/src/main/java/ch/unige/solidify/specification/OrcidSynchronizationSpecification.java
new file mode 100644
index 000000000..e3aa962e8
--- /dev/null
+++ b/solidify-orcid/src/main/java/ch/unige/solidify/specification/OrcidSynchronizationSpecification.java
@@ -0,0 +1,44 @@
+/*-
+ * %%----------------------------------------------------------------------------------------------
+ * Solidify Framework - Solidify ORCID - OrcidSynchronizationSpecification.java
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * %----------------------------------------------------------------------------------------------%
+ * Copyright (C) 2017 - 2024 University of Geneva
+ * %----------------------------------------------------------------------------------------------%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-2.0.html>.
+ * ----------------------------------------------------------------------------------------------%%
+ */
+package ch.unige.solidify.specification;
+
+import java.util.List;
+
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Predicate;
+import jakarta.persistence.criteria.Root;
+
+import ch.unige.solidify.model.OrcidSynchronization;
+
+public class OrcidSynchronizationSpecification extends SolidifySpecification<OrcidSynchronization> {
+  public OrcidSynchronizationSpecification(OrcidSynchronization criteria) {
+    super(criteria);
+  }
+
+  @Override
+  protected void completePredicatesList(Root<OrcidSynchronization> root, CriteriaQuery<?> query, CriteriaBuilder builder,
+          List<Predicate> predicatesList) {
+    // do nothing
+  }
+}
-- 
GitLab


From 370179538e8958e2ce1464c758d6f8c5a936032f Mon Sep 17 00:00:00 2001
From: Nicolas Rod <Nicolas.Rod@unige.ch>
Date: Thu, 23 Jan 2025 18:35:25 +0100
Subject: [PATCH 2/4] solidify sendToOrcidProfile() method

---
 .../solidify/model/OrcidSynchronization.java  | 11 +++
 .../business/OrcidSynchronizationService.java | 72 +++++++++++++++++--
 2 files changed, 77 insertions(+), 6 deletions(-)

diff --git a/solidify-orcid-model/src/main/java/ch/unige/solidify/model/OrcidSynchronization.java b/solidify-orcid-model/src/main/java/ch/unige/solidify/model/OrcidSynchronization.java
index 1b3234c1d..eca28a22b 100644
--- a/solidify-orcid-model/src/main/java/ch/unige/solidify/model/OrcidSynchronization.java
+++ b/solidify-orcid-model/src/main/java/ch/unige/solidify/model/OrcidSynchronization.java
@@ -23,6 +23,8 @@
 package ch.unige.solidify.model;
 
 import static ch.unige.solidify.SolidifyConstants.DB_ID_LENGTH;
+import static ch.unige.solidify.model.OrcidSynchronization.OBJECT_ID_DB_FIELD;
+import static ch.unige.solidify.model.OrcidSynchronization.PERSON_ID_DB_FIELD;
 
 import java.math.BigInteger;
 import java.time.OffsetDateTime;
@@ -34,6 +36,8 @@ import jakarta.persistence.Column;
 import jakarta.persistence.Entity;
 import jakarta.persistence.Inheritance;
 import jakarta.persistence.InheritanceType;
+import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
 import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.Size;
 
@@ -42,8 +46,15 @@ import ch.unige.solidify.util.StringTool;
 
 @Entity
 @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
+@Table(name = "orcid_synchronization", uniqueConstraints = {
+        @UniqueConstraint(columnNames = { OBJECT_ID_DB_FIELD, PERSON_ID_DB_FIELD, "put_code" }) })
 public abstract class OrcidSynchronization extends ResourceNormalized {
 
+  public static final String OBJECT_ID_DB_FIELD = "object_id";
+  public static final String OBJECT_ID_FIELD = "objectId";
+  public static final String PERSON_ID_DB_FIELD = "person_id";
+  public static final String PERSON_ID_FIELD = "personId";
+
   @Schema(description = "The resId of the object synchronized with ORCID")
   @NotNull
   @Size(max = DB_ID_LENGTH)
diff --git a/solidify-orcid/src/main/java/ch/unige/solidify/business/OrcidSynchronizationService.java b/solidify-orcid/src/main/java/ch/unige/solidify/business/OrcidSynchronizationService.java
index a01d04448..08454c732 100644
--- a/solidify-orcid/src/main/java/ch/unige/solidify/business/OrcidSynchronizationService.java
+++ b/solidify-orcid/src/main/java/ch/unige/solidify/business/OrcidSynchronizationService.java
@@ -26,20 +26,37 @@ import java.math.BigInteger;
 import java.time.OffsetDateTime;
 import java.util.List;
 import java.util.Optional;
+import javax.xml.namespace.QName;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import jakarta.xml.bind.JAXBElement;
+
+import ch.unige.solidify.exception.SolidifyRuntimeException;
 import ch.unige.solidify.model.OrcidSynchronization;
+import ch.unige.solidify.model.PersonWithOrcid;
+import ch.unige.solidify.model.xml.orcid.v3_0.common.OrcidId;
+import ch.unige.solidify.model.xml.orcid.v3_0.work.Work;
 import ch.unige.solidify.repository.OrcidSynchronizationRepository;
-import ch.unige.solidify.service.OrcidOAuthService;
+import ch.unige.solidify.service.OrcidClientService;
 import ch.unige.solidify.service.ResourceService;
 import ch.unige.solidify.specification.OrcidSynchronizationSpecification;
 import ch.unige.solidify.specification.SolidifySpecification;
 
 public abstract class OrcidSynchronizationService extends ResourceService<OrcidSynchronization> {
 
-  protected OrcidOAuthService orcidOAuthService;
+  private static final Logger log = LoggerFactory.getLogger(OrcidSynchronizationService.class);
+
+  protected static final String ORCID_COMMON_NS = "http://www.orcid.org/ns/common";
+  protected static final String ORCID_DATASET_TYPE = "data-set";
+  protected static final String ORCID_DOI_TYPE = "doi";
+  protected static final String ORCID_SELF_RELATIONSHIP = "self";
 
-  public OrcidSynchronizationService(OrcidOAuthService orcidOAuthService) {
-    this.orcidOAuthService = orcidOAuthService;
+  protected final OrcidClientService orcidClientService;
+
+  public OrcidSynchronizationService(OrcidClientService orcidClientService) {
+    this.orcidClientService = orcidClientService;
   }
 
   public List<OrcidSynchronization> findByObjectId(String aipId) {
@@ -54,8 +71,51 @@ public abstract class OrcidSynchronizationService extends ResourceService<OrcidS
     return ((OrcidSynchronizationRepository) this.itemRepository).findByPersonIdAndPutCode(personId, putCode);
   }
 
-  protected abstract OrcidSynchronization storeOrcidSynchronization(String personId, BigInteger putCode, String objectId,
-          OffsetDateTime uploadDate);
+  public OrcidSynchronization sendToOrcidProfile(PersonWithOrcid person, String objectId) {
+    if (person.getOrcidToken() == null) {
+      throw new SolidifyRuntimeException("Person " + person.getFullName() + " (" + person.getResId() + ") doesn't have any ORCID token");
+    }
+    final Work work = this.buildWork(objectId);
+    final List<OrcidSynchronization> previousSynchronizationList = this.findByPersonIdAndObjectId(person.getResId(), objectId);
+    if (previousSynchronizationList.isEmpty()) {
+      // Create ORCID record
+      BigInteger putCode = this.orcidClientService.uploadWork(work, person.getOrcidToken());
+      log.info("Object '{}' ({}) has been uploaded to ORCID profile of person {} as Work with putCode {}", work.getTitle().getTitle(), objectId,
+              person, putCode);
+      return this.saveOrcidSynchronization(person.getResId(), putCode, objectId, OffsetDateTime.now());
+    } else if (previousSynchronizationList.size() == 1) {
+      // Update ORCID record
+      final OrcidSynchronization previousSynchronization = previousSynchronizationList.get(0);
+      BigInteger putCode = this.orcidClientService.uploadWork(work, person.getOrcidToken(), previousSynchronization.getPutCode());
+      log.info("Object '{}' ({}) has been updated on ORCID profile of person {} as Work with putCode {}", work.getTitle().getTitle(), objectId,
+              person, putCode);
+      if (putCode != null) {
+        previousSynchronization.setPutCode(putCode);
+      }
+      previousSynchronization.setUploadDate(OffsetDateTime.now());
+      return this.save(previousSynchronization);
+    } else {
+      throw new IllegalStateException(
+              "More than one OrcidSynchronization found for person " + person + " for object '" + work.getTitle().getTitle() + "' (" + objectId
+                      + ")");
+    }
+  }
+
+  public abstract OrcidSynchronization saveOrcidSynchronization(String personId, BigInteger putCode, String objectId, OffsetDateTime uploadDate);
+
+  protected abstract Work buildWork(String objectId);
+
+  protected OrcidId buildOrcidId(String orcidValue) {
+    OrcidId orcidId = new OrcidId();
+    JAXBElement<String> uri = new JAXBElement<>(new QName(ORCID_COMMON_NS, "uri"), String.class,
+            "https://orcid.org/" + orcidValue);
+    orcidId.getContent().add(uri);
+    JAXBElement<String> path = new JAXBElement<>(new QName(ORCID_COMMON_NS, "path"), String.class, orcidValue);
+    orcidId.getContent().add(path);
+    JAXBElement<String> host = new JAXBElement<>(new QName(ORCID_COMMON_NS, "host"), String.class, "orcid.org");
+    orcidId.getContent().add(host);
+    return orcidId;
+  }
 
   @Override
   public SolidifySpecification<OrcidSynchronization> getSpecification(OrcidSynchronization orcidSynchronization) {
-- 
GitLab


From f4eea95cbc8aeb032adbe0ffdbf18c51aae8476a Mon Sep 17 00:00:00 2001
From: Nicolas Rod <Nicolas.Rod@unige.ch>
Date: Fri, 24 Jan 2025 12:33:15 +0100
Subject: [PATCH 3/4] improve log message when update is replaced by create

---
 .../business/OrcidSynchronizationService.java        | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/solidify-orcid/src/main/java/ch/unige/solidify/business/OrcidSynchronizationService.java b/solidify-orcid/src/main/java/ch/unige/solidify/business/OrcidSynchronizationService.java
index 08454c732..5afd16b7d 100644
--- a/solidify-orcid/src/main/java/ch/unige/solidify/business/OrcidSynchronizationService.java
+++ b/solidify-orcid/src/main/java/ch/unige/solidify/business/OrcidSynchronizationService.java
@@ -86,9 +86,15 @@ public abstract class OrcidSynchronizationService extends ResourceService<OrcidS
     } else if (previousSynchronizationList.size() == 1) {
       // Update ORCID record
       final OrcidSynchronization previousSynchronization = previousSynchronizationList.get(0);
-      BigInteger putCode = this.orcidClientService.uploadWork(work, person.getOrcidToken(), previousSynchronization.getPutCode());
-      log.info("Object '{}' ({}) has been updated on ORCID profile of person {} as Work with putCode {}", work.getTitle().getTitle(), objectId,
-              person, putCode);
+      BigInteger previousPutCode = previousSynchronization.getPutCode();
+      BigInteger putCode = this.orcidClientService.uploadWork(work, person.getOrcidToken(), previousPutCode);
+      if (previousPutCode.equals(putCode)) {
+        log.info("Object '{}' ({}) has been updated on ORCID profile of person {} as Work with putCode {}", work.getTitle().getTitle(), objectId,
+                person, putCode);
+      } else {
+        log.info("Object '{}' ({}) has been created again on ORCID profile of person {} as Work with putCode {}", work.getTitle().getTitle(),
+                objectId, person, putCode);
+      }
       if (putCode != null) {
         previousSynchronization.setPutCode(putCode);
       }
-- 
GitLab


From 1a68eb34ca2d479ce388dadc8fff6cad52cf1189 Mon Sep 17 00:00:00 2001
From: Nicolas Rod <Nicolas.Rod@unige.ch>
Date: Fri, 24 Jan 2025 16:09:09 +0100
Subject: [PATCH 4/4] revert public static final

---
 .../src/main/java/ch/unige/solidify/auth/model/OrcidInfo.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/solidify-authorization-model/src/main/java/ch/unige/solidify/auth/model/OrcidInfo.java b/solidify-authorization-model/src/main/java/ch/unige/solidify/auth/model/OrcidInfo.java
index 2399435d6..5fad8d09e 100644
--- a/solidify-authorization-model/src/main/java/ch/unige/solidify/auth/model/OrcidInfo.java
+++ b/solidify-authorization-model/src/main/java/ch/unige/solidify/auth/model/OrcidInfo.java
@@ -29,8 +29,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
 public interface OrcidInfo {
 
   // ORCID
-  String ORCID_PATTERN = "|[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}|[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{3}X";
-  String ORCID_MESSAGE = "The ORCID should be of the form 1234-1234-1234-1234 or 1234-1234-1234-123X";
+  public static final String ORCID_PATTERN = "|[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}|[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{3}X";
+  public static final String ORCID_MESSAGE = "The ORCID should be of the form 1234-1234-1234-1234 or 1234-1234-1234-123X";
 
   @Schema(description = "The ORCID of the person (Format: xxxx-xxxx-xxxx-xxxx).")
   String getOrcid();
-- 
GitLab