diff --git a/CHANGELOG.md b/CHANGELOG.md
index f918a6bc35e..e3cff39b8d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## v3.23.24 - 2026-10-09
+
+[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v3.24.8...v3.23.24)
+
## v3.23.22 - 2026-10-05
[Full Changelog](https://github.com/ORCID/ORCID-Source/compare/v3.23.21...v3.23.22)
diff --git a/orcid-api-web/pom.xml b/orcid-api-web/pom.xml
index 2379d667abe..d209cabb951 100644
--- a/orcid-api-web/pom.xml
+++ b/orcid-api-web/pom.xml
@@ -67,13 +67,6 @@
-
-
- org.togglz
- togglz-junit
- 2.4.1.Final
- test
-
${project.groupId}
orcid-test
diff --git a/orcid-api-web/src/main/java/org/orcid/api/memberV2/server/delegator/impl/MemberV2ApiServiceDelegatorImpl.java b/orcid-api-web/src/main/java/org/orcid/api/memberV2/server/delegator/impl/MemberV2ApiServiceDelegatorImpl.java
index d2ba9aba218..154d4a8c344 100644
--- a/orcid-api-web/src/main/java/org/orcid/api/memberV2/server/delegator/impl/MemberV2ApiServiceDelegatorImpl.java
+++ b/orcid-api-web/src/main/java/org/orcid/api/memberV2/server/delegator/impl/MemberV2ApiServiceDelegatorImpl.java
@@ -279,6 +279,7 @@ public Response viewActivities(String orcid) {
public Response viewWork(String orcid, Long putCode) {
Work w = workManagerReadOnly.getWork(orcid, putCode);
orcidSecurityManager.checkAndFilter(orcid, w, ScopePathType.ORCID_WORKS_READ_LIMITED);
+ contributorUtils.filterContributorPrivateData(w);
ActivityUtils.cleanEmptyFields(w);
ActivityUtils.setPathToActivity(w, orcid);
sourceUtils.setSourceName(w);
@@ -1090,6 +1091,7 @@ public Response viewBulkWorks(String orcid, String putCodes) {
WorkBulk workBulk = workManagerReadOnly.findWorkBulk(orcid, putCodes);
orcidSecurityManager.checkAndFilter(orcid, workBulk, ScopePathType.ORCID_WORKS_READ_LIMITED);
+ contributorUtils.filterContributorPrivateData(workBulk);
ActivityUtils.cleanEmptyFields(workBulk);
ActivityUtils.setPathToBulk(workBulk, orcid);
sourceUtils.setSourceName(workBulk);
diff --git a/orcid-api-web/src/main/java/org/orcid/api/memberV3/server/delegator/impl/MemberV3ApiServiceDelegatorImpl.java b/orcid-api-web/src/main/java/org/orcid/api/memberV3/server/delegator/impl/MemberV3ApiServiceDelegatorImpl.java
index b7f727fd197..e8f0b9d89d4 100644
--- a/orcid-api-web/src/main/java/org/orcid/api/memberV3/server/delegator/impl/MemberV3ApiServiceDelegatorImpl.java
+++ b/orcid-api-web/src/main/java/org/orcid/api/memberV3/server/delegator/impl/MemberV3ApiServiceDelegatorImpl.java
@@ -247,6 +247,7 @@ public Response viewWork(String orcid, Long putCode) {
checkProfileStatus(orcid, true);
Work w = workManagerReadOnly.getWork(orcid, putCode);
orcidSecurityManager.checkAndFilter(orcid, w, ScopePathType.ORCID_WORKS_READ_LIMITED);
+ contributorUtils.filterContributorPrivateData(w);
ActivityUtils.cleanEmptyFields(w);
ActivityUtils.setPathToActivity(w, orcid);
sourceUtils.setSourceName(w);
@@ -1114,6 +1115,7 @@ public Response viewBulkWorks(String orcid, String putCodes) {
WorkBulk workBulk = workManagerReadOnly.findWorkBulk(orcid, putCodes);
orcidSecurityManager.checkAndFilter(orcid, workBulk, ScopePathType.ORCID_WORKS_READ_LIMITED);
+ contributorUtils.filterContributorPrivateData(workBulk);
ActivityUtils.cleanEmptyFields(workBulk);
ActivityUtils.setPathToBulk(workBulk, orcid);
sourceUtils.setSourceName(workBulk);
diff --git a/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceDelegator_WorksTest.java b/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceDelegator_WorksTest.java
index ac9c4ad87db..fbe9d43fc23 100644
--- a/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceDelegator_WorksTest.java
+++ b/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceDelegator_WorksTest.java
@@ -174,9 +174,27 @@ public void testViewWorkReadPublic() {
assertNotNull(work.getWorkContributors().getContributor().get(0).getContributorOrcid());
assertEquals("0000-0000-0000-0000", work.getWorkContributors().getContributor().get(0).getContributorOrcid().getPath());
assertNull(work.getWorkContributors().getContributor().get(0).getCreditName());
+ assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
Utils.assertIsPublicOrSource(work, "APP-5555555555555555");
}
+ // Work 11 was deposited by APP-5555555555555555; the email is withheld from every caller
+ @Test
+ public void testViewWorkOmitsContributorEmailForAnotherClientReadPublic() {
+ SecurityContextTestUtils.setUpSecurityContextForClientOnly("APP-6666666666666666", ScopePathType.READ_PUBLIC);
+ Work work = (Work) serviceDelegator.viewWork(ORCID, 11L).getEntity();
+ assertEquals(1, work.getWorkContributors().getContributor().size());
+ assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
+ }
+
+ @Test
+ public void testViewWorkOmitsContributorEmailForAnotherClientReadLimited() {
+ SecurityContextTestUtils.setUpSecurityContext(ORCID, "APP-6666666666666666", ScopePathType.READ_LIMITED);
+ Work work = (Work) serviceDelegator.viewWork(ORCID, 11L).getEntity();
+ assertEquals(1, work.getWorkContributors().getContributor().size());
+ assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
+ }
+
@Test
public void testViewWorkSummaryReadPublic() {
SecurityContextTestUtils.setUpSecurityContextForClientOnly("APP-5555555555555555", ScopePathType.READ_PUBLIC);
@@ -729,6 +747,9 @@ public void testViewBulkWorks() {
assertEquals("/0000-0000-0000-0003/work/11", ((Work) workBulk.getBulk().get(0)).getPath());
assertEquals("/0000-0000-0000-0003/work/12", ((Work) workBulk.getBulk().get(1)).getPath());
assertEquals("/0000-0000-0000-0003/work/13", ((Work) workBulk.getBulk().get(2)).getPath());
+ Work work11 = (Work) workBulk.getBulk().get(0);
+ assertEquals(1, work11.getWorkContributors().getContributor().size());
+ assertNull(work11.getWorkContributors().getContributor().get(0).getContributorEmail());
}
@Test
diff --git a/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceVersionedDelegatorTest.java b/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceVersionedDelegatorTest.java
index f0e4a850319..b5119067e99 100644
--- a/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceVersionedDelegatorTest.java
+++ b/orcid-api-web/src/test/java/org/orcid/api/memberV2/server/delegator/MemberV2ApiServiceVersionedDelegatorTest.java
@@ -2,6 +2,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -1984,6 +1985,26 @@ public void testDeactivatedRecordDeleteAddress() {
fail();
}
+ /**
+ * The contributor email is write-only. The versioned beans run every work through the
+ * 2.0 <-> 2.1 converter, which copies every bean property, so check it stays out on both.
+ */
+ @Test
+ public void testViewWorkOmitsContributorEmailOnV2_0AndV2_1() {
+ SecurityContextTestUtils.setUpSecurityContext("0000-0000-0000-0003", ScopePathType.READ_LIMITED);
+ assertNoContributorEmail((Work) serviceDelegator.viewWork("0000-0000-0000-0003", 11L).getEntity());
+ assertNoContributorEmail((Work) serviceDelegator_v2_1.viewWork("0000-0000-0000-0003", 11L).getEntity());
+ assertNoContributorEmail((Work) ((WorkBulk) serviceDelegator.viewBulkWorks("0000-0000-0000-0003", "11").getEntity()).getBulk().get(0));
+ assertNoContributorEmail((Work) ((WorkBulk) serviceDelegator_v2_1.viewBulkWorks("0000-0000-0000-0003", "11").getEntity()).getBulk().get(0));
+ }
+
+ private void assertNoContributorEmail(Work work) {
+ assertEquals(Long.valueOf(11), work.getPutCode());
+ assertEquals(1, work.getWorkContributors().getContributor().size());
+ assertEquals("0000-0000-0000-0000", work.getWorkContributors().getContributor().get(0).getContributorOrcid().getPath());
+ assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
+ }
+
private void assertSourceElement(SourceAware element, boolean isHttps) {
if(element.getSource() != null && element.getSource().getSourceOrcid() != null) {
assertProtocol(element.getSource().getSourceOrcid().getUri(), isHttps);
diff --git a/orcid-api-web/src/test/java/org/orcid/api/memberV3/server/delegator/MemberV3ApiServiceDelegator_WorksTest.java b/orcid-api-web/src/test/java/org/orcid/api/memberV3/server/delegator/MemberV3ApiServiceDelegator_WorksTest.java
index 12819562718..7da0c8b2ecc 100644
--- a/orcid-api-web/src/test/java/org/orcid/api/memberV3/server/delegator/MemberV3ApiServiceDelegator_WorksTest.java
+++ b/orcid-api-web/src/test/java/org/orcid/api/memberV3/server/delegator/MemberV3ApiServiceDelegator_WorksTest.java
@@ -208,9 +208,27 @@ public void testViewWorkReadPublic() {
assertNotNull(work.getWorkContributors().getContributor().get(0).getContributorOrcid());
assertEquals("0000-0000-0000-0000", work.getWorkContributors().getContributor().get(0).getContributorOrcid().getPath());
assertNull(work.getWorkContributors().getContributor().get(0).getCreditName());
+ assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
Utils.assertIsPublicOrSource(work, "APP-5555555555555555");
}
+ // Work 11 was deposited by APP-5555555555555555; the email is withheld from every caller
+ @Test
+ public void testViewWorkOmitsContributorEmailForAnotherClientReadPublic() {
+ SecurityContextTestUtils.setUpSecurityContextForClientOnly("APP-6666666666666666", ScopePathType.READ_PUBLIC);
+ Work work = (Work) serviceDelegator.viewWork(ORCID, 11L).getEntity();
+ assertEquals(1, work.getWorkContributors().getContributor().size());
+ assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
+ }
+
+ @Test
+ public void testViewWorkOmitsContributorEmailForAnotherClientReadLimited() {
+ SecurityContextTestUtils.setUpSecurityContext(ORCID, "APP-6666666666666666", ScopePathType.READ_LIMITED);
+ Work work = (Work) serviceDelegator.viewWork(ORCID, 11L).getEntity();
+ assertEquals(1, work.getWorkContributors().getContributor().size());
+ assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
+ }
+
@Test
public void testViewWorkSummaryReadPublic() {
SecurityContextTestUtils.setUpSecurityContextForClientOnly("APP-5555555555555555", ScopePathType.READ_PUBLIC);
@@ -716,6 +734,9 @@ public void testViewBulkWorks() {
assertEquals("/0000-0000-0000-0003/work/11", ((Work) workBulk.getBulk().get(0)).getPath());
assertEquals("/0000-0000-0000-0003/work/12", ((Work) workBulk.getBulk().get(1)).getPath());
assertEquals("/0000-0000-0000-0003/work/13", ((Work) workBulk.getBulk().get(2)).getPath());
+ Work work11 = (Work) workBulk.getBulk().get(0);
+ assertEquals(1, work11.getWorkContributors().getContributor().size());
+ assertNull(work11.getWorkContributors().getContributor().get(0).getContributorEmail());
}
@Test
diff --git a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV2.java b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV2.java
index 4fa7f592d11..000e570c0ac 100644
--- a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV2.java
+++ b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV2.java
@@ -1,13 +1,21 @@
package org.orcid.core.adapter.mapstruct;
+import java.util.HashMap;
+import java.util.Map;
+
import org.apache.commons.lang3.StringUtils;
+import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
import org.orcid.core.manager.IdentityProviderManager;
import org.orcid.core.manager.impl.OrcidUrlManager;
+import org.orcid.core.utils.JsonUtils;
import org.orcid.jaxb.model.notification.amended_v2.NotificationAmended;
import org.orcid.jaxb.model.notification.permission_v2.AuthorizationUrl;
import org.orcid.jaxb.model.notification.permission_v2.Item;
+import org.orcid.jaxb.model.notification.permission_v2.Items;
import org.orcid.jaxb.model.notification.permission_v2.NotificationPermission;
import org.orcid.jaxb.model.v3.release.notification.amended.AmendedSection;
import org.orcid.model.notification.institutional_sign_in_v2.NotificationInstitutionalConnection;
@@ -16,11 +24,51 @@
import org.orcid.persistence.jpa.entities.NotificationInstitutionalConnectionEntity;
import org.orcid.persistence.jpa.entities.NotificationItemEntity;
-@Mapper
+@Mapper(componentModel = "spring", uses = { ExternalIdentifierTypeMapper.class, UrlMapperV2.class })
public interface NotificationMapperV2 {
NotificationMapperV2 INSTANCE = Mappers.getMapper(NotificationMapperV2.class);
+ @Mapping(target = "id", ignore = true)
+ @Mapping(source = "externalIdentifier.type", target = "externalIdType", qualifiedByName = "apiToDb")
+ @Mapping(source = "externalIdentifier.value", target = "externalIdValue")
+ @Mapping(source = "externalIdentifier.url", target = "externalIdUrl")
+ @Mapping(source = "externalIdentifier.relationship", target = "externalIdRelationship")
+ @Mapping(source = "additionalInfo", target = "additionalInfo")
+ NotificationItemEntity toNotificationItemEntity(Item item);
+
+ @Mapping(source = "id", target = "putCode")
+ @Mapping(source = "externalIdType", target = "externalIdentifier.type", qualifiedByName = "dbToApi")
+ @Mapping(source = "externalIdValue", target = "externalIdentifier.value")
+ @Mapping(source = "externalIdUrl", target = "externalIdentifier.url")
+ @Mapping(source = "externalIdRelationship", target = "externalIdentifier.relationship")
+ @Mapping(source = "additionalInfo", target = "additionalInfo")
+ Item toItem(NotificationItemEntity entity);
+
+ @AfterMapping
+ default void afterToItem(NotificationItemEntity entity, @MappingTarget Item item) {
+ if (StringUtils.isBlank(entity.getExternalIdType()) && StringUtils.isBlank(entity.getExternalIdValue())
+ && StringUtils.isBlank(entity.getExternalIdUrl()) && StringUtils.isBlank(entity.getExternalIdRelationship())) {
+ item.setExternalIdentifier(null);
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ default String mapAdditionalInfo(Map map) {
+ if (map == null || map.isEmpty()) {
+ return null;
+ }
+ return JsonUtils.convertToJsonString(map);
+ }
+
+ @SuppressWarnings("rawtypes")
+ default Map mapAdditionalInfo(String json) {
+ if (StringUtils.isBlank(json)) {
+ return null;
+ }
+ return JsonUtils.readObjectFromJsonString(json, HashMap.class);
+ }
+
default String buildAuthorizationUrlIfBlank(String existingUrl, String path, OrcidUrlManager orcidUrlManager) {
if (StringUtils.isBlank(existingUrl)) {
return orcidUrlManager.getBaseUrl() + path;
@@ -34,6 +82,9 @@ default void mapPermissionBtoA(NotificationAddItemsEntity entity, NotificationPe
authUrl.setPath(fullPath);
authUrl.setHost(baseHost);
}
+ if (notification.getItems() == null) {
+ notification.setItems(new Items());
+ }
}
default void mapInstitutionalBtoA(NotificationInstitutionalConnectionEntity entity, NotificationInstitutionalConnection notification, String fullPath,
@@ -66,7 +117,6 @@ default void mapAmendedAtoB(NotificationAmended model, NotificationAmendedEntity
if (model.getAmendedSection() == null) {
return;
}
-
switch (model.getAmendedSection()) {
case AFFILIATION:
entity.setAmendedSection(AmendedSection.AFFILIATION.name());
@@ -108,6 +158,9 @@ default void mapAmendedAtoB(NotificationAmended model, NotificationAmendedEntity
}
default void mapAmendedBtoA(NotificationAmendedEntity entity, NotificationAmended model) {
+ if (model.getItems() == null) {
+ model.setItems(new Items());
+ }
if (entity.getAmendedSection() == null) {
return;
}
diff --git a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV3.java b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV3.java
index dcacdd647d8..6951f11afe8 100644
--- a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV3.java
+++ b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/NotificationMapperV3.java
@@ -1,24 +1,75 @@
package org.orcid.core.adapter.mapstruct;
+import java.util.HashMap;
+import java.util.Map;
+
import org.apache.commons.lang3.StringUtils;
+import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
import org.orcid.core.manager.IdentityProviderManager;
+import org.orcid.core.utils.JsonUtils;
+import org.orcid.jaxb.model.v3.release.notification.amended.NotificationAmended;
import org.orcid.jaxb.model.v3.release.notification.permission.AuthorizationUrl;
import org.orcid.jaxb.model.v3.release.notification.permission.Item;
+import org.orcid.jaxb.model.v3.release.notification.permission.Items;
import org.orcid.jaxb.model.v3.release.notification.permission.NotificationPermission;
import org.orcid.model.v3.release.notification.institutional_sign_in.NotificationInstitutionalConnection;
import org.orcid.model.v3.release.notification.internal.NotificationFindMyStuff;
import org.orcid.persistence.jpa.entities.NotificationAddItemsEntity;
+import org.orcid.persistence.jpa.entities.NotificationAmendedEntity;
import org.orcid.persistence.jpa.entities.NotificationFindMyStuffEntity;
import org.orcid.persistence.jpa.entities.NotificationInstitutionalConnectionEntity;
import org.orcid.persistence.jpa.entities.NotificationItemEntity;
+import org.orcid.pojo.ajaxForm.PojoUtil;
-@Mapper
+@Mapper(componentModel = "spring", uses = { ExternalIdentifierTypeMapper.class, UrlMapperV3.class })
public interface NotificationMapperV3 {
NotificationMapperV3 INSTANCE = Mappers.getMapper(NotificationMapperV3.class);
+ @Mapping(target = "id", ignore = true)
+ @Mapping(source = "externalIdentifier.type", target = "externalIdType", qualifiedByName = "apiToDb")
+ @Mapping(source = "externalIdentifier.value", target = "externalIdValue")
+ @Mapping(source = "externalIdentifier.url", target = "externalIdUrl")
+ @Mapping(source = "externalIdentifier.relationship", target = "externalIdRelationship")
+ @Mapping(source = "additionalInfo", target = "additionalInfo")
+ NotificationItemEntity toNotificationItemEntity(Item item);
+
+ @Mapping(source = "id", target = "putCode")
+ @Mapping(source = "externalIdType", target = "externalIdentifier.type", qualifiedByName = "dbToApi")
+ @Mapping(source = "externalIdValue", target = "externalIdentifier.value")
+ @Mapping(source = "externalIdUrl", target = "externalIdentifier.url")
+ @Mapping(source = "externalIdRelationship", target = "externalIdentifier.relationship")
+ @Mapping(source = "additionalInfo", target = "additionalInfo")
+ Item toItem(NotificationItemEntity entity);
+
+ @AfterMapping
+ default void afterToItem(NotificationItemEntity entity, @MappingTarget Item item) {
+ if (StringUtils.isBlank(entity.getExternalIdType()) && StringUtils.isBlank(entity.getExternalIdValue())
+ && StringUtils.isBlank(entity.getExternalIdUrl()) && StringUtils.isBlank(entity.getExternalIdRelationship())) {
+ item.setExternalIdentifier(null);
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ default String mapAdditionalInfo(Map map) {
+ if (map == null || map.isEmpty()) {
+ return null;
+ }
+ return JsonUtils.convertToJsonString(map);
+ }
+
+ @SuppressWarnings("rawtypes")
+ default Map mapAdditionalInfo(String json) {
+ if (PojoUtil.isEmpty(json)) {
+ return null;
+ }
+ return JsonUtils.readObjectFromJsonString(json, HashMap.class);
+ }
+
default String buildAuthorizationUrlIfBlank(String existingUrl, String path, String baseUrl) {
if (StringUtils.isBlank(existingUrl)) {
return baseUrl + path;
@@ -32,6 +83,9 @@ default void mapPermissionBtoA(NotificationAddItemsEntity entity, NotificationPe
authUrl.setPath(fullPath);
authUrl.setHost(baseHost);
}
+ if (notification.getItems() == null) {
+ notification.setItems(new Items());
+ }
}
default void mapInstitutionalBtoA(NotificationInstitutionalConnectionEntity entity, NotificationInstitutionalConnection notification, String fullPath,
@@ -50,6 +104,12 @@ default void mapInstitutionalBtoA(NotificationInstitutionalConnectionEntity enti
}
}
+ default void mapAmendedBtoA(NotificationAmendedEntity entity, NotificationAmended notification) {
+ if (notification.getItems() == null) {
+ notification.setItems(new Items());
+ }
+ }
+
default void mapFindMyStuffAtoB(NotificationFindMyStuff notification, NotificationFindMyStuffEntity entity, String existingAuthorizationUrl,
String builtAuthorizationUrl) {
if (StringUtils.isBlank(existingAuthorizationUrl)) {
diff --git a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/impl/JpaJaxbNotificationAdapterImpl.java b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/impl/JpaJaxbNotificationAdapterImpl.java
index 5a8bf94f267..a5bb5663381 100644
--- a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/impl/JpaJaxbNotificationAdapterImpl.java
+++ b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/impl/JpaJaxbNotificationAdapterImpl.java
@@ -22,7 +22,9 @@
import org.orcid.core.adapter.JpaJaxbNotificationAdapter;
import org.orcid.core.adapter.mapstruct.ExternalIdentifierTypeMapper;
+import org.orcid.core.adapter.mapstruct.NotificationMapperV2;
import org.orcid.core.adapter.mapstruct.SourceMapperV2;
+import org.orcid.core.adapter.mapstruct.UrlMapperV2;
import org.orcid.core.exception.OrcidValidationException;
import org.orcid.core.manager.IdentityProviderManager;
import org.orcid.core.manager.impl.OrcidUrlManager;
@@ -34,6 +36,7 @@
import org.orcid.jaxb.model.notification.custom_v2.NotificationTip;
import org.orcid.jaxb.model.notification.permission_v2.AuthorizationUrl;
import org.orcid.jaxb.model.notification.permission_v2.Item;
+import org.orcid.jaxb.model.notification.permission_v2.Items;
import org.orcid.jaxb.model.notification.permission_v2.NotificationPermission;
import org.orcid.jaxb.model.notification_v2.Notification;
import org.orcid.model.notification.institutional_sign_in_v2.NotificationInstitutionalConnection;
@@ -44,12 +47,15 @@
*/
@Mapper(
componentModel = "spring",
- uses = {SourceMapperV2.class, ExternalIdentifierTypeMapper.class}
+ uses = {SourceMapperV2.class, NotificationMapperV2.class, ExternalIdentifierTypeMapper.class, UrlMapperV2.class}
)
public abstract class JpaJaxbNotificationAdapterImpl implements JpaJaxbNotificationAdapter {
private static final String LAST_RESORT_IDENTITY_PROVIDER_NAME = "identity provider";
+ @Autowired
+ protected NotificationMapperV2 notificationMapperV2;
+
@Autowired
protected OrcidUrlManager orcidUrlManager;
@@ -158,11 +164,11 @@ protected void afterMapPermission(NotificationPermission n, @MappingTarget Notif
@AfterMapping
protected void afterMapPermissionEntity(NotificationAddItemsEntity entity, @MappingTarget NotificationPermission n) {
- AuthorizationUrl authUrl = n.getAuthorizationUrl();
- if (authUrl != null && authUrl.getUri() != null) {
- authUrl.setPath(extractFullPath(authUrl.getUri()));
- authUrl.setHost(orcidUrlManager.getBaseHost());
+ String fullPath = null;
+ if (n.getAuthorizationUrl() != null && n.getAuthorizationUrl().getUri() != null) {
+ fullPath = extractFullPath(n.getAuthorizationUrl().getUri());
}
+ notificationMapperV2.mapPermissionBtoA(entity, n, fullPath, orcidUrlManager.getBaseHost());
}
// 6. Notification Institutional Connection
@@ -188,71 +194,36 @@ protected void afterMapInstitutionalConnection(NotificationInstitutionalConnecti
@AfterMapping
protected void afterMapInstitutionalConnectionEntity(NotificationInstitutionalConnectionEntity entity, @MappingTarget NotificationInstitutionalConnection n) {
- AuthorizationUrl authUrl = n.getAuthorizationUrl();
- if (authUrl != null && authUrl.getUri() != null) {
- authUrl.setPath(extractFullPath(authUrl.getUri()));
- authUrl.setHost(orcidUrlManager.getBaseHost());
- }
-
- String providerId = entity.getAuthenticationProviderId();
- if (StringUtils.isNotBlank(providerId)) {
- String idpName = identityProviderManager.retrieveIdentitifyProviderName(providerId);
- n.setIdpName(idpName);
- } else {
- n.setIdpName(LAST_RESORT_IDENTITY_PROVIDER_NAME);
+ String fullPath = null;
+ if (n.getAuthorizationUrl() != null && n.getAuthorizationUrl().getUri() != null) {
+ fullPath = extractFullPath(n.getAuthorizationUrl().getUri());
}
+ notificationMapperV2.mapInstitutionalBtoA(entity, n, fullPath, orcidUrlManager.getBaseHost(), identityProviderManager,
+ LAST_RESORT_IDENTITY_PROVIDER_NAME);
}
// 7. Notification Amended
@Mapping(source = "putCode", target = "id")
@Mapping(target = "dateCreated", ignore = true)
@Mapping(source = "items.items", target = "notificationItems")
- @Mapping(source = "amendedSection", target = "amendedSection")
+ @Mapping(target = "amendedSection", ignore = true)
protected abstract NotificationAmendedEntity map(NotificationAmended n);
@Mapping(source = "id", target = "putCode")
@Mapping(source = "dateCreated", target = "createdDate")
@Mapping(source = "notificationItems", target = "items.items")
- @Mapping(source = "amendedSection", target = "amendedSection")
+ @Mapping(target = "amendedSection", ignore = true)
@Mapping(source = ".", target = "source")
protected abstract NotificationAmended map(NotificationAmendedEntity e);
- // Custom AmendedSection Enum Converters
- protected String mapAmendedSection(org.orcid.jaxb.model.notification.amended_v2.AmendedSection section) {
- if (section == null) return org.orcid.jaxb.model.v3.release.notification.amended.AmendedSection.UNKNOWN.name();
- return section.name();
- }
-
- protected org.orcid.jaxb.model.notification.amended_v2.AmendedSection mapAmendedSection(String section) {
- if (StringUtils.isBlank(section)) return org.orcid.jaxb.model.notification.amended_v2.AmendedSection.UNKNOWN;
- try {
- return org.orcid.jaxb.model.notification.amended_v2.AmendedSection.valueOf(section);
- } catch (IllegalArgumentException e) {
- return org.orcid.jaxb.model.notification.amended_v2.AmendedSection.AFFILIATION;
- }
- }
-
- // Notification Items Mapping
- @Mapping(source = "externalIdentifier.type", target = "externalIdType", qualifiedByName = "apiToDb")
- @Mapping(source = "externalIdentifier.value", target = "externalIdValue")
- @Mapping(source = "additionalInfo", target = "additionalInfo")
- protected abstract NotificationItemEntity mapItem(Item item);
-
- @Mapping(source = "externalIdType", target = "externalIdentifier.type", qualifiedByName = "dbToApi")
- @Mapping(source = "externalIdValue", target = "externalIdentifier.value")
- @Mapping(source = "additionalInfo", target = "additionalInfo")
- protected abstract Item mapItem(NotificationItemEntity entity);
-
- @SuppressWarnings("rawtypes")
- protected String mapAdditionalInfo(Map map) {
- if (map == null || map.isEmpty()) return null;
- return JsonUtils.convertToJsonString(map);
+ @AfterMapping
+ protected void afterMapAmended(NotificationAmended n, @MappingTarget NotificationAmendedEntity entity) {
+ notificationMapperV2.mapAmendedAtoB(n, entity);
}
- @SuppressWarnings("rawtypes")
- protected Map mapAdditionalInfo(String json) {
- if (StringUtils.isBlank(json)) return null;
- return JsonUtils.readObjectFromJsonString(json, HashMap.class);
+ @AfterMapping
+ protected void afterMapAmendedEntity(NotificationAmendedEntity entity, @MappingTarget NotificationAmended n) {
+ notificationMapperV2.mapAmendedBtoA(entity, n);
}
// XMLGregorianCalendar <-> java.util.Date Converters
diff --git a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/v3/impl/JpaJaxbNotificationAdapterImpl.java b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/v3/impl/JpaJaxbNotificationAdapterImpl.java
index 89c319150b9..a0b26be42b5 100644
--- a/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/v3/impl/JpaJaxbNotificationAdapterImpl.java
+++ b/orcid-core/src/main/java/org/orcid/core/adapter/mapstruct/v3/impl/JpaJaxbNotificationAdapterImpl.java
@@ -20,10 +20,11 @@
import org.mapstruct.MappingTarget;
import org.springframework.beans.factory.annotation.Autowired;
+import org.orcid.core.adapter.v3.JpaJaxbNotificationAdapter;
import org.orcid.core.adapter.mapstruct.ExternalIdentifierTypeMapper;
+import org.orcid.core.adapter.mapstruct.NotificationMapperV3;
import org.orcid.core.adapter.mapstruct.SourceMapperV3;
import org.orcid.core.adapter.mapstruct.UrlMapperV3;
-import org.orcid.core.adapter.v3.JpaJaxbNotificationAdapter;
import org.orcid.core.exception.OrcidValidationException;
import org.orcid.core.manager.IdentityProviderManager;
import org.orcid.core.manager.impl.OrcidUrlManager;
@@ -36,6 +37,7 @@
import org.orcid.jaxb.model.v3.release.notification.custom.NotificationTip;
import org.orcid.jaxb.model.v3.release.notification.permission.AuthorizationUrl;
import org.orcid.jaxb.model.v3.release.notification.permission.Item;
+import org.orcid.jaxb.model.v3.release.notification.permission.Items;
import org.orcid.jaxb.model.v3.release.notification.permission.NotificationPermission;
import org.orcid.model.v3.release.notification.institutional_sign_in.NotificationInstitutionalConnection;
import org.orcid.model.v3.release.notification.internal.NotificationFindMyStuff;
@@ -53,12 +55,15 @@
@Mapper(
componentModel = "spring",
- uses = {SourceMapperV3.class, ExternalIdentifierTypeMapper.class, UrlMapperV3.class}
+ uses = {SourceMapperV3.class, NotificationMapperV3.class, ExternalIdentifierTypeMapper.class, UrlMapperV3.class}
)
public abstract class JpaJaxbNotificationAdapterImpl implements JpaJaxbNotificationAdapter {
private static final String LAST_RESORT_IDENTITY_PROVIDER_NAME = "identity provider";
+ @Autowired
+ protected NotificationMapperV3 notificationMapperV3;
+
@Autowired
protected OrcidUrlManager orcidUrlManager;
@@ -169,11 +174,11 @@ protected void afterMapPermission(NotificationPermission n, @MappingTarget Notif
@AfterMapping
protected void afterMapPermissionEntity(NotificationAddItemsEntity entity, @MappingTarget NotificationPermission n) {
- AuthorizationUrl authUrl = n.getAuthorizationUrl();
- if (authUrl != null && authUrl.getUri() != null) {
- authUrl.setPath(extractFullPath(authUrl.getUri()));
- authUrl.setHost(orcidUrlManager.getBaseHost());
+ String fullPath = null;
+ if (n.getAuthorizationUrl() != null && n.getAuthorizationUrl().getUri() != null) {
+ fullPath = extractFullPath(n.getAuthorizationUrl().getUri());
}
+ notificationMapperV3.mapPermissionBtoA(entity, n, fullPath, orcidUrlManager.getBaseHost());
}
// 6. Notification Institutional Connection
@@ -199,19 +204,12 @@ protected void afterMapInstitutionalConnection(NotificationInstitutionalConnecti
@AfterMapping
protected void afterMapInstitutionalConnectionEntity(NotificationInstitutionalConnectionEntity entity, @MappingTarget NotificationInstitutionalConnection n) {
- AuthorizationUrl authUrl = n.getAuthorizationUrl();
- if (authUrl != null && authUrl.getUri() != null) {
- authUrl.setPath(extractFullPath(authUrl.getUri()));
- authUrl.setHost(orcidUrlManager.getBaseHost());
- }
-
- String providerId = entity.getAuthenticationProviderId();
- if (StringUtils.isNotBlank(providerId)) {
- String idpName = identityProviderManager.retrieveIdentitifyProviderName(providerId);
- n.setIdpName(idpName);
- } else {
- n.setIdpName(LAST_RESORT_IDENTITY_PROVIDER_NAME);
+ String fullPath = null;
+ if (n.getAuthorizationUrl() != null && n.getAuthorizationUrl().getUri() != null) {
+ fullPath = extractFullPath(n.getAuthorizationUrl().getUri());
}
+ notificationMapperV3.mapInstitutionalBtoA(entity, n, fullPath, orcidUrlManager.getBaseHost(), identityProviderManager,
+ LAST_RESORT_IDENTITY_PROVIDER_NAME);
}
// 7. Notification Amended
@@ -228,6 +226,11 @@ protected void afterMapInstitutionalConnectionEntity(NotificationInstitutionalCo
@Mapping(source = ".", target = "source")
protected abstract NotificationAmended map(NotificationAmendedEntity e);
+ @AfterMapping
+ protected void afterMapAmendedEntity(NotificationAmendedEntity entity, @MappingTarget NotificationAmended n) {
+ notificationMapperV3.mapAmendedBtoA(entity, n);
+ }
+
// 8. Notification Find My Stuff (V3 specific)
@Mapping(source = "putCode", target = "id")
@Mapping(target = "dateCreated", ignore = true)
@@ -247,17 +250,16 @@ protected void afterMapFindMyStuff(NotificationFindMyStuff n, @MappingTarget Not
validateAndConvertToURI(authUrl);
entity.setAuthorizationUrl(authUrl);
}
- entity.setAuthenticationProviderId(n.getServiceProviderId());
+ notificationMapperV3.mapFindMyStuffAtoB(n, entity, entity.getAuthorizationUrl(), entity.getAuthorizationUrl());
}
@AfterMapping
protected void afterMapFindMyStuffEntity(NotificationFindMyStuffEntity entity, @MappingTarget NotificationFindMyStuff n) {
- AuthorizationUrl authUrl = n.getAuthorizationUrl();
- if (authUrl != null && authUrl.getUri() != null) {
- authUrl.setPath(extractFullPath(authUrl.getUri()));
- authUrl.setHost(orcidUrlManager.getBaseHost());
+ String fullPath = null;
+ if (n.getAuthorizationUrl() != null && n.getAuthorizationUrl().getUri() != null) {
+ fullPath = extractFullPath(n.getAuthorizationUrl().getUri());
}
- n.setServiceProviderId(entity.getAuthenticationProviderId());
+ notificationMapperV3.mapFindMyStuffBtoA(entity, n, fullPath, orcidUrlManager.getBaseHost());
}
// Custom AmendedSection Enum Converters
@@ -275,33 +277,6 @@ protected org.orcid.jaxb.model.v3.release.notification.amended.AmendedSection ma
}
}
- // Notification Items Mapping
- @Mapping(source = "externalIdentifier.type", target = "externalIdType", qualifiedByName = "apiToDb")
- @Mapping(source = "externalIdentifier.value", target = "externalIdValue")
- @Mapping(source = "externalIdentifier.url", target = "externalIdUrl")
- @Mapping(source = "externalIdentifier.relationship", target = "externalIdRelationship")
- @Mapping(source = "additionalInfo", target = "additionalInfo")
- protected abstract NotificationItemEntity mapItem(Item item);
-
- @Mapping(source = "externalIdType", target = "externalIdentifier.type", qualifiedByName = "dbToApi")
- @Mapping(source = "externalIdValue", target = "externalIdentifier.value")
- @Mapping(source = "externalIdUrl", target = "externalIdentifier.url")
- @Mapping(source = "externalIdRelationship", target = "externalIdentifier.relationship")
- @Mapping(source = "additionalInfo", target = "additionalInfo")
- protected abstract Item mapItem(NotificationItemEntity entity);
-
- @SuppressWarnings("rawtypes")
- protected String mapAdditionalInfo(Map map) {
- if (map == null || map.isEmpty()) return null;
- return JsonUtils.convertToJsonString(map);
- }
-
- @SuppressWarnings("rawtypes")
- protected Map mapAdditionalInfo(String json) {
- if (PojoUtil.isEmpty(json)) return null;
- return JsonUtils.readObjectFromJsonString(json, HashMap.class);
- }
-
// XMLGregorianCalendar <-> java.util.Date Converters
protected Date xmlGregorianCalendarToDate(XMLGregorianCalendar cal) {
if (cal == null) {
diff --git a/orcid-core/src/main/java/org/orcid/core/utils/ContributorUtils.java b/orcid-core/src/main/java/org/orcid/core/utils/ContributorUtils.java
index 1741b214190..3eb21d22592 100644
--- a/orcid-core/src/main/java/org/orcid/core/utils/ContributorUtils.java
+++ b/orcid-core/src/main/java/org/orcid/core/utils/ContributorUtils.java
@@ -8,8 +8,11 @@
import org.orcid.jaxb.model.common_v2.Contributor;
import org.orcid.jaxb.model.common_v2.ContributorAttributes;
import org.orcid.jaxb.model.common_v2.CreditName;
+import org.orcid.jaxb.model.record.bulk.BulkElement;
import org.orcid.jaxb.model.record_v2.Funding;
import org.orcid.jaxb.model.record_v2.FundingContributor;
+import org.orcid.jaxb.model.record_v2.Work;
+import org.orcid.jaxb.model.record_v2.WorkBulk;
import org.orcid.persistence.dao.RecordNameDao;
import org.orcid.pojo.ContributorsRolesAndSequencesV2;
import org.orcid.pojo.ajaxForm.PojoUtil;
@@ -55,6 +58,29 @@ public void filterContributorPrivateData(Funding funding) {
}
}
+ /**
+ * The contributor email is write-only (work-3.0.xsd): it is never returned
+ * when reading a work. Unlike the Funding overload, credit names are left
+ * as deposited.
+ */
+ public void filterContributorPrivateData(Work work) {
+ if (work != null && work.getWorkContributors() != null && work.getWorkContributors().getContributor() != null) {
+ for (Contributor contributor : work.getWorkContributors().getContributor()) {
+ contributor.setContributorEmail(null);
+ }
+ }
+ }
+
+ public void filterContributorPrivateData(WorkBulk works) {
+ if (works != null && works.getBulk() != null) {
+ for (BulkElement element : works.getBulk()) {
+ if (element instanceof Work) {
+ filterContributorPrivateData((Work) element);
+ }
+ }
+ }
+ }
+
public List getContributorsGroupedByOrcid(List contributors, Integer maxContributorsForUI) {
List contributorsRolesAndSequencesList = new ArrayList<>();
for(Contributor contributor : contributors) {
diff --git a/orcid-core/src/main/java/org/orcid/core/utils/v3/ContributorUtils.java b/orcid-core/src/main/java/org/orcid/core/utils/v3/ContributorUtils.java
index f6c4248b3cc..98305a5a18d 100644
--- a/orcid-core/src/main/java/org/orcid/core/utils/v3/ContributorUtils.java
+++ b/orcid-core/src/main/java/org/orcid/core/utils/v3/ContributorUtils.java
@@ -4,11 +4,14 @@
import org.orcid.core.contributors.roles.credit.CreditRole;
import org.orcid.core.manager.v3.ProfileEntityManager;
import org.orcid.core.manager.v3.read_only.RecordNameManagerReadOnly;
+import org.orcid.jaxb.model.record.bulk.BulkElement;
import org.orcid.jaxb.model.v3.release.common.Contributor;
import org.orcid.jaxb.model.v3.release.common.ContributorAttributes;
import org.orcid.jaxb.model.v3.release.common.CreditName;
import org.orcid.jaxb.model.v3.release.record.Funding;
import org.orcid.jaxb.model.v3.release.record.FundingContributor;
+import org.orcid.jaxb.model.v3.release.record.Work;
+import org.orcid.jaxb.model.v3.release.record.WorkBulk;
import org.orcid.pojo.ContributorsRolesAndSequences;
import org.orcid.pojo.ajaxForm.PojoUtil;
@@ -43,6 +46,29 @@ public void filterContributorPrivateData(Funding funding) {
}
}
+ /**
+ * The contributor email is write-only (work-3.0.xsd): it is never returned
+ * when reading a work. Unlike the Funding overload, credit names are left
+ * as deposited.
+ */
+ public void filterContributorPrivateData(Work work) {
+ if (work != null && work.getWorkContributors() != null && work.getWorkContributors().getContributor() != null) {
+ for (Contributor contributor : work.getWorkContributors().getContributor()) {
+ contributor.setContributorEmail(null);
+ }
+ }
+ }
+
+ public void filterContributorPrivateData(WorkBulk works) {
+ if (works != null && works.getBulk() != null) {
+ for (BulkElement element : works.getBulk()) {
+ if (element instanceof Work) {
+ filterContributorPrivateData((Work) element);
+ }
+ }
+ }
+ }
+
public List getContributorsGroupedByOrcid(List contributors, Integer maxContributorsForUI) {
List contributorsRolesAndSequencesList = new ArrayList<>();
for(Contributor contributor : contributors) {
diff --git a/orcid-core/src/main/resources/org/orcid/core/template/digest_email.ftl b/orcid-core/src/main/resources/org/orcid/core/template/digest_email.ftl
index d6a49feefcb..74448651abf 100644
--- a/orcid-core/src/main/resources/org/orcid/core/template/digest_email.ftl
+++ b/orcid-core/src/main/resources/org/orcid/core/template/digest_email.ftl
@@ -19,6 +19,7 @@
<#if notificationType == 'PERMISSION'>
${(digestEmail.notificationsBySourceId[sourceId].source.sourceName.content)!sourceId}: <#if notification.notificationSubject??>${notification.notificationSubject} <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}#if>-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c}#if>)#if><#else><@emailMacros.msg "email.digest.requesttoadd" /> <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}#if>-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c}#if>)#if>#if>
+<#if notification.items?? && notification.items.itemsByType?has_content>
<#assign itemsByType=notification.items.itemsByType>
<#list itemsByType?keys?sort as itemType>
<@emailMacros.msg "email.common.recordsection." + itemType /> (${itemsByType[itemType]?size})
@@ -29,6 +30,10 @@ ${(digestEmail.notificationsBySourceId[sourceId].source.sourceName.content)!sour
<@emailMacros.msg "email.digest.plaintext.addnow" /><@emailMacros.space />${baseUri}/inbox/encrypted/${notification.encryptedPutCode}/action
<@emailMacros.msg "email.digest.plaintext.moreinfo" /><@emailMacros.space />${baseUri}/inbox#${notification.putCode}
#list>
+<#else>
+<@emailMacros.msg "email.digest.plaintext.addnow" /><@emailMacros.space />${baseUri}/inbox/encrypted/${notification.encryptedPutCode}/action
+<@emailMacros.msg "email.digest.plaintext.moreinfo" /><@emailMacros.space />${baseUri}/inbox#${notification.putCode}
+#if>
<#elseif notificationType == 'AMENDED' && !verboseNotifications>
<#assign amendedSection><@emailMacros.msg "email.common.recordsection." + notification.amendedSection />#assign>
<@emailMacros.msg "email.digest.hasupdated_1" />
diff --git a/orcid-core/src/main/resources/org/orcid/core/template/digest_email_html.ftl b/orcid-core/src/main/resources/org/orcid/core/template/digest_email_html.ftl
index 0e836a77555..a91034a3a41 100644
--- a/orcid-core/src/main/resources/org/orcid/core/template/digest_email_html.ftl
+++ b/orcid-core/src/main/resources/org/orcid/core/template/digest_email_html.ftl
@@ -32,6 +32,7 @@
<#if notificationType == 'PERMISSION'>

${(digestEmail.notificationsBySourceId[sourceId].source.sourceName.content)!sourceId}: <#if notification.notificationSubject??>${notification.notificationSubject} <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}#if>-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c}#if>)#if> <#else><@emailMacros.msg "email.digest.requesttoadd" /> <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}#if>-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c}#if>)#if> #if>
+ <#if notification.items??>
<#assign itemsByType=notification.items.itemsByType>
<#list itemsByType?keys?sort as itemType>
#list>
+ #if>
<#elseif notificationType == 'AMENDED' && !verboseNotifications>
diff --git a/orcid-core/src/main/resources/org/orcid/core/template/digest_notification.ftl b/orcid-core/src/main/resources/org/orcid/core/template/digest_notification.ftl
index 20a0a05974c..6c2ceddd54a 100644
--- a/orcid-core/src/main/resources/org/orcid/core/template/digest_notification.ftl
+++ b/orcid-core/src/main/resources/org/orcid/core/template/digest_notification.ftl
@@ -39,6 +39,7 @@ ${'\n'}${emailName}<@emailMacros.space /><@emailMacros.msg "notification.digest.
<#else>
<#if notification.notificationIntro??>${notification.notificationIntro}#if>
${(digestEmail.notificationsBySourceId[sourceId].source.sourceName.content)!sourceId}: <#if notification.notificationSubject??>${notification.notificationSubject} <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}#if>-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c}#if>)#if><#else><@emailMacros.msg "email.digest.requesttoadd" /> <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}#if>-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c}#if>)#if>#if>
+ <#if notification.items?? && notification.items.itemsByType?has_content>
<#assign itemsByType=notification.items.itemsByType>
<#list itemsByType?keys?sort as itemType>
<@emailMacros.msg "email.common.recordsection." + itemType /> (${itemsByType[itemType]?size})
@@ -49,6 +50,10 @@ ${'\n'}${emailName}<@emailMacros.space /><@emailMacros.msg "notification.digest.
<@emailMacros.msg "email.digest.plaintext.addnow" /><@emailMacros.space />${baseUri}/inbox/encrypted/${notification.encryptedPutCode}/action
<@emailMacros.msg "email.digest.plaintext.moreinfo" /><@emailMacros.space />${baseUri}/inbox#${notification.putCode}
#list>
+ <#else>
+ <@emailMacros.msg "email.digest.plaintext.addnow" /><@emailMacros.space />${baseUri}/inbox/encrypted/${notification.encryptedPutCode}/action
+ <@emailMacros.msg "email.digest.plaintext.moreinfo" /><@emailMacros.space />${baseUri}/inbox#${notification.putCode}
+ #if>
#if>
<#elseif notificationType == 'AMENDED' && !verboseNotifications>
<#assign amendedSection><@emailMacros.msg "email.common.recordsection." + notification.amendedSection />#assign>
diff --git a/orcid-core/src/main/resources/org/orcid/core/template/digest_notification_html.ftl b/orcid-core/src/main/resources/org/orcid/core/template/digest_notification_html.ftl
index 68b792b10d8..26e7d1f6276 100644
--- a/orcid-core/src/main/resources/org/orcid/core/template/digest_notification_html.ftl
+++ b/orcid-core/src/main/resources/org/orcid/core/template/digest_notification_html.ftl
@@ -110,6 +110,7 @@
<#else>
<#if notification.notificationIntro??>
${notification.notificationIntro}
#if>
<#if notification.notificationSubject??>${notification.notificationSubject} <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}#if>-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c}#if>)#if><#else><@emailMacros.msg "email.digest.requesttoadd" /> <#if notification.createdDate??>(${notification.createdDate.year?c}-<#if notification.createdDate.month?string?length == 1>0${notification.createdDate.month?c}<#else>${notification.createdDate.month?c}#if>-<#if notification.createdDate.day?string?length == 1>0${notification.createdDate.day?c}<#else>${notification.createdDate.day?c}#if>)#if>#if>
+ <#if notification.items??>
<#assign itemsByType=notification.items.itemsByType>
<#list itemsByType?keys?sort as itemType>
@@ -125,6 +126,7 @@
#list>
+ #if>
ItemType.WORK.equals(i.getItemType()))
+ .findFirst().orElse(null);
+ assertNotNull(item1);
+ assertEquals("101", item1.getPutCode());
+ assertEquals("Work Title", item1.getItemName());
+ assertNotNull(item1.getExternalIdentifier());
+ assertEquals("doi", item1.getExternalIdentifier().getType());
+ assertEquals("10.1234/test", item1.getExternalIdentifier().getValue());
+ assertEquals("https://doi.org/10.1234/test", item1.getExternalIdentifier().getUrl().getValue());
+ assertEquals(org.orcid.jaxb.model.record_v2.Relationship.SELF, item1.getExternalIdentifier().getRelationship());
+
+ Item item2 = permissionNotification.getItems().getItems().stream()
+ .filter(i -> ItemType.EMPLOYMENT.equals(i.getItemType()))
+ .findFirst().orElse(null);
+ assertNotNull(item2);
+ assertEquals("102", item2.getPutCode());
+ assertEquals("University of Testing", item2.getItemName());
+ assertNull(item2.getExternalIdentifier());
+ }
+
@Test
public void testToNotificationPermissionEntity() {
NotificationPermission notification = new NotificationPermission();
diff --git a/orcid-core/src/test/java/org/orcid/core/adapter/v3/JpaJaxbNotificationAdapterTest.java b/orcid-core/src/test/java/org/orcid/core/adapter/v3/JpaJaxbNotificationAdapterTest.java
index 7b7335a99df..c871d2fb326 100644
--- a/orcid-core/src/test/java/org/orcid/core/adapter/v3/JpaJaxbNotificationAdapterTest.java
+++ b/orcid-core/src/test/java/org/orcid/core/adapter/v3/JpaJaxbNotificationAdapterTest.java
@@ -155,6 +155,7 @@ public void testEntityToNotificationWithSource() {
Notification amendedNotification = jpaJaxbNotificationAdapter.toNotification(amendedEntity);
assertNotNull(amendedNotification);
assertTrue(amendedNotification instanceof NotificationAmended);
+ assertNotNull(((NotificationAmended) amendedNotification).getItems());
assertNotNull(amendedNotification.getSource());
assertNotNull(amendedNotification.getSource().getSourceClientId());
assertEquals(CLIENT_SOURCE_ID, amendedNotification.getSource().getSourceClientId().getPath());
@@ -169,6 +170,7 @@ public void testEntityToNotificationWithSource() {
Notification permissionNotification = jpaJaxbNotificationAdapter.toNotification(addItemsEntity);
assertNotNull(permissionNotification);
assertTrue(permissionNotification instanceof NotificationPermission);
+ assertNotNull(((NotificationPermission) permissionNotification).getItems());
assertNotNull(permissionNotification.getSource());
assertNotNull(permissionNotification.getSource().getSourceClientId());
assertEquals(CLIENT_SOURCE_ID, permissionNotification.getSource().getSourceClientId().getPath());
@@ -176,6 +178,60 @@ public void testEntityToNotificationWithSource() {
assertEquals("Client name", permissionNotification.getSource().getSourceName().getContent());
}
+ @Test
+ public void testToNotificationPermissionWithItems() {
+ NotificationAddItemsEntity addItemsEntity = new NotificationAddItemsEntity();
+ addItemsEntity.setId(125L);
+ addItemsEntity.setNotificationType(org.orcid.jaxb.model.notification_v2.NotificationType.PERMISSION.name());
+ addItemsEntity.setClientSourceId(CLIENT_SOURCE_ID);
+ addItemsEntity.setAuthorizationUrl("https://orcid.org/oauth/authorize?client_id=APP-1234");
+
+ NotificationItemEntity itemEntity1 = new NotificationItemEntity();
+ itemEntity1.setId(101L);
+ itemEntity1.setItemType("WORK");
+ itemEntity1.setItemName("Work Title");
+ itemEntity1.setExternalIdType("DOI");
+ itemEntity1.setExternalIdValue("10.1234/test");
+ itemEntity1.setExternalIdUrl("https://doi.org/10.1234/test");
+ itemEntity1.setExternalIdRelationship("SELF");
+
+ NotificationItemEntity itemEntity2 = new NotificationItemEntity();
+ itemEntity2.setId(102L);
+ itemEntity2.setItemType("EMPLOYMENT");
+ itemEntity2.setItemName("University of Testing");
+
+ addItemsEntity.setNotificationItems(java.util.Set.of(itemEntity1, itemEntity2));
+
+ Notification notification = jpaJaxbNotificationAdapter.toNotification(addItemsEntity);
+ assertNotNull(notification);
+ assertTrue(notification instanceof NotificationPermission);
+ NotificationPermission permissionNotification = (NotificationPermission) notification;
+ assertNotNull(permissionNotification.getItems());
+ assertNotNull(permissionNotification.getItems().getItems());
+ assertEquals(2, permissionNotification.getItems().getItems().size());
+ assertEquals(2, permissionNotification.getItems().getItemsByType().size());
+
+ Item item1 = permissionNotification.getItems().getItems().stream()
+ .filter(i -> ItemType.WORK.equals(i.getItemType()))
+ .findFirst().orElse(null);
+ assertNotNull(item1);
+ assertEquals("101", item1.getPutCode());
+ assertEquals("Work Title", item1.getItemName());
+ assertNotNull(item1.getExternalIdentifier());
+ assertEquals("doi", item1.getExternalIdentifier().getType());
+ assertEquals("10.1234/test", item1.getExternalIdentifier().getValue());
+ assertEquals("https://doi.org/10.1234/test", item1.getExternalIdentifier().getUrl().getValue());
+ assertEquals(Relationship.SELF, item1.getExternalIdentifier().getRelationship());
+
+ Item item2 = permissionNotification.getItems().getItems().stream()
+ .filter(i -> ItemType.EMPLOYMENT.equals(i.getItemType()))
+ .findFirst().orElse(null);
+ assertNotNull(item2);
+ assertEquals("102", item2.getPutCode());
+ assertEquals("University of Testing", item2.getItemName());
+ assertNull(item2.getExternalIdentifier());
+ }
+
@Test
public void testToNotificationPermissionEntity() {
NotificationPermission notification = new NotificationPermission();
diff --git a/orcid-core/src/test/java/org/orcid/core/utils/ContributorUtilsTest.java b/orcid-core/src/test/java/org/orcid/core/utils/ContributorUtilsTest.java
index 39864ed1dde..567eec90471 100644
--- a/orcid-core/src/test/java/org/orcid/core/utils/ContributorUtilsTest.java
+++ b/orcid-core/src/test/java/org/orcid/core/utils/ContributorUtilsTest.java
@@ -25,14 +25,18 @@
import org.orcid.core.manager.ProfileEntityCacheManager;
import org.orcid.core.manager.ProfileEntityManager;
import org.orcid.jaxb.model.common_v2.Contributor;
+import org.orcid.jaxb.model.common_v2.ContributorAttributes;
import org.orcid.jaxb.model.common_v2.ContributorEmail;
import org.orcid.jaxb.model.common_v2.ContributorOrcid;
+import org.orcid.jaxb.model.common_v2.ContributorRole;
import org.orcid.jaxb.model.common_v2.CreditName;
import org.orcid.jaxb.model.common_v2.Title;
+import org.orcid.jaxb.model.error_v2.OrcidError;
import org.orcid.jaxb.model.record_v2.Funding;
import org.orcid.jaxb.model.record_v2.FundingContributor;
import org.orcid.jaxb.model.record_v2.FundingContributors;
import org.orcid.jaxb.model.record_v2.FundingTitle;
+import org.orcid.jaxb.model.record_v2.SequenceType;
import org.orcid.jaxb.model.record_v2.Work;
import org.orcid.jaxb.model.record_v2.WorkBulk;
import org.orcid.jaxb.model.record_v2.WorkContributors;
@@ -131,6 +135,60 @@ public void testFilterContributorPrivateDataForFundingWithoutContributors() {
assertNotNull(funding); // test no failures
}
+ @Test
+ public void testFilterContributorPrivateDataForWork() {
+ // A public name is available, so a name replacement would be visible
+ when(profileEntityManager.orcidExists(anyString())).thenReturn(true);
+ when(cacheManager.getPublicCreditName(any(String.class))).thenReturn("a public name");
+
+ Work work = getWorkWithOrcidContributor();
+ ContributorAttributes attributes = new ContributorAttributes();
+ attributes.setContributorRole(ContributorRole.AUTHOR);
+ attributes.setContributorSequence(SequenceType.FIRST);
+ work.getWorkContributors().getContributor().get(0).setContributorAttributes(attributes);
+
+ contributorUtils.filterContributorPrivateData(work);
+
+ Contributor contributor = work.getWorkContributors().getContributor().get(0);
+ assertNull(contributor.getContributorEmail());
+ assertEquals("original credit name", contributor.getCreditName().getContent());
+ assertEquals("0000-0003-4902-6327", contributor.getContributorOrcid().getPath());
+ assertEquals(ContributorRole.AUTHOR, contributor.getContributorAttributes().getContributorRole());
+ assertEquals(SequenceType.FIRST, contributor.getContributorAttributes().getContributorSequence());
+ }
+
+ @Test
+ public void testFilterContributorPrivateDataForWorkWithNoOrcidRecord() {
+ Work work = getWorkWithContributorWithoutOrcid();
+ contributorUtils.filterContributorPrivateData(work);
+
+ Contributor contributor = work.getWorkContributors().getContributor().get(0);
+ assertNull(contributor.getContributorEmail());
+ assertEquals("original credit name", contributor.getCreditName().getContent());
+ }
+
+ @Test
+ public void testFilterContributorPrivateDataForWorkWithoutContributors() {
+ Work work = getWorkWithoutContributors();
+ contributorUtils.filterContributorPrivateData(work);
+ assertNull(work.getWorkContributors());
+ }
+
+ @Test
+ public void testFilterContributorPrivateDataForBulkWork() {
+ OrcidError error = new OrcidError();
+ error.setDeveloperMessage("not a work");
+ WorkBulk bulk = new WorkBulk();
+ bulk.setBulk(new ArrayList<>(Arrays.asList(getWorkWithOrcidContributor(), error, getWorkWithContributorWithoutOrcid())));
+
+ contributorUtils.filterContributorPrivateData(bulk);
+
+ assertEquals(3, bulk.getBulk().size());
+ assertNull(((Work) bulk.getBulk().get(0)).getWorkContributors().getContributor().get(0).getContributorEmail());
+ assertEquals("not a work", ((OrcidError) bulk.getBulk().get(1)).getDeveloperMessage());
+ assertNull(((Work) bulk.getBulk().get(2)).getWorkContributors().getContributor().get(0).getContributorEmail());
+ }
+
private Work getWorkWithoutContributors() {
Work work = new Work();
WorkTitle workTitle = new WorkTitle();
diff --git a/orcid-core/src/test/java/org/orcid/core/utils/v3/ContributorUtilsTest.java b/orcid-core/src/test/java/org/orcid/core/utils/v3/ContributorUtilsTest.java
index 00dcaeb5445..38597193ef7 100644
--- a/orcid-core/src/test/java/org/orcid/core/utils/v3/ContributorUtilsTest.java
+++ b/orcid-core/src/test/java/org/orcid/core/utils/v3/ContributorUtilsTest.java
@@ -25,11 +25,14 @@
import org.orcid.core.manager.v3.ActivityManager;
import org.orcid.core.manager.v3.ProfileEntityManager;
import org.orcid.core.manager.v3.read_only.RecordNameManagerReadOnly;
+import org.orcid.jaxb.model.common.SequenceType;
import org.orcid.jaxb.model.v3.release.common.Contributor;
+import org.orcid.jaxb.model.v3.release.common.ContributorAttributes;
import org.orcid.jaxb.model.v3.release.common.ContributorEmail;
import org.orcid.jaxb.model.v3.release.common.ContributorOrcid;
import org.orcid.jaxb.model.v3.release.common.CreditName;
import org.orcid.jaxb.model.v3.release.common.Title;
+import org.orcid.jaxb.model.v3.release.error.OrcidError;
import org.orcid.jaxb.model.v3.release.record.Funding;
import org.orcid.jaxb.model.v3.release.record.FundingContributor;
import org.orcid.jaxb.model.v3.release.record.FundingContributors;
@@ -130,6 +133,60 @@ public void testFilterContributorPrivateDataForFundingWithoutContributors() {
assertNotNull(funding); // test no failures
}
+ @Test
+ public void testFilterContributorPrivateDataForWork() {
+ // A public name is available, so a name replacement would be visible
+ when(profileEntityManager.orcidExists(anyString())).thenReturn(true);
+ when(recordNameManagerReadOnlyV3.fetchDisplayablePublicName(anyString())).thenReturn("a public name");
+
+ Work work = getWorkWithOrcidContributor();
+ ContributorAttributes attributes = new ContributorAttributes();
+ attributes.setContributorRole("author");
+ attributes.setContributorSequence(SequenceType.FIRST);
+ work.getWorkContributors().getContributor().get(0).setContributorAttributes(attributes);
+
+ contributorUtils.filterContributorPrivateData(work);
+
+ Contributor contributor = work.getWorkContributors().getContributor().get(0);
+ assertNull(contributor.getContributorEmail());
+ assertEquals("original credit name", contributor.getCreditName().getContent());
+ assertEquals("0000-0003-4902-6327", contributor.getContributorOrcid().getPath());
+ assertEquals("author", contributor.getContributorAttributes().getContributorRole());
+ assertEquals(SequenceType.FIRST, contributor.getContributorAttributes().getContributorSequence());
+ }
+
+ @Test
+ public void testFilterContributorPrivateDataForWorkWithNoOrcidRecord() {
+ Work work = getWorkWithContributorWithoutOrcid();
+ contributorUtils.filterContributorPrivateData(work);
+
+ Contributor contributor = work.getWorkContributors().getContributor().get(0);
+ assertNull(contributor.getContributorEmail());
+ assertEquals("original credit name", contributor.getCreditName().getContent());
+ }
+
+ @Test
+ public void testFilterContributorPrivateDataForWorkWithoutContributors() {
+ Work work = getWorkWithoutContributors();
+ contributorUtils.filterContributorPrivateData(work);
+ assertNull(work.getWorkContributors());
+ }
+
+ @Test
+ public void testFilterContributorPrivateDataForBulkWork() {
+ OrcidError error = new OrcidError();
+ error.setDeveloperMessage("not a work");
+ WorkBulk bulk = new WorkBulk();
+ bulk.setBulk(new ArrayList<>(Arrays.asList(getWorkWithOrcidContributor(), error, getWorkWithContributorWithoutOrcid())));
+
+ contributorUtils.filterContributorPrivateData(bulk);
+
+ assertEquals(3, bulk.getBulk().size());
+ assertNull(((Work) bulk.getBulk().get(0)).getWorkContributors().getContributor().get(0).getContributorEmail());
+ assertEquals("not a work", ((OrcidError) bulk.getBulk().get(1)).getDeveloperMessage());
+ assertNull(((Work) bulk.getBulk().get(2)).getWorkContributors().getContributor().get(0).getContributorEmail());
+ }
+
private Work getWorkWithoutContributors() {
Work work = new Work();
WorkTitle workTitle = new WorkTitle();
diff --git a/orcid-persistence/src/main/java/org/orcid/persistence/dao/impl/ClientDetailsDaoImpl.java b/orcid-persistence/src/main/java/org/orcid/persistence/dao/impl/ClientDetailsDaoImpl.java
index 11d0ac2b613..ba95e030c93 100644
--- a/orcid-persistence/src/main/java/org/orcid/persistence/dao/impl/ClientDetailsDaoImpl.java
+++ b/orcid-persistence/src/main/java/org/orcid/persistence/dao/impl/ClientDetailsDaoImpl.java
@@ -100,9 +100,8 @@ public void updateLastModified(String clientId) {
/**
* Update the last modified dates of given client ids
- *
- * @param clientIds
- * A list of client ids
+ *
+ * @param clientIds A list of client ids
* @return the amount of modified rows
*/
@@ -194,9 +193,8 @@ public void removeClient(String clientId) {
/**
* Get the public client that belongs to the given orcid ID
- *
- * @param ownerId
- * The user or group id
+ *
+ * @param ownerId The user or group id
* @return the public client that belongs to the given user
*/
@Override
@@ -216,9 +214,8 @@ public ClientDetailsEntity getPublicClient(String ownerId) {
/**
* Get member name
- *
- * @param clientId
- * The client id
+ *
+ * @param clientId The client id
* @return the name of the member owner of the given client
*/
@Override
@@ -269,7 +266,7 @@ public List findLegacyClientIds() {
@Transactional
public void changePersistenceTokensProperty(String clientId, boolean isPersistenTokensEnabled) {
Query updateQuery = entityManager
- .createQuery("update ClientDetailsEntity c set c.lastModified = :lastModified, c.persistentTokensEnabled = :isPersistenTokensEnabled where c.id = :clientId");
+ .createQuery("update ClientDetailsEntity c set c.lastModified = :lastModified, c.persistentTokensEnabled = :isPersistenTokensEnabled where c.id = :clientId");
updateQuery.setParameter("clientId", clientId);
updateQuery.setParameter("lastModified", new Date());
updateQuery.setParameter("isPersistenTokensEnabled", isPersistenTokensEnabled);
@@ -280,7 +277,7 @@ public void changePersistenceTokensProperty(String clientId, boolean isPersisten
@Transactional
public void activateClient(String clientDetailsId) {
Query updateQuery = entityManager
- .createQuery("update ClientDetailsEntity c set c.lastModified = :lastModified, c.deactivatedDate = null, c.deactivatedBy = null where c.id = :clientId");
+ .createQuery("update ClientDetailsEntity c set c.lastModified = :lastModified, c.deactivatedDate = null, c.deactivatedBy = null where c.id = :clientId");
updateQuery.setParameter("clientId", clientDetailsId);
updateQuery.setParameter("lastModified", new Date());
updateQuery.executeUpdate();
@@ -290,7 +287,7 @@ public void activateClient(String clientDetailsId) {
@Transactional
public void deactivateClient(String clientDetailsId, String deactivatedBy) {
Query updateQuery = entityManager
- .createQuery("update ClientDetailsEntity c set c.lastModified = :lastModified, c.deactivatedDate = :deactivatedDate, c.deactivatedBy = :deactivatedBy where c.id = :clientId");
+ .createQuery("update ClientDetailsEntity c set c.lastModified = :lastModified, c.deactivatedDate = :deactivatedDate, c.deactivatedBy = :deactivatedBy where c.id = :clientId");
updateQuery.setParameter("clientId", clientDetailsId);
Date now = new Date();
updateQuery.setParameter("lastModified", now);
@@ -324,7 +321,7 @@ public boolean updateClientGrantedAuthority(String clientId, String grantedAutho
@Transactional
public boolean updateNotificationInfo(String clientId, boolean userNotificationEnabled, String notificationWebpageUrl, String notificationDomains) {
Query updateNotificationInfoQuery = entityManager.createNativeQuery(
- "UPDATE client_details SET user_notification_enabled= :userNotificationEnabled, notification_webpage_url= :notificationWebpageUrl, notification_domains= :notificationDomains WHERE id = :clientId");
+ "UPDATE client_details SET user_notification_enabled= :userNotificationEnabled, notification_webpage_url= :notificationWebpageUrl, notification_domains= :notificationDomains WHERE client_details_id = :clientId");
updateNotificationInfoQuery.setParameter("clientId", clientId);
updateNotificationInfoQuery.setParameter("userNotificationEnabled", userNotificationEnabled);
updateNotificationInfoQuery.setParameter("notificationWebpageUrl", notificationWebpageUrl);
@@ -336,7 +333,7 @@ public boolean updateNotificationInfo(String clientId, boolean userNotificationE
@Transactional(value = "transactionManagerReadOnly", readOnly = true)
@SuppressWarnings("unchecked")
public List findMVPEnabled() {
- Query query = entityManager.createQuery("from ClientDetailsEntity where userNotificationEnabled = :userNotificationEnabled and client_type = :premiumUpdater" );
+ Query query = entityManager.createQuery("from ClientDetailsEntity where userNotificationEnabled = :userNotificationEnabled and clientType = :premiumUpdater");
query.setParameter("userNotificationEnabled", true);
query.setParameter("premiumUpdater", ClientType.PREMIUM_UPDATER.name());
return query.getResultList();
diff --git a/orcid-persistence/src/test/java/org/orcid/persistence/dao/ClientDetailsDaoTest.java b/orcid-persistence/src/test/java/org/orcid/persistence/dao/ClientDetailsDaoTest.java
index b784aa911dc..d5b514a993e 100644
--- a/orcid-persistence/src/test/java/org/orcid/persistence/dao/ClientDetailsDaoTest.java
+++ b/orcid-persistence/src/test/java/org/orcid/persistence/dao/ClientDetailsDaoTest.java
@@ -1,6 +1,8 @@
package org.orcid.persistence.dao;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
@@ -12,12 +14,14 @@
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.orcid.jaxb.model.clientgroup.ClientType;
+import org.orcid.persistence.jpa.entities.ClientDetailsEntity;
import org.orcid.test.DBUnitTest;
import org.orcid.test.OrcidJUnit4ClassRunner;
import org.springframework.test.context.ContextConfiguration;
@RunWith(OrcidJUnit4ClassRunner.class)
-@ContextConfiguration(locations = { "classpath:test-orcid-persistence-context.xml" })
+@ContextConfiguration(locations = {"classpath:test-orcid-persistence-context.xml"})
public class ClientDetailsDaoTest extends DBUnitTest {
private static String CLIENT_ID = "APP-5555555555555557";
@@ -48,4 +52,16 @@ public void testUpdateLastModifiedBulk() {
assertEquals(updateClients, 2);
}
+ @Test
+ public void testFindMVPEnabled() {
+ clientDetailsDao.updateClientType(ClientType.PREMIUM_UPDATER.name(), CLIENT_ID);
+ clientDetailsDao.updateNotificationInfo(CLIENT_ID, true, "http://test.com", "test.com");
+ List mvpEnabledClients = clientDetailsDao.findMVPEnabled();
+ assertNotNull(mvpEnabledClients);
+ assertEquals(1, mvpEnabledClients.size());
+ assertEquals(CLIENT_ID, mvpEnabledClients.get(0).getId());
+ assertEquals(ClientType.PREMIUM_UPDATER.name(), mvpEnabledClients.get(0).getClientType());
+ assertTrue(mvpEnabledClients.get(0).isUserNotificationEnabled());
+ }
+
}
diff --git a/orcid-pub-web/src/main/java/org/orcid/api/publicV2/server/delegator/impl/PublicV2ApiServiceDelegatorImpl.java b/orcid-pub-web/src/main/java/org/orcid/api/publicV2/server/delegator/impl/PublicV2ApiServiceDelegatorImpl.java
index ae7c00a46ff..4b45bce7359 100644
--- a/orcid-pub-web/src/main/java/org/orcid/api/publicV2/server/delegator/impl/PublicV2ApiServiceDelegatorImpl.java
+++ b/orcid-pub-web/src/main/java/org/orcid/api/publicV2/server/delegator/impl/PublicV2ApiServiceDelegatorImpl.java
@@ -207,6 +207,7 @@ public Response viewActivities(String orcid) {
public Response viewWork(String orcid, Long putCode) {
Work w = workManagerReadOnly.getWork(orcid, putCode);
publicAPISecurityManagerV2.checkIsPublic(w);
+ contributorUtilsReadOnly.filterContributorPrivateData(w);
ActivityUtils.cleanEmptyFields(w);
ActivityUtils.setPathToActivity(w, orcid);
@@ -557,6 +558,7 @@ public Response viewBulkWorks(String orcid, String putCodes) {
}
WorkBulk workBulk = workManagerReadOnly.findWorkBulk(orcid, putCodes);
publicAPISecurityManagerV2.filter(workBulk);
+ contributorUtilsReadOnly.filterContributorPrivateData(workBulk);
ActivityUtils.cleanEmptyFields(workBulk);
ActivityUtils.setPathToBulk(workBulk, orcid);
sourceUtils.setSourceName(workBulk);
diff --git a/orcid-pub-web/src/main/java/org/orcid/api/publicV3/server/delegator/impl/PublicV3ApiServiceDelegatorImpl.java b/orcid-pub-web/src/main/java/org/orcid/api/publicV3/server/delegator/impl/PublicV3ApiServiceDelegatorImpl.java
index 124ecbd44aa..722be3d0cfc 100644
--- a/orcid-pub-web/src/main/java/org/orcid/api/publicV3/server/delegator/impl/PublicV3ApiServiceDelegatorImpl.java
+++ b/orcid-pub-web/src/main/java/org/orcid/api/publicV3/server/delegator/impl/PublicV3ApiServiceDelegatorImpl.java
@@ -246,6 +246,7 @@ public Response viewWork(String orcid, Long putCode) {
checkProfileStatus(orcid);
Work w = workManagerReadOnly.getWork(orcid, putCode);
publicAPISecurityManagerV3.checkIsPublic(w);
+ contributorUtilsReadOnly.filterContributorPrivateData(w);
ActivityUtils.cleanEmptyFields(w);
ActivityUtils.setPathToActivity(w, orcid);
sourceUtilsReadOnly.setSourceName(w);
@@ -645,6 +646,7 @@ public Response viewBulkWorks(String orcid, String putCodes) {
}
WorkBulk workBulk = workManagerReadOnly.findWorkBulk(orcid, putCodes);
publicAPISecurityManagerV3.filter(workBulk);
+ contributorUtilsReadOnly.filterContributorPrivateData(workBulk);
ActivityUtils.cleanEmptyFields(workBulk);
ActivityUtils.setPathToBulk(workBulk, orcid);
sourceUtils.setSourceName(workBulk);
diff --git a/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceDelegatorTest.java b/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceDelegatorTest.java
index e5a73d9f004..e34f4d971d0 100644
--- a/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceDelegatorTest.java
+++ b/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceDelegatorTest.java
@@ -155,6 +155,7 @@ public void testViewWork() {
assertNotNull(work.getWorkContributors().getContributor().get(0).getContributorOrcid());
assertEquals("0000-0000-0000-0000", work.getWorkContributors().getContributor().get(0).getContributorOrcid().getPath());
assertNull(work.getWorkContributors().getContributor().get(0).getCreditName());
+ assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
}
@Test
@@ -205,6 +206,7 @@ public void testViewBulkWorks() {
assertNotNull(work.getWorkContributors().getContributor().get(0).getContributorOrcid());
assertEquals("0000-0000-0000-0000", work.getWorkContributors().getContributor().get(0).getContributorOrcid().getPath());
assertNull(work.getWorkContributors().getContributor().get(0).getCreditName());
+ assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
}
@Test
diff --git a/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceVersionedDelegatorTest.java b/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceVersionedDelegatorTest.java
index 5dceaabc87c..d32679e6c56 100644
--- a/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceVersionedDelegatorTest.java
+++ b/orcid-pub-web/src/test/java/org/orcid/api/publicV2/server/PublicV2ApiServiceVersionedDelegatorTest.java
@@ -2,6 +2,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -990,6 +991,25 @@ public void testViewPersonAndRecordKeepVerifiedAndPrimaryOnV2_1() {
assertEmailFlags(record.getPerson().getEmails().getEmails());
}
+ /**
+ * The contributor email is write-only. The versioned beans run every work through the
+ * 2.0 <-> 2.1 converter, which copies every bean property, so check it stays out on both.
+ */
+ @Test
+ public void testViewWorkOmitsContributorEmailOnV2_0AndV2_1() {
+ assertNoContributorEmail((Work) serviceDelegator.viewWork(userWithPublicEmails, 11L).getEntity());
+ assertNoContributorEmail((Work) serviceDelegatorV2_1.viewWork(userWithPublicEmails, 11L).getEntity());
+ assertNoContributorEmail((Work) ((WorkBulk) serviceDelegator.viewBulkWorks(userWithPublicEmails, "11").getEntity()).getBulk().get(0));
+ assertNoContributorEmail((Work) ((WorkBulk) serviceDelegatorV2_1.viewBulkWorks(userWithPublicEmails, "11").getEntity()).getBulk().get(0));
+ }
+
+ private void assertNoContributorEmail(Work work) {
+ assertEquals(Long.valueOf(11), work.getPutCode());
+ assertEquals(1, work.getWorkContributors().getContributor().size());
+ assertEquals("0000-0000-0000-0000", work.getWorkContributors().getContributor().get(0).getContributorOrcid().getPath());
+ assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
+ }
+
/**
* Compares values rather than asserting true: a dropped flag is null, and false has to stay
* false rather than collapse to null.
diff --git a/orcid-pub-web/src/test/java/org/orcid/api/publicV3/server/PublicV3ApiServiceDelegatorTest.java b/orcid-pub-web/src/test/java/org/orcid/api/publicV3/server/PublicV3ApiServiceDelegatorTest.java
index f1bd4e59461..9757db70723 100644
--- a/orcid-pub-web/src/test/java/org/orcid/api/publicV3/server/PublicV3ApiServiceDelegatorTest.java
+++ b/orcid-pub-web/src/test/java/org/orcid/api/publicV3/server/PublicV3ApiServiceDelegatorTest.java
@@ -183,6 +183,7 @@ public void testViewWork() {
assertNotNull(work.getWorkContributors().getContributor().get(0).getContributorOrcid());
assertEquals("0000-0000-0000-0000", work.getWorkContributors().getContributor().get(0).getContributorOrcid().getPath());
assertNull(work.getWorkContributors().getContributor().get(0).getCreditName());
+ assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
}
@Test
@@ -233,6 +234,7 @@ public void testViewBulkWorks() {
assertNotNull(work.getWorkContributors().getContributor().get(0).getContributorOrcid());
assertEquals("0000-0000-0000-0000", work.getWorkContributors().getContributor().get(0).getContributorOrcid().getPath());
assertNull(work.getWorkContributors().getContributor().get(0).getCreditName());
+ assertNull(work.getWorkContributors().getContributor().get(0).getContributorEmail());
}
@Test
diff --git a/orcid-scheduler-web/src/test/java/org/orcid/scheduler/email/cli/manager/EmailMessageSenderTest.java b/orcid-scheduler-web/src/test/java/org/orcid/scheduler/email/cli/manager/EmailMessageSenderTest.java
index 5056cdb734d..533e4155c4a 100644
--- a/orcid-scheduler-web/src/test/java/org/orcid/scheduler/email/cli/manager/EmailMessageSenderTest.java
+++ b/orcid-scheduler-web/src/test/java/org/orcid/scheduler/email/cli/manager/EmailMessageSenderTest.java
@@ -187,6 +187,32 @@ public void digestDoesNotEvaluateStoredNotificationBody() {
assertTrue("stored markup should still render", html.contains("Hello from "));
}
+ @Test
+ public void testPermissionNotificationEmail() {
+ NotificationPermission notification = new NotificationPermission();
+ notification.setPutCode(100L);
+ notification.setNotificationType(NotificationType.PERMISSION);
+ notification.setSubject("Connect with your organization");
+ notification.setNotificationSubject("Connect with your organization");
+ notification.setNotificationIntro("University of Testing");
+ notification.setAuthorizationUrl(new AuthorizationUrl("https://orcid.org/oauth/authorize?client_id=APP-1234"));
+ notification.setCreatedDate(DateUtils.convertToXMLGregorianCalendar("2026-10-01T12:00:00"));
+ Source source = new Source();
+ source.setSourceName(new SourceName("University of Testing"));
+ source.setSourceClientId(new SourceClientId("APP-1234567890123456"));
+ notification.setSource(source);
+
+ List notifications = new ArrayList<>();
+ notifications.add(notification);
+
+ EmailMessage emailMessage = emailMessageSender.createDigest("0000-0000-0000-0000", notifications);
+ assertNotNull(emailMessage);
+ assertNotNull(emailMessage.getBodyHtml());
+ assertNotNull(emailMessage.getBodyText());
+ assertTrue(emailMessage.getBodyHtml().contains("University of Testing"));
+ assertTrue(emailMessage.getBodyText().contains("University of Testing"));
+ }
+
private List generateNotifications() {
List notifications = new ArrayList<>();
diff --git a/orcid-test/src/main/resources/data/WorksEntityData.xml b/orcid-test/src/main/resources/data/WorksEntityData.xml
index 56d886530fa..97e383b79b0 100644
--- a/orcid-test/src/main/resources/data/WorksEntityData.xml
+++ b/orcid-test/src/main/resources/data/WorksEntityData.xml
@@ -180,7 +180,7 @@
added_to_profile_date="2016-04-01 11:09:28.636"
visibility="PUBLIC"
external_ids_json="{"workExternalIdentifier":[{"workExternalIdentifierType":"DOI","relationship":"self","workExternalIdentifierId":{"content":"1"}}],"scope":null}"
- contributors_json="{"contributor":[{"contributorOrcid":{"path":"0000-0000-0000-0000"},"creditName":{"content":null},"contributorEmail":null,"contributorAttributes":null}]}"
+ contributors_json="{"contributor":[{"contributorOrcid":{"path":"0000-0000-0000-0000"},"creditName":{"content":null},"contributorEmail":{"value":"never@show.this"},"contributorAttributes":null}]}"
display_index="0"
work_url="http://testuri.org"
featured_display_index="0"
diff --git a/orcid-web/src/main/java/org/orcid/frontend/spring/configuration/OrcidBeanClassLoaderAware.java b/orcid-web/src/main/java/org/orcid/frontend/spring/configuration/OrcidBeanClassLoaderAware.java
index 262b5e1b70f..57547d94e77 100644
--- a/orcid-web/src/main/java/org/orcid/frontend/spring/configuration/OrcidBeanClassLoaderAware.java
+++ b/orcid-web/src/main/java/org/orcid/frontend/spring/configuration/OrcidBeanClassLoaderAware.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.orcid.authorization.authentication.MFAWebAuthenticationDetails;
import org.orcid.frontend.web.util.MFAWebAuthenticationDetailsDeserializer;
import org.orcid.frontend.web.util.SwitchUserGrantedAuthorityDeserializer;
@@ -34,6 +35,16 @@ private ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModules(SecurityJackson2Modules.getModules(this.loader));
mapper.registerModules(new CoreJackson2Module());
+ mapper.activateDefaultTyping(
+ BasicPolymorphicTypeValidator.builder()
+ .allowIfSubType("java.lang")
+ .allowIfSubType("org.orcid")
+ .allowIfSubType("org.springframework.security")
+ .allowIfSubType("jakarta.servlet")
+ .allowIfSubType("java.util")
+ .build(),
+ ObjectMapper.DefaultTyping.NON_FINAL,
+ JsonTypeInfo.As.PROPERTY);
mapper.addMixIn(String[].class, StringArrayMixin.class);
mapper.addMixIn(SwitchUserGrantedAuthority.class, SwitchUserGrantedAuthorityMixin.class);
mapper.addMixIn(MFAWebAuthenticationDetails.class, MFAWebAuthenticationDetailsMixin.class);
diff --git a/orcid-web/src/test/java/org/orcid/frontend/spring/session/redis/OrcidRedisIndexedSessionRepositoryTest.java b/orcid-web/src/test/java/org/orcid/frontend/spring/session/redis/OrcidRedisIndexedSessionRepositoryTest.java
index c3084ba9806..2f1640593aa 100644
--- a/orcid-web/src/test/java/org/orcid/frontend/spring/session/redis/OrcidRedisIndexedSessionRepositoryTest.java
+++ b/orcid-web/src/test/java/org/orcid/frontend/spring/session/redis/OrcidRedisIndexedSessionRepositoryTest.java
@@ -17,12 +17,16 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import org.orcid.frontend.spring.configuration.OrcidBeanClassLoaderAware;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.RedisOperations;
+import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.web.context.request.RequestContextHolder;
+import static org.junit.Assert.assertEquals;
+
@RunWith(MockitoJUnitRunner.class)
public class OrcidRedisIndexedSessionRepositoryTest {
@@ -83,4 +87,19 @@ public void deleteByIdShouldWorkWithoutHttpRequestContext() {
verify(principalSetOperations).remove(eq(SESSION_ID));
verify(expirationSetOperations).remove(eq("expires:" + SESSION_ID));
}
+
+ @Test
+ public void springSessionSerializerShouldAllowJavaLongsForSessionDelta() {
+ OrcidBeanClassLoaderAware beanClassLoaderAware = new OrcidBeanClassLoaderAware();
+ beanClassLoaderAware.setBeanClassLoader(getClass().getClassLoader());
+
+ RedisSerializer