diff --git a/doc/release-notes/7978-linking-ORCID-profile-from-metadata-tab.md b/doc/release-notes/7978-linking-ORCID-profile-from-metadata-tab.md new file mode 100644 index 00000000000..62c8b308073 --- /dev/null +++ b/doc/release-notes/7978-linking-ORCID-profile-from-metadata-tab.md @@ -0,0 +1,3 @@ +### Displaying author's identifier as link + +In the dataset page's metadata tab the author's identifier is displayed as a clickable link, which points to the profile page in the external service (ORCID, VIAF etc.), given that the identifier scheme provides a resolvable landing page. If the identifier does not match the expected scheme, a link is not shown. diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetAuthor.java b/src/main/java/edu/harvard/iq/dataverse/DatasetAuthor.java index ce8405a0164..d33d709107f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetAuthor.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetAuthor.java @@ -7,14 +7,11 @@ package edu.harvard.iq.dataverse; import java.util.Comparator; -import java.util.regex.Pattern; - /** * * @author skraffmiller */ - public class DatasetAuthor { public static Comparator DisplayOrder = new Comparator(){ @@ -89,59 +86,23 @@ public boolean isEmpty() { ); } - /** - * https://support.orcid.org/hc/en-us/articles/360006897674-Structure-of-the-ORCID-Identifier - */ - final public static String REGEX_ORCID = "^\\d{4}-\\d{4}-\\d{4}-(\\d{4}|\\d{3}X)$"; - final public static String REGEX_ISNI = "^\\d*$"; - final public static String REGEX_LCNA = "^[a-z]+\\d+$"; - final public static String REGEX_VIAF = "^\\d*$"; - /** - * GND regex from https://www.wikidata.org/wiki/Property:P227 - */ - final public static String REGEX_GND = "^1[01]?\\d{7}[0-9X]|[47]\\d{6}-\\d|[1-9]\\d{0,7}-[0-9X]|3\\d{7}[0-9X]$"; - - /** - * Each author identification type has its own valid pattern/syntax. - */ - public static Pattern getValidPattern(String regex) { - return Pattern.compile(regex); + public String getIdentifierAsUrl() { + if (idType != null && !idType.isEmpty() && idValue != null && !idValue.isEmpty()) { + return getIdentifierAsUrl(idType, idValue); + } + return null; } - public String getIdentifierAsUrl() { + public static String getIdentifierAsUrl(String idType, String idValue) { if (idType != null && !idType.isEmpty() && idValue != null && !idValue.isEmpty()) { - DatasetFieldValueValidator datasetFieldValueValidator = new DatasetFieldValueValidator(); - switch (idType) { - case "ORCID": - if (datasetFieldValueValidator.isValidAuthorIdentifier(idValue, getValidPattern(REGEX_ORCID))) { - return "https://orcid.org/" + idValue; - } - break; - case "ISNI": - if (datasetFieldValueValidator.isValidAuthorIdentifier(idValue, getValidPattern(REGEX_ISNI))) { - return "http://www.isni.org/isni/" + idValue; - } - break; - case "LCNA": - if (datasetFieldValueValidator.isValidAuthorIdentifier(idValue, getValidPattern(REGEX_LCNA))) { - return "http://id.loc.gov/authorities/names/" + idValue; - } - break; - case "VIAF": - if (datasetFieldValueValidator.isValidAuthorIdentifier(idValue, getValidPattern(REGEX_VIAF))) { - return "https://viaf.org/viaf/" + idValue; - } - break; - case "GND": - if (datasetFieldValueValidator.isValidAuthorIdentifier(idValue, getValidPattern(REGEX_GND))) { - return "https://d-nb.info/gnd/" + idValue; - } - break; - default: - break; + try { + ExternalIdentifier externalIdentifier = ExternalIdentifier.valueOf(idType); + if (externalIdentifier.isValidIdentifier(idValue)) + return externalIdentifier.format(idValue); + } catch (Exception e) { + // non registered identifier } } return null; } - } diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetFieldCompoundValue.java b/src/main/java/edu/harvard/iq/dataverse/DatasetFieldCompoundValue.java index 407a1d57bd3..5d83f1e4f8d 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetFieldCompoundValue.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetFieldCompoundValue.java @@ -14,7 +14,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.ResourceBundle; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -25,7 +24,11 @@ import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Table; +import javax.persistence.Transient; + import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; /** * @@ -68,6 +71,19 @@ public static DatasetFieldCompoundValue createNewEmptyDatasetFieldCompoundValue( @OrderBy("datasetFieldType ASC") private List childDatasetFields = new ArrayList<>(); + // configurations for link creation + private static final Map> linkComponents = Map.of( + "author", new ImmutablePair<>("authorIdentifierScheme", "authorIdentifier") + ); + + // field for handling links. Annotation '@Transient' prevents these fields to be saved in DB + @Transient + private Map linkMap = new LinkedHashMap<>(); + @Transient + private String linkScheme = null; + @Transient + private String linkValue = null; + public Long getId() { return id; } @@ -133,15 +149,29 @@ public DatasetFieldCompoundValue copy(DatasetField parent) { return compoundValue; } - public Map getDisplayValueMap() { + public Map getDisplayValueMap() { // todo - this currently only supports child datasetfields with single values // need to determine how we would want to handle multiple Map fieldMap = new LinkedHashMap<>(); + linkMap.clear(); boolean fixTrailingComma = false; + Pair linkComponents = getLinkComponents(); + linkScheme = null; + linkValue = null; for (DatasetField childDatasetField : childDatasetFields) { fixTrailingComma = false; // skip the value if it is empty or N/A if (!StringUtils.isBlank(childDatasetField.getValue()) && !DatasetField.NA_VALUE.equals(childDatasetField.getValue())) { + if (linkComponents != null) { + if (fieldNameEquals(childDatasetField, linkComponents.getKey())) { + linkScheme = childDatasetField.getValue(); + } else if (fieldNameEquals(childDatasetField, linkComponents.getValue())) { + linkValue = childDatasetField.getValue(); + if (StringUtils.isNotBlank(linkScheme) && StringUtils.isNotBlank(linkValue)) + linkMap.put(childDatasetField, true); + } + } + String format = childDatasetField.getDatasetFieldType().getDisplayFormat(); if (StringUtils.isBlank(format)) { format = "#VALUE"; @@ -161,8 +191,8 @@ public Map getDisplayValueMap() { //todo: this should be handled in more generic way for any other text that can then be internationalized // if we need to use replaceAll for regexp, then make sure to use: java.util.regex.Matcher.quoteReplacement() .replace("#EMAIL", BundleUtil.getStringFromBundle("dataset.email.hiddenMessage")) - .replace("#VALUE", sanitizedValue ); - fieldMap.put(childDatasetField,displayValue); + .replace("#VALUE", sanitizedValue); + fieldMap.put(childDatasetField, displayValue); } } @@ -172,7 +202,29 @@ public Map getDisplayValueMap() { return fieldMap; } - + + public String getLink() { + return DatasetAuthor.getIdentifierAsUrl(linkScheme, linkValue); + } + + public boolean isLink(DatasetField datasetField) { + return linkMap.containsKey(datasetField) && linkMap.get(datasetField) == true && getLink() != null; + } + + private boolean fieldNameEquals(DatasetField datasetField, String linkTypeComponent) { + return datasetField.getDatasetFieldType().getName().equals(linkTypeComponent); + } + + public boolean isLinkableField() { + return linkComponents.containsKey(parentDatasetField.getDatasetFieldType().getName()); + } + + public Pair getLinkComponents() { + if (!isLinkableField()) + return null; + return linkComponents.get(parentDatasetField.getDatasetFieldType().getName()); + } + private Map removeLastComma(Map mapIn) { Iterator> itr = mapIn.entrySet().iterator(); @@ -192,6 +244,5 @@ private Map removeLastComma(Map mapI } return mapIn; - } } diff --git a/src/main/java/edu/harvard/iq/dataverse/ExternalIdentifier.java b/src/main/java/edu/harvard/iq/dataverse/ExternalIdentifier.java new file mode 100644 index 00000000000..0b7285c017e --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/ExternalIdentifier.java @@ -0,0 +1,58 @@ +package edu.harvard.iq.dataverse; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public enum ExternalIdentifier { + ORCID("ORCID", "https://orcid.org/%s", "^\\d{4}-\\d{4}-\\d{4}-(\\d{4}|\\d{3}X)$"), + ISNI("ISNI", "http://www.isni.org/isni/%s", "^\\d*$"), + LCNA("LCNA", "http://id.loc.gov/authorities/names/%s", "^[a-z]+\\d+$"), + VIAF("VIAF", "https://viaf.org/viaf/%s", "^\\d*$"), + // GND regex from https://www.wikidata.org/wiki/Property:P227 + GND("GND", "https://d-nb.info/gnd/%s", "^1[01]?\\d{7}[0-9X]|[47]\\d{6}-\\d|[1-9]\\d{0,7}-[0-9X]|3\\d{7}[0-9X]$"), + // note: DAI is missing from this list, because it doesn't have resolvable URL + ResearcherID("ResearcherID", "https://publons.com/researcher/%s/", "^[A-Z\\d][A-Z\\d-]+[A-Z\\d]$"), + ScopusID("ScopusID", "https://www.scopus.com/authid/detail.uri?authorId=%s", "^\\d*$"); + + private String name; + private String template; + private Pattern pattern; + private Matcher matcher; + + ExternalIdentifier(String name, String template, String regex) { + this.template = template; + this.pattern = Pattern.compile(regex); + this.matcher = pattern.matcher(""); + } + + public ExternalIdentifier of(String name) { + System.err.println(name); + for (ExternalIdentifier identifier : values()) { + System.err.println(" vs " + identifier.name); + if (identifier.name.toLowerCase().equals(name.toLowerCase())) { + return identifier; + } + } + return null; + } + + public boolean isValidIdentifier(String userInput) { + return matcher.reset(userInput).matches(); + } + + public String getName() { + return name; + } + + public String getTemplate() { + return template; + } + + public Pattern getPattern() { + return pattern; + } + + public String format(String idValue) { + return String.format(template, idValue); + } +} diff --git a/src/main/webapp/metadataFragment.xhtml b/src/main/webapp/metadataFragment.xhtml index 221db42a7c8..4702e52158f 100755 --- a/src/main/webapp/metadataFragment.xhtml +++ b/src/main/webapp/metadataFragment.xhtml @@ -73,7 +73,7 @@ - +

- + - + + + + + + + + + + @@ -124,13 +133,15 @@ -
+ +
+
- + - +
diff --git a/src/test/java/edu/harvard/iq/dataverse/DatasetAuthorTest.java b/src/test/java/edu/harvard/iq/dataverse/DatasetAuthorTest.java index 5b943448fca..fddb95eda9e 100644 --- a/src/test/java/edu/harvard/iq/dataverse/DatasetAuthorTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/DatasetAuthorTest.java @@ -31,6 +31,10 @@ public static Collection parameters() { { "LCNA", "n82058243", "http://id.loc.gov/authorities/names/n82058243" }, { "VIAF", "172389567", "https://viaf.org/viaf/172389567" }, { "GND", "4079154-3", "https://d-nb.info/gnd/4079154-3" }, + { "ResearcherID", "634082", "https://publons.com/researcher/634082/" }, + { "ResearcherID", "AAW-9289-2021", "https://publons.com/researcher/AAW-9289-2021/" }, + { "ResearcherID", "J-9733-2013", "https://publons.com/researcher/J-9733-2013/" }, + { "ScopusID", "6602344670", "https://www.scopus.com/authid/detail.uri?authorId=6602344670" }, { null, null, null, }, }); } @@ -44,5 +48,4 @@ public void getIdentifierAsUrl() { } assertEquals(expectedIdentifierAsUrl, datasetAuthor.getIdentifierAsUrl()); } - } diff --git a/src/test/java/edu/harvard/iq/dataverse/DatasetFieldValueValidatorTest.java b/src/test/java/edu/harvard/iq/dataverse/DatasetFieldValueValidatorTest.java index dedafe7722e..804b573c7b7 100644 --- a/src/test/java/edu/harvard/iq/dataverse/DatasetFieldValueValidatorTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/DatasetFieldValueValidatorTest.java @@ -141,7 +141,7 @@ public void testIsValid() { @Test public void testIsValidAuthorIdentifierOrcid() { DatasetFieldValueValidator validator = new DatasetFieldValueValidator(); - Pattern pattern = DatasetAuthor.getValidPattern(DatasetAuthor.REGEX_ORCID); + Pattern pattern = ExternalIdentifier.valueOf("ORCID").getPattern(); assertTrue(validator.isValidAuthorIdentifier("0000-0002-1825-0097", pattern)); // An "X" at the end of an ORCID is less common but still valid. assertTrue(validator.isValidAuthorIdentifier("0000-0002-1694-233X", pattern)); @@ -154,7 +154,7 @@ public void testIsValidAuthorIdentifierOrcid() { @Test public void testIsValidAuthorIdentifierIsni() { DatasetFieldValueValidator validator = new DatasetFieldValueValidator(); - Pattern pattern = DatasetAuthor.getValidPattern(DatasetAuthor.REGEX_ISNI); + Pattern pattern = ExternalIdentifier.valueOf("ISNI").getPattern(); assertTrue(validator.isValidAuthorIdentifier("0000000121032683", pattern)); assertFalse(validator.isValidAuthorIdentifier("junk", pattern)); } @@ -162,7 +162,7 @@ public void testIsValidAuthorIdentifierIsni() { @Test public void testIsValidAuthorIdentifierLcna() { DatasetFieldValueValidator validator = new DatasetFieldValueValidator(); - Pattern pattern = DatasetAuthor.getValidPattern(DatasetAuthor.REGEX_LCNA); + Pattern pattern = ExternalIdentifier.valueOf("LCNA").getPattern(); assertTrue(validator.isValidAuthorIdentifier("n82058243", pattern)); assertTrue(validator.isValidAuthorIdentifier("foobar123", pattern)); assertFalse(validator.isValidAuthorIdentifier("junk", pattern)); @@ -171,7 +171,7 @@ public void testIsValidAuthorIdentifierLcna() { @Test public void testIsValidAuthorIdentifierViaf() { DatasetFieldValueValidator validator = new DatasetFieldValueValidator(); - Pattern pattern = DatasetAuthor.getValidPattern(DatasetAuthor.REGEX_VIAF); + Pattern pattern = ExternalIdentifier.valueOf("VIAF").getPattern(); assertTrue(validator.isValidAuthorIdentifier("172389567", pattern)); assertFalse(validator.isValidAuthorIdentifier("junk", pattern)); } @@ -179,7 +179,7 @@ public void testIsValidAuthorIdentifierViaf() { @Test public void testIsValidAuthorIdentifierGnd() { DatasetFieldValueValidator validator = new DatasetFieldValueValidator(); - Pattern pattern = DatasetAuthor.getValidPattern(DatasetAuthor.REGEX_GND); + Pattern pattern = ExternalIdentifier.valueOf("GND").getPattern(); assertTrue(validator.isValidAuthorIdentifier("4079154-3", pattern)); assertFalse(validator.isValidAuthorIdentifier("junk", pattern)); } diff --git a/src/test/java/edu/harvard/iq/dataverse/ExternalIdentifierTest.java b/src/test/java/edu/harvard/iq/dataverse/ExternalIdentifierTest.java new file mode 100644 index 00000000000..c14d2e4086e --- /dev/null +++ b/src/test/java/edu/harvard/iq/dataverse/ExternalIdentifierTest.java @@ -0,0 +1,53 @@ +package edu.harvard.iq.dataverse; + +import org.junit.Test; + +import java.util.regex.Pattern; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class ExternalIdentifierTest { + + @Test + public void testIsValidAuthorIdentifierOrcid() { + ExternalIdentifier identifier = ExternalIdentifier.valueOf("ORCID"); + assertTrue(identifier.isValidIdentifier("0000-0002-1825-0097")); + // An "X" at the end of an ORCID is less common but still valid. + assertTrue(identifier.isValidIdentifier("0000-0002-1694-233X")); + assertFalse(identifier.isValidIdentifier("0000 0002 1825 0097")); + assertFalse(identifier.isValidIdentifier(" 0000-0002-1825-0097")); + assertFalse(identifier.isValidIdentifier("0000-0002-1825-0097 ")); + assertFalse(identifier.isValidIdentifier("junk")); + } + + @Test + public void testIsValidAuthorIdentifierIsni() { + ExternalIdentifier identifier = ExternalIdentifier.valueOf("ISNI"); + assertTrue(identifier.isValidIdentifier("0000000121032683")); + assertFalse(identifier.isValidIdentifier("junk")); + } + + @Test + public void testIsValidAuthorIdentifierLcna() { + ExternalIdentifier identifier = ExternalIdentifier.valueOf("LCNA"); + assertTrue(identifier.isValidIdentifier("n82058243")); + assertTrue(identifier.isValidIdentifier("foobar123")); + assertFalse(identifier.isValidIdentifier("junk")); + } + + @Test + public void testIsValidAuthorIdentifierViaf() { + ExternalIdentifier identifier = ExternalIdentifier.valueOf("VIAF"); + assertTrue(identifier.isValidIdentifier("172389567")); + assertFalse(identifier.isValidIdentifier("junk")); + } + + @Test + public void testIsValidAuthorIdentifierGnd() { + ExternalIdentifier identifier = ExternalIdentifier.valueOf("GND"); + assertTrue(identifier.isValidIdentifier("4079154-3")); + assertFalse(identifier.isValidIdentifier("junk")); + } + +}