From 4fd8780088a84884498fe4cf82c918339ced6e29 Mon Sep 17 00:00:00 2001
From: Nicolas Rod <Nicolas.Rod@unige.ch>
Date: Fri, 24 Jan 2025 18:45:07 +0100
Subject: [PATCH 1/3] feat(ORCID): updating a missing ORCID Work do not always
 recreate a new one

---
 .../business/OrcidSynchronizationService.java | 43 +++++++++++------
 .../solidify/service/OrcidClientService.java  | 48 +++++++++++++++----
 2 files changed, 66 insertions(+), 25 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 5afd16b7d..1bc8d914f 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
@@ -35,6 +35,7 @@ import jakarta.xml.bind.JAXBElement;
 
 import ch.unige.solidify.exception.SolidifyRuntimeException;
 import ch.unige.solidify.model.OrcidSynchronization;
+import ch.unige.solidify.model.OrcidToken;
 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;
@@ -55,7 +56,7 @@ public abstract class OrcidSynchronizationService extends ResourceService<OrcidS
 
   protected final OrcidClientService orcidClientService;
 
-  public OrcidSynchronizationService(OrcidClientService orcidClientService) {
+  protected OrcidSynchronizationService(OrcidClientService orcidClientService) {
     this.orcidClientService = orcidClientService;
   }
 
@@ -72,34 +73,45 @@ public abstract class OrcidSynchronizationService extends ResourceService<OrcidS
   }
 
   public OrcidSynchronization sendToOrcidProfile(PersonWithOrcid person, String objectId) {
-    if (person.getOrcidToken() == null) {
+    return this.sendToOrcidProfile(person, objectId, false);
+  }
+
+  public OrcidSynchronization sendToOrcidProfile(PersonWithOrcid person, String objectId, boolean recreateIfMissingInProfile) {
+    OrcidToken orcidToken = person.getOrcidToken();
+    if (orcidToken == 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());
+      BigInteger putCode = this.orcidClientService.uploadWork(work, orcidToken);
       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());
+      return this.saveNewOrcidSynchronization(person.getResId(), putCode, objectId, OffsetDateTime.now());
     } else if (previousSynchronizationList.size() == 1) {
       // Update ORCID record
       final OrcidSynchronization previousSynchronization = previousSynchronizationList.get(0);
       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);
-      }
+      BigInteger putCode = this.orcidClientService.uploadWork(work, orcidToken, previousPutCode, recreateIfMissingInProfile);
       if (putCode != null) {
+        // Upload succeeded
+        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);
+        }
+
+        // Upload succeeded, update OrcidSynchronization in database
         previousSynchronization.setPutCode(putCode);
+        previousSynchronization.setUploadDate(OffsetDateTime.now());
+        return this.save(previousSynchronization);
+      } else {
+        // Upload failed, do not update OrcidSynchronization in database
+        return null;
       }
-      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
@@ -107,7 +119,8 @@ public abstract class OrcidSynchronizationService extends ResourceService<OrcidS
     }
   }
 
-  public abstract OrcidSynchronization saveOrcidSynchronization(String personId, BigInteger putCode, String objectId, OffsetDateTime uploadDate);
+  public abstract OrcidSynchronization saveNewOrcidSynchronization(String personId, BigInteger putCode, String objectId,
+          OffsetDateTime uploadDate);
 
   protected abstract Work buildWork(String objectId);
 
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
index a56271c70..7dd3f4d3d 100644
--- a/solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidClientService.java
+++ b/solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidClientService.java
@@ -117,10 +117,10 @@ public class OrcidClientService {
   }
 
   public BigInteger uploadWork(Work work, OrcidToken orcidToken) {
-    return this.uploadWork(work, orcidToken, null);
+    return this.uploadWork(work, orcidToken, null, false);
   }
 
-  public BigInteger uploadWork(Work work, OrcidToken orcidToken, BigInteger putCode) {
+  public BigInteger uploadWork(Work work, OrcidToken orcidToken, BigInteger putCode, boolean recreateIfMissingInProfile) {
     try {
       final RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
       final RestTemplate client = restTemplateBuilder.rootUri(this.apiUrl).build();
@@ -128,8 +128,14 @@ public class OrcidClientService {
       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);
+        // ORCID Work has already been synchronized once -> update it in ORCID profile
+        if (recreateIfMissingInProfile) {
+          return this.updateOrCreateOrcidRecord(work, putCode, client, headers, orcidToken);
+        } else {
+          return this.updateOrcidRecord(work, putCode, client, headers, orcidToken);
+        }
       } else {
+        // ORCID Work has never been synchronized yet -> create it in ORCID profile
         return this.createOrcidRecord(work, client, headers, orcidToken);
       }
     } catch (RuntimeException e) {
@@ -138,17 +144,18 @@ public class OrcidClientService {
   }
 
   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 {
+      work.setPutCode(putCode);
+      final String workAsXml = this.serializeWorkToXml(work);
+      final HttpEntity<String> request = new HttpEntity<>(workAsXml, headers);
       client.put(this.apiUrl + orcidToken.getOrcid() + "/work/" + putCode, request, String.class);
+      return putCode;
     } 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);
+      // ORCID record has been removed from the ORCID servers since it was synchronized, but do not automatically create a new one
+      log.warn("Work '{}' with putCode {} could not be updated as it doesn't exist in ORCID profile {}",
+              work.getTitle().getTitle(), putCode, orcidToken.getOrcid());
+      return null;
     }
-    return putCode;
   }
 
   private BigInteger createOrcidRecord(Work work, RestTemplate client, HttpHeaders headers, OrcidToken orcidToken) {
@@ -164,6 +171,27 @@ public class OrcidClientService {
     }
   }
 
+  /**
+   * Update an existing Work in an ORCID profile. If the Work doesn't exist anymore, it creates a new one.
+   *
+   * @param work
+   * @param putCode
+   * @param client
+   * @param headers
+   * @param orcidToken
+   * @return
+   */
+  private BigInteger updateOrCreateOrcidRecord(Work work, BigInteger putCode, RestTemplate client, HttpHeaders headers, OrcidToken orcidToken) {
+    BigInteger updatedPutCode = this.updateOrcidRecord(work, putCode, client, headers, orcidToken);
+    if (updatedPutCode != null) {
+      return updatedPutCode;
+    } else {
+      // ORCID record has been removed from the ORCID servers since it was synchronized --> create a new one
+      work.setPutCode(null);
+      return this.createOrcidRecord(work, client, headers, orcidToken);
+    }
+  }
+
   private String serializeWorkToXml(Work work) {
     try {
       final JAXBContext jaxbContext = JAXBContext.newInstance(Work.class);
-- 
GitLab


From bf5cff3c1cfbd9c85a0862acf43f66ce57ce2b31 Mon Sep 17 00:00:00 2001
From: Nicolas Rod <Nicolas.Rod@unige.ch>
Date: Mon, 27 Jan 2025 17:08:22 +0100
Subject: [PATCH 2/3] do not return null but use
 SolidifyOrcidWorkMissingInProfileException + replace boolean by
 OrcidWorkUpdateMode enum

---
 .../business/OrcidSynchronizationService.java | 21 ++++----
 ...ifyOrcidWorkMissingInProfileException.java | 14 ++++++
 .../solidify/model/OrcidWorkUpdateMode.java   |  6 +++
 .../solidify/service/OrcidClientService.java  | 50 +++++++++----------
 4 files changed, 54 insertions(+), 37 deletions(-)
 create mode 100644 solidify-orcid/src/main/java/ch/unige/solidify/exception/SolidifyOrcidWorkMissingInProfileException.java
 create mode 100644 solidify-orcid/src/main/java/ch/unige/solidify/model/OrcidWorkUpdateMode.java

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 1bc8d914f..b3bbae9e8 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
@@ -33,9 +33,11 @@ import org.slf4j.LoggerFactory;
 
 import jakarta.xml.bind.JAXBElement;
 
+import ch.unige.solidify.exception.SolidifyOrcidWorkMissingInProfileException;
 import ch.unige.solidify.exception.SolidifyRuntimeException;
 import ch.unige.solidify.model.OrcidSynchronization;
 import ch.unige.solidify.model.OrcidToken;
+import ch.unige.solidify.model.OrcidWorkUpdateMode;
 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;
@@ -72,11 +74,7 @@ public abstract class OrcidSynchronizationService extends ResourceService<OrcidS
     return ((OrcidSynchronizationRepository) this.itemRepository).findByPersonIdAndPutCode(personId, putCode);
   }
 
-  public OrcidSynchronization sendToOrcidProfile(PersonWithOrcid person, String objectId) {
-    return this.sendToOrcidProfile(person, objectId, false);
-  }
-
-  public OrcidSynchronization sendToOrcidProfile(PersonWithOrcid person, String objectId, boolean recreateIfMissingInProfile) {
+  public OrcidSynchronization sendToOrcidProfile(PersonWithOrcid person, String objectId, OrcidWorkUpdateMode updateMode) {
     OrcidToken orcidToken = person.getOrcidToken();
     if (orcidToken == null) {
       throw new SolidifyRuntimeException("Person " + person.getFullName() + " (" + person.getResId() + ") doesn't have any ORCID token");
@@ -93,8 +91,8 @@ public abstract class OrcidSynchronizationService extends ResourceService<OrcidS
       // Update ORCID record
       final OrcidSynchronization previousSynchronization = previousSynchronizationList.get(0);
       BigInteger previousPutCode = previousSynchronization.getPutCode();
-      BigInteger putCode = this.orcidClientService.uploadWork(work, orcidToken, previousPutCode, recreateIfMissingInProfile);
-      if (putCode != null) {
+      try {
+        BigInteger putCode = this.orcidClientService.uploadWork(work, orcidToken, previousPutCode, updateMode);
         // Upload succeeded
         if (previousPutCode.equals(putCode)) {
           log.info("Object '{}' ({}) has been updated on ORCID profile of person {} as Work with putCode {}", work.getTitle().getTitle(),
@@ -108,9 +106,12 @@ public abstract class OrcidSynchronizationService extends ResourceService<OrcidS
         previousSynchronization.setPutCode(putCode);
         previousSynchronization.setUploadDate(OffsetDateTime.now());
         return this.save(previousSynchronization);
-      } else {
-        // Upload failed, do not update OrcidSynchronization in database
-        return null;
+      } catch (SolidifyOrcidWorkMissingInProfileException e) {
+        log.warn("Object '{}' ({}) with putCode {} could not be updated in ORCID profile of person {} as it doesn't exist",
+                work.getTitle().getTitle(), objectId, previousPutCode, person);
+        throw e;
+      } catch (RuntimeException e) {
+        throw new SolidifyRuntimeException("An error occurred while uploading a Work to ORCID", e);
       }
     } else {
       throw new IllegalStateException(
diff --git a/solidify-orcid/src/main/java/ch/unige/solidify/exception/SolidifyOrcidWorkMissingInProfileException.java b/solidify-orcid/src/main/java/ch/unige/solidify/exception/SolidifyOrcidWorkMissingInProfileException.java
new file mode 100644
index 000000000..f4b7b6449
--- /dev/null
+++ b/solidify-orcid/src/main/java/ch/unige/solidify/exception/SolidifyOrcidWorkMissingInProfileException.java
@@ -0,0 +1,14 @@
+package ch.unige.solidify.exception;
+
+public class SolidifyOrcidWorkMissingInProfileException extends SolidifyRuntimeException {
+  public SolidifyOrcidWorkMissingInProfileException() {
+  }
+
+  public SolidifyOrcidWorkMissingInProfileException(String message) {
+    super(message);
+  }
+
+  public SolidifyOrcidWorkMissingInProfileException(String message, Throwable exception) {
+    super(message, exception);
+  }
+}
diff --git a/solidify-orcid/src/main/java/ch/unige/solidify/model/OrcidWorkUpdateMode.java b/solidify-orcid/src/main/java/ch/unige/solidify/model/OrcidWorkUpdateMode.java
new file mode 100644
index 000000000..acca0fb26
--- /dev/null
+++ b/solidify-orcid/src/main/java/ch/unige/solidify/model/OrcidWorkUpdateMode.java
@@ -0,0 +1,6 @@
+package ch.unige.solidify.model;
+
+public enum OrcidWorkUpdateMode {
+  CREATE_IF_MISSING, // If no Work with corresponding putCode exists in ORCID profile, create a new one
+  UPDATE_ONLY        // If no Work with corresponding putCode exists in ORCID profile, do not create a new one
+}
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
index 7dd3f4d3d..6c1520c41 100644
--- a/solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidClientService.java
+++ b/solidify-orcid/src/main/java/ch/unige/solidify/service/OrcidClientService.java
@@ -47,9 +47,11 @@ import jakarta.xml.bind.Marshaller;
 
 import ch.unige.solidify.config.SolidifyProperties;
 import ch.unige.solidify.controller.OrcidController;
+import ch.unige.solidify.exception.SolidifyOrcidWorkMissingInProfileException;
 import ch.unige.solidify.exception.SolidifyResourceNotFoundException;
 import ch.unige.solidify.exception.SolidifyRuntimeException;
 import ch.unige.solidify.model.OrcidToken;
+import ch.unige.solidify.model.OrcidWorkUpdateMode;
 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;
@@ -117,29 +119,25 @@ public class OrcidClientService {
   }
 
   public BigInteger uploadWork(Work work, OrcidToken orcidToken) {
-    return this.uploadWork(work, orcidToken, null, false);
+    return this.uploadWork(work, orcidToken, null, null);
   }
 
-  public BigInteger uploadWork(Work work, OrcidToken orcidToken, BigInteger putCode, boolean recreateIfMissingInProfile) {
-    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) {
-        // ORCID Work has already been synchronized once -> update it in ORCID profile
-        if (recreateIfMissingInProfile) {
-          return this.updateOrCreateOrcidRecord(work, putCode, client, headers, orcidToken);
-        } else {
-          return this.updateOrcidRecord(work, putCode, client, headers, orcidToken);
-        }
+  public BigInteger uploadWork(Work work, OrcidToken orcidToken, BigInteger putCode, OrcidWorkUpdateMode updateMode) {
+    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) {
+      // ORCID Work has already been synchronized once -> update it in ORCID profile
+      if (OrcidWorkUpdateMode.CREATE_IF_MISSING == updateMode) {
+        return this.updateOrCreateOrcidRecord(work, putCode, client, headers, orcidToken);
       } else {
-        // ORCID Work has never been synchronized yet -> create it in ORCID profile
-        return this.createOrcidRecord(work, client, headers, orcidToken);
+        return this.updateOrcidRecord(work, putCode, client, headers, orcidToken);
       }
-    } catch (RuntimeException e) {
-      throw new SolidifyRuntimeException("An error occurred while uploading a Work to ORCID", e);
+    } else {
+      // ORCID Work has never been synchronized yet -> create it in ORCID profile
+      return this.createOrcidRecord(work, client, headers, orcidToken);
     }
   }
 
@@ -151,10 +149,9 @@ public class OrcidClientService {
       client.put(this.apiUrl + orcidToken.getOrcid() + "/work/" + putCode, request, String.class);
       return putCode;
     } catch (HttpClientErrorException.NotFound e) {
-      // ORCID record has been removed from the ORCID servers since it was synchronized, but do not automatically create a new one
-      log.warn("Work '{}' with putCode {} could not be updated as it doesn't exist in ORCID profile {}",
-              work.getTitle().getTitle(), putCode, orcidToken.getOrcid());
-      return null;
+      throw new SolidifyOrcidWorkMissingInProfileException(
+              "Work '" + work.getTitle().getTitle() + "' with putCode " + putCode + " could not be updated as it doesn't exist in ORCID profile "
+                      + orcidToken.getOrcid());
     }
   }
 
@@ -182,10 +179,9 @@ public class OrcidClientService {
    * @return
    */
   private BigInteger updateOrCreateOrcidRecord(Work work, BigInteger putCode, RestTemplate client, HttpHeaders headers, OrcidToken orcidToken) {
-    BigInteger updatedPutCode = this.updateOrcidRecord(work, putCode, client, headers, orcidToken);
-    if (updatedPutCode != null) {
-      return updatedPutCode;
-    } else {
+    try {
+      return this.updateOrcidRecord(work, putCode, client, headers, orcidToken);
+    } catch (SolidifyOrcidWorkMissingInProfileException e) {
       // ORCID record has been removed from the ORCID servers since it was synchronized --> create a new one
       work.setPutCode(null);
       return this.createOrcidRecord(work, client, headers, orcidToken);
-- 
GitLab


From af2f285419afb5b4722df79f41ad43a7907e964a Mon Sep 17 00:00:00 2001
From: Nicolas Rod <Nicolas.Rod@unige.ch>
Date: Mon, 27 Jan 2025 17:19:47 +0100
Subject: [PATCH 3/3] rename saveNewOrcidSynchronization method

---
 .../unige/solidify/business/OrcidSynchronizationService.java  | 4 ++--
 1 file changed, 2 insertions(+), 2 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 b3bbae9e8..4637965a8 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,7 +86,7 @@ public abstract class OrcidSynchronizationService extends ResourceService<OrcidS
       BigInteger putCode = this.orcidClientService.uploadWork(work, orcidToken);
       log.info("Object '{}' ({}) has been uploaded to ORCID profile of person {} as Work with putCode {}", work.getTitle().getTitle(), objectId,
               person, putCode);
-      return this.saveNewOrcidSynchronization(person.getResId(), putCode, objectId, OffsetDateTime.now());
+      return this.createOrcidSynchronization(person.getResId(), putCode, objectId, OffsetDateTime.now());
     } else if (previousSynchronizationList.size() == 1) {
       // Update ORCID record
       final OrcidSynchronization previousSynchronization = previousSynchronizationList.get(0);
@@ -120,7 +120,7 @@ public abstract class OrcidSynchronizationService extends ResourceService<OrcidS
     }
   }
 
-  public abstract OrcidSynchronization saveNewOrcidSynchronization(String personId, BigInteger putCode, String objectId,
+  public abstract OrcidSynchronization createOrcidSynchronization(String personId, BigInteger putCode, String objectId,
           OffsetDateTime uploadDate);
 
   protected abstract Work buildWork(String objectId);
-- 
GitLab