Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
a9f72f2
HDC 3b 1.5 initial update
qqmyers Aug 11, 2022
63aeed1
add workflow settings to main list per qa
qqmyers Aug 11, 2022
a410659
start adding default bestID
qqmyers Aug 11, 2022
8b69c0f
replace system.out.println call
qqmyers Aug 11, 2022
cbd20fa
Improve default compound field handling and some reformatting
qqmyers Aug 11, 2022
94d40e6
add template custom instructions info
qqmyers Aug 11, 2022
aa88182
get blocks from metadataroot
qqmyers Aug 12, 2022
c9c7c91
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Aug 12, 2022
62fe214
fix parsing to match spec/dash msg
qqmyers Sep 1, 2022
e5228af
use getString for 'Relationship', revert other changes
qqmyers Sep 1, 2022
fb56d04
temporarily drop name/type in display
qqmyers Sep 1, 2022
8ecb8e4
misplaced }
qqmyers Sep 1, 2022
789b3c9
Add callback loop and make display tolerant wrt name/type being found
qqmyers Sep 6, 2022
d06c5dc
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Sep 6, 2022
d84ae12
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Sep 23, 2022
37ebf7d
add fields missing in https://notify.coar-repositories.org/scenarios/10/
qqmyers Sep 23, 2022
ceb3ff5
debugging
qqmyers Oct 26, 2022
62888e2
added logging/switched to info for HDC debugging
qqmyers Oct 27, 2022
6c46571
Merge remote-tracking branch 'IQSS/develop' into
qqmyers Apr 12, 2024
1a28089
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Apr 23, 2024
d714a1a
merge issues
qqmyers Apr 23, 2024
0b967a9
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Oct 2, 2024
9d68054
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Dec 12, 2024
1a856f3
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Jun 27, 2025
b34880d
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Oct 2, 2025
9cdb4f6
updates to send context, use as namespace, use relationType, DASH-NRS
qqmyers Oct 3, 2025
0ffbb8e
doc updates
qqmyers Oct 6, 2025
52fea24
note some ToDos
qqmyers Oct 6, 2025
26e0ee4
New format has strings, not objects with @id
qqmyers Oct 7, 2025
2b5c3f6
Merge remote-tracking branch 'IQSS/develop' into GDCC/8914-COAR-compl…
qqmyers Oct 24, 2025
f4a4ed5
add space to fix link
qqmyers Oct 24, 2025
76304a4
more doc tweaks
qqmyers Oct 27, 2025
5e0f546
refactor, use JVMSettings, add tests, add superusers only flag
qqmyers Oct 29, 2025
6e9030d
refactor wf step - name change, multiple targets, gen. improvements
qqmyers Oct 29, 2025
486a498
drop lower case start to DataCite relation types, use constants
qqmyers Oct 29, 2025
da6e5a4
test fixes, use COAR step announcement builder
qqmyers Oct 29, 2025
e9dd212
don't autofollow redirect, handle more redirect codes
qqmyers Oct 30, 2025
21682b5
get absolute redirect url, fix signposting parsing
qqmyers Oct 30, 2025
4276798
remove overloaded method (failing in xhtml)
qqmyers Oct 30, 2025
140d964
cleanup, fine logging
qqmyers Oct 31, 2025
e2804f1
info to fine
qqmyers Oct 31, 2025
b8288f8
doc updates
qqmyers Oct 31, 2025
e0984d3
Merge branch 'GDCC/8914-COAR-compliant_messaging' of https://github.c…
qqmyers Oct 31, 2025
7ede9e8
Merge remote-tracking branch 'IQSS/develop' into
qqmyers Nov 13, 2025
d127ab5
Changes per review
qqmyers Nov 13, 2025
8c96c77
only send new relationships
qqmyers Nov 13, 2025
7641bc4
refactor/cleanup
qqmyers Nov 17, 2025
156377c
release note
qqmyers Nov 17, 2025
7763257
fix #11990
qqmyers Nov 18, 2025
10d2f24
diff and test additions/fixes
qqmyers Nov 18, 2025
ad37d93
cleanup
qqmyers Nov 20, 2025
09cc4ab
Merge remote-tracking branch 'IQSS/develop' into
qqmyers Nov 20, 2025
28c728c
typo
qqmyers Nov 20, 2025
c6dca2d
Merge branch 'develop' into GDCC/8914-COAR-compliant_messaging2
qqmyers Nov 20, 2025
704ea2e
Merge remote-tracking branch 'IQSS/develop' into
qqmyers Nov 20, 2025
506896f
Merge branch 'GDCC/8914-COAR-compliant_messaging2' of https://github.…
qqmyers Nov 20, 2025
baa9dca
Merge remote-tracking branch 'IQSS/develop' into
qqmyers Nov 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/release-notes/11983-COAR-Notify2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Improved COAR Notify Relationship Announcement Support

Dataverse no longer sends duplicate [COAR Notify Relationship Announcement Workflow](https://coar-notify.net/catalogue/workflows/repository-relationship-repository/) messages when new dataset versions are published (and the relationship metadata has not been changed).
20 changes: 20 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/Dataset.java
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,26 @@ public DatasetVersion getReleasedVersion() {
}
return null;
}

/**
* Returns the second-most-recent released version of this dataset.
* Assumes versions are ordered from most recent to oldest.
*
* @return The prior released version, or null if there is only one or no released versions
*/
public DatasetVersion getPriorReleasedVersion() {
boolean foundReleasedVersion = false;
for (DatasetVersion version : this.getVersions()) {
if (version.isReleased()) {
if(foundReleasedVersion) {
return version;
} else {
foundReleasedVersion = true;
}
}
}
return null;
}

public DatasetVersion getVersionFromId(Long datasetVersionId) {
for (DatasetVersion version : this.getVersions()) {
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/edu/harvard/iq/dataverse/DatasetField.java
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ public void setDatasetFieldValues(List<DatasetFieldValue> datasetFieldValues) {

@ManyToMany(cascade = {CascadeType.MERGE})
@JoinTable(indexes = {@Index(columnList="datasetfield_id"),@Index(columnList="controlledvocabularyvalues_id")})
@OrderBy("displayOrder ASC")
private List<ControlledVocabularyValue> controlledVocabularyValues = new ArrayList<>();

public List<ControlledVocabularyValue> getControlledVocabularyValues() {
Expand Down Expand Up @@ -604,14 +605,15 @@ private DatasetField copy(Object versionOrTemplate, DatasetFieldCompoundValue pa

if (versionOrTemplate != null) {
if (versionOrTemplate instanceof DatasetVersion) {
dsf.setDatasetVersion((DatasetVersion) versionOrTemplate);
dsf.setDatasetVersion((DatasetVersion) versionOrTemplate);
} else {
dsf.setTemplate((Template) versionOrTemplate);
}
}

dsf.setParentDatasetFieldCompoundValue(parent);
dsf.setControlledVocabularyValues(controlledVocabularyValues);

dsf.getControlledVocabularyValues().addAll(controlledVocabularyValues);

for (DatasetFieldValue dsfv : datasetFieldValues) {
dsf.getDatasetFieldValues().add(dsfv.copy(dsf));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import jakarta.persistence.Transient;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

Expand Down Expand Up @@ -254,4 +255,83 @@ private Map<DatasetField, String> removeLastComma(Map<DatasetField, String> mapI

return mapIn;
}


/**
* Compares this DatasetFieldCompoundValue with another for equality based on
* their child fields. Two compound values are considered equal if they have the
* same child fields with the same values in the same order.
*
* @param other The DatasetFieldCompoundValue to compare with
* @return true if both compound values have equal child fields, false otherwise
*/
public boolean valuesEqual(DatasetFieldCompoundValue other) {
if (this == other) {
return true;
}
if (other == null) {
return false;
}

List<DatasetField> children1 = this.getChildDatasetFields();
List<DatasetField> children2 = other.getChildDatasetFields();

if (children1.size() != children2.size()) {
return false;
}

// Compare each child field
for (DatasetField child1 : children1) {

DatasetField child2 = children2.stream()
.filter(c -> c.getDatasetFieldType().equals(child1.getDatasetFieldType())).findFirst().orElse(null);

if (child2 == null) {
return false;
}

// Compare values based on field type
if (child1.getDatasetFieldType().isControlledVocabulary()) {

List<ControlledVocabularyValue> cvs1 = child1.getControlledVocabularyValues();
List<ControlledVocabularyValue> cvs2 = child2.getControlledVocabularyValues();

if (cvs1.size() != cvs2.size()) {

return false;
}

for (ControlledVocabularyValue cv1Val : cvs1) {
boolean found = cvs2.stream().anyMatch(cv2Val -> cv1Val.getStrValue().equals(cv2Val.getStrValue()));
if (!found) {
return false;
}
}
} else {
// Handle regular field values (including multiple values)
List<DatasetFieldValue> dfvs1 = child1.getDatasetFieldValues();
List<DatasetFieldValue> dfvs2 = child2.getDatasetFieldValues();

if (dfvs1.size() != dfvs2.size()) {
return false;
}

for (DatasetFieldValue dfv1 : dfvs1) {
String value1 = dfv1.getValue();
boolean found = dfvs2.stream()
.anyMatch(dfv2 -> {
String value2 = dfv2.getValue();
if (value1 == null && value2 == null) {
return true;
}
return value1 != null && value1.equals(value2);
});
if (!found) {
return false;
}
}
}
}
return true;
}
}
19 changes: 18 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/DatasetFieldValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;

/**
*
Expand Down Expand Up @@ -193,6 +194,22 @@ public DatasetFieldValue copy(DatasetField dsf) {
dsfv.setValue(value);

return dsfv;
}
}

/**
* Compares this DatasetFieldValue with another for equality based on their values.
*
* @param other The DatasetFieldValue to compare with
* @return true if both values are equal (case-sensitive), false otherwise
*/
public boolean valuesEqual(DatasetFieldValue other) {
if (this == other) {
return true;
}
if (other == null) {
return false;
}
return Strings.CS.equals(this.getValue(), other.getValue());
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package edu.harvard.iq.dataverse.workflow.internalspi;

import edu.harvard.iq.dataverse.ControlledVocabularyValue;
import edu.harvard.iq.dataverse.Dataset;
import edu.harvard.iq.dataverse.DatasetField;
import edu.harvard.iq.dataverse.DatasetFieldCompoundValue;
import edu.harvard.iq.dataverse.DatasetFieldType;
import edu.harvard.iq.dataverse.DatasetFieldValue;
import edu.harvard.iq.dataverse.DatasetVersion;
import edu.harvard.iq.dataverse.GlobalId;
import edu.harvard.iq.dataverse.branding.BrandingUtil;
Expand All @@ -26,7 +29,7 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
Expand Down Expand Up @@ -88,12 +91,34 @@ public WorkflowStepResult run(WorkflowContext context) {
// First check that we have what is required
Dataset d = context.getDataset();
DatasetVersion dv = d.getReleasedVersion();
DatasetVersion priorVersion = d.getPriorReleasedVersion();
List<DatasetField> dvf = dv.getDatasetFields();
Map<String, DatasetField> fields = new HashMap<String, DatasetField>();
List<String> reqFields = ListSplitUtil.split((String) context.getSettings().getOrDefault(COARNotifyRelationshipAnnouncementTriggerFields.toString(), ""));

Map<String, DatasetField> priorFields = new HashMap<String, DatasetField>();
if (priorVersion != null) {
for (DatasetField pdf : priorVersion.getDatasetFields()) {
if (!pdf.isEmpty() && reqFields.contains(pdf.getDatasetFieldType().getName())) {
priorFields.put(pdf.getDatasetFieldType().getName(), pdf);
}
}
}

for (DatasetField df : dvf) {
if (!df.isEmpty() && reqFields.contains(df.getDatasetFieldType().getName())) {
fields.put(df.getDatasetFieldType().getName(), df);
DatasetField priorField = priorFields.get(df.getDatasetFieldType().getName());

if (priorVersion == null || priorField == null) {
// No prior version, include all values
fields.put(df.getDatasetFieldType().getName(), df);
} else {
// Create a filtered field with only new values
DatasetField filteredField = filterNewValues(df, priorField);
if (!filteredField.isEmpty()) {
fields.put(df.getDatasetFieldType().getName(), filteredField);
}
}
}
}

Expand Down Expand Up @@ -412,4 +437,90 @@ private boolean isURI(String number) {
return false;
}

/**
* Create a new DatasetField containing only values that are new compared to the
* prior field. This creates a detached copy to avoid modifying the managed
* entity.
*
* @param currentField The field from the current version
* @param priorField The field from the prior version
* @return A new DatasetField with only new values
*/
private DatasetField filterNewValues(DatasetField currentField, DatasetField priorField) {
DatasetField filtered = new DatasetField();
DatasetFieldType fieldType = currentField.getDatasetFieldType();
filtered.setDatasetFieldType(fieldType);

// Handle primitive fields
if (fieldType.isPrimitive()) {
if (fieldType.isControlledVocabulary()) {
// Handle controlled vocabulary fields
List<ControlledVocabularyValue> currentCVs = currentField.getControlledVocabularyValues();
List<ControlledVocabularyValue> priorCVs = priorField != null ? priorField.getControlledVocabularyValues() : new ArrayList<>();

List<ControlledVocabularyValue> newCVs = new ArrayList<>();
for (ControlledVocabularyValue currentCV : currentCVs) {
boolean isNew = true;
for (ControlledVocabularyValue priorCV : priorCVs) {
if (currentCV.getStrValue().equals(priorCV.getStrValue())) {
isNew = false;
break;
}
}
if (isNew) {
newCVs.add(currentCV);
}
}
filtered.setControlledVocabularyValues(newCVs);
} else {
// Handle regular fields
List<DatasetFieldValue> currentDFVs = currentField.getDatasetFieldValues();
List<DatasetFieldValue> priorDFVs = priorField != null ? priorField.getDatasetFieldValues() : new ArrayList<>();

List<DatasetFieldValue> newDFVs = new ArrayList<>();
for (DatasetFieldValue currentDFV : currentDFVs) {
boolean isNew = true;
for (DatasetFieldValue priorDFV : priorDFVs) {
if (currentDFV.valuesEqual(priorDFV)) {
isNew = false;
break;
}
}
if (isNew) {
newDFVs.add(currentDFV);
}
}
filtered.setDatasetFieldValues(newDFVs);
}
} else {
// Handle compound fields
List<DatasetFieldCompoundValue> currentCompounds = currentField.getDatasetFieldCompoundValues();
List<DatasetFieldCompoundValue> priorCompounds = priorField != null ? priorField.getDatasetFieldCompoundValues() : new ArrayList<>();

List<DatasetFieldCompoundValue> newCompounds = new ArrayList<>();

for (DatasetFieldCompoundValue currentCompound : currentCompounds) {
boolean isNew = true;

for (DatasetFieldCompoundValue priorCompound : priorCompounds) {

if (currentCompound.valuesEqual(priorCompound)) {
isNew = false;
break;
}
}

if (isNew) {
// Create a copy of the compound value with all its children
DatasetFieldCompoundValue newCompound = currentCompound.copy(filtered);
newCompound.setParentDatasetField(filtered);
newCompounds.add(newCompound);
}
}

filtered.setDatasetFieldCompoundValues(newCompounds);
}

return filtered;
}
}
1 change: 0 additions & 1 deletion src/test/java/edu/harvard/iq/dataverse/api/LDNInboxIT.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

package edu.harvard.iq.dataverse.api;

import org.junit.jupiter.api.Test;
Expand Down
5 changes: 0 additions & 5 deletions src/test/java/edu/harvard/iq/dataverse/api/LDNInboxTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@
import edu.harvard.iq.dataverse.DataverseRoleServiceBean;
import edu.harvard.iq.dataverse.DataverseServiceBean;
import edu.harvard.iq.dataverse.GlobalId;
import edu.harvard.iq.dataverse.MailServiceBean;
import edu.harvard.iq.dataverse.RoleAssigneeServiceBean;
import edu.harvard.iq.dataverse.RoleAssignment;
import edu.harvard.iq.dataverse.UserNotification;
import edu.harvard.iq.dataverse.UserNotificationServiceBean;
import edu.harvard.iq.dataverse.api.ldn.COARNotifyRelationshipAnnouncement;
import edu.harvard.iq.dataverse.authorization.DataverseRole;
import edu.harvard.iq.dataverse.authorization.Permission;
import edu.harvard.iq.dataverse.authorization.RoleAssignee;
Expand All @@ -19,7 +17,6 @@
import edu.harvard.iq.dataverse.pidproviders.PidUtil;
import edu.harvard.iq.dataverse.settings.JvmSettings;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.util.SystemConfig;
import edu.harvard.iq.dataverse.util.testing.JvmSetting;
import edu.harvard.iq.dataverse.util.testing.LocalJvmSettings;
import jakarta.json.Json;
Expand All @@ -40,8 +37,6 @@
import org.mockito.junit.jupiter.MockitoExtension;

import static jakarta.ws.rs.core.Response.Status.OK;
import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST;
import static jakarta.ws.rs.core.Response.Status.FORBIDDEN;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down
Loading