Skip to content
This repository was archived by the owner on Oct 6, 2023. It is now read-only.

Commit cbfadfc

Browse files
committed
Merge branch 'v1.3' of https://github.com/doecode/server into v1.3
2 parents d641bd0 + 12ec33f commit cbfadfc

File tree

7 files changed

+187
-13
lines changed

7 files changed

+187
-13
lines changed

.gitignore

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
.settings/
2-
.classpath
1+
.settings/
2+
.classpath
33
nb-configuration.xml
44
nbproject/
5-
/target/
6-
/bin/
7-
.vagrant/
5+
/target/
6+
/bin/
7+
.vagrant/
8+
/nbactions.xml

pom.xml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<artifactId>doecode</artifactId>
66
<packaging>war</packaging>
77
<version>1.3</version>
8-
<name>DOE Code Web Application</name>
8+
<name>DOE Code Web Application (Server)</name>
99
<url>http://maven.apache.org</url>
1010

1111
<properties>
@@ -16,6 +16,7 @@
1616
<projectName>doecodeapi</projectName>
1717
<datacite.url>https://mds.test.datacite.org/</datacite.url>
1818
<environment>development</environment>
19+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
1920
</properties>
2021

2122
<dependencies>
@@ -330,6 +331,7 @@
330331
<configuration>
331332
<source>${jdk.version}</source>
332333
<target>${jdk.version}</target>
334+
<encoding>${project.build.sourceEncoding}</encoding>
333335
</configuration>
334336

335337

@@ -352,6 +354,14 @@
352354
<webXml>src/main/webapp/WEB-INF/web.xml</webXml>
353355
</configuration>
354356
</plugin>
357+
<plugin>
358+
<groupId>org.apache.maven.plugins</groupId>
359+
<artifactId>maven-resources-plugin</artifactId>
360+
<version>2.4.3</version>
361+
<configuration>
362+
<encoding>${project.build.sourceEncoding}</encoding>
363+
</configuration>
364+
</plugin>
355365
</plugins>
356366
</build>
357367
</project>

src/main/java/gov/osti/entity/Agent.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.annotation.JsonIgnore;
44
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
5+
import gov.osti.services.Validation;
56
import java.io.Serializable;
67
import javax.persistence.Column;
78
import javax.persistence.GeneratedValue;
@@ -85,9 +86,8 @@ public void setEmail(String email) {
8586
public String getOrcid() {
8687
return orcid;
8788
}
88-
8989
public void setOrcid(String orcid) {
90-
this.orcid = orcid;
90+
this.orcid = Validation.formatORCID(orcid, "dashed");
9191
}
9292

9393
/**

src/main/java/gov/osti/services/Metadata.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import gov.osti.connectors.SourceForge;
2323
import gov.osti.doi.DataCite;
2424
import gov.osti.entity.Agent;
25+
import gov.osti.entity.Contributor;
2526
import gov.osti.entity.MetadataSnapshot;
2627
import gov.osti.entity.DOECodeMetadata;
2728
import gov.osti.entity.DOECodeMetadata.Accessibility;
@@ -728,7 +729,7 @@ private void store(EntityManager em, DOECodeMetadata md, User user) throws NotFo
728729
// and thus ignored by the Bean copy; this sets the value regardless if setReleaseDate() got called
729730
if (md.hasSetReleaseDate())
730731
emd.setReleaseDate(md.getReleaseDate());
731-
732+
732733
// what comes back needs to be complete:
733734
noNulls.copyProperties(md, emd);
734735

@@ -1137,6 +1138,7 @@ private Response doAnnounce(String json, InputStream file, FormDataContentDispos
11371138
.badRequest(errors)
11381139
.build();
11391140
}
1141+
11401142
// send this to OSTI
11411143
OstiMetadata omd = new OstiMetadata();
11421144
omd.set(md);
@@ -1539,6 +1541,18 @@ else if (m.getLicenses().contains(DOECodeMetadata.License.Other.value()) && Stri
15391541
if (!Validation.isValidEmail(developer.getEmail()))
15401542
reasons.add("Developer email \"" + developer.getEmail() +"\" is not valid.");
15411543
}
1544+
if ( StringUtils.isNotBlank(developer.getOrcid()) ) {
1545+
if (!Validation.isValidORCID(developer.getOrcid()))
1546+
reasons.add("Developer ORCID \"" + developer.getOrcid() +"\" is not valid.");
1547+
}
1548+
}
1549+
}
1550+
if (!(null==m.getContributors() || m.getContributors().isEmpty())) {
1551+
for ( Contributor contributor : m.getContributors() ) {
1552+
if ( StringUtils.isNotBlank(contributor.getOrcid()) ) {
1553+
if (!Validation.isValidORCID(contributor.getOrcid()))
1554+
reasons.add("Contributor ORCID \"" + contributor.getOrcid() +"\" is not valid.");
1555+
}
15421556
}
15431557
}
15441558
// if "OS" accessibility, a REPOSITORY LINK is REQUIRED

src/main/java/gov/osti/services/NoNullsBeanUtilsBean.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ public void copyProperty(Object dest, String name, Object value)
2727
throws IllegalAccessException, InvocationTargetException {
2828
// skip any NULLs
2929
if (null!=value) {
30-
if (value.getClass() == java.lang.String.class) {
31-
String tmpStr = value.toString().trim();
32-
value = tmpStr.equalsIgnoreCase("") ? null : tmpStr;
33-
}
3430
super.copyProperty(dest, name, value);
3531
}
3632
}

src/main/java/gov/osti/services/Validation.java

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
66
import com.fasterxml.jackson.core.JsonParseException;
77
import com.fasterxml.jackson.databind.JsonMappingException;
8+
import com.fasterxml.jackson.databind.JsonNode;
89
import com.fasterxml.jackson.databind.ObjectMapper;
10+
import com.fasterxml.jackson.databind.node.ObjectNode;
911
import com.google.i18n.phonenumbers.NumberParseException;
1012
import com.google.i18n.phonenumbers.PhoneNumberUtil;
1113
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
@@ -14,7 +16,9 @@
1416
import gov.osti.repository.SubversionRepository;
1517
import java.io.IOException;
1618
import java.io.Serializable;
19+
import java.math.BigDecimal;
1720
import java.net.URLEncoder;
21+
import java.util.regex.Matcher;
1822
import java.util.regex.Pattern;
1923
import javax.servlet.ServletContext;
2024
import javax.servlet.http.HttpServletRequest;
@@ -108,6 +112,7 @@ public class Validation {
108112
protected static final Pattern EMAIL_PATTERN = Pattern.compile("^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$");
109113
protected static final Pattern URL_PATTERN = Pattern.compile("\\bhttps?://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
110114
protected static final Pattern DOI_PATTERN = Pattern.compile("10.\\d{4,9}/[-._;()/:A-Za-z0-9]+$");
115+
protected static final Pattern ORCID_PATTERN = Pattern.compile("(?i)^\\s*(?:(?:https?:\\/\\/)?orcid\\.org\\/)?(\\d{4}(?:-?\\d{4}){3})\\s*$");
111116

112117
@JsonIgnoreProperties (ignoreUnknown = true)
113118
private static class ValidationRequest implements Serializable {
@@ -157,6 +162,85 @@ public void setError(String error) {
157162
this.error = error;
158163
}
159164
}
165+
166+
/**
167+
* Generates check digit as per ISO 7064 11,2. (from orcid.org)
168+
* @param value the value to check.
169+
* @return string the checksum digit of the value.
170+
*/
171+
private static String generateCheckDigit(String value) {
172+
int total = 0;
173+
for (int i = 0; i < value.length(); i++) {
174+
int digit = Character.getNumericValue(value.charAt(i));
175+
total = (total + digit) * 2;
176+
}
177+
int remainder = total % 11;
178+
int result = (12 - remainder) % 11;
179+
return result == 10 ? "X" : String.valueOf(result);
180+
}
181+
182+
/**
183+
* Strip a string down to the known ORCID 16 digit value
184+
* @param value the value to format.
185+
* @return string the stripped down base 16 digits of the input ORCID, or null if value is invalid ORCID.
186+
*/
187+
private static String stripBaseORCID(String value) {
188+
if (value == null)
189+
return null;
190+
191+
// strip out valid 16 digit numeric
192+
Matcher matcher = ORCID_PATTERN.matcher(value);
193+
194+
if (matcher.find())
195+
// return matching 16 digit characters
196+
return matcher.group(1).replaceAll("-", "");
197+
else
198+
// no valid ORCID pattern
199+
return null;
200+
}
201+
202+
/**
203+
* Return a JSON of various formats of the ORCID value
204+
* @param value the value to format.
205+
* @return ObjectNode with three formats [base, dashed, url] of the value, or NULL if value is invalid ORCID.
206+
*/
207+
private static ObjectNode formatORCID(String value) {
208+
String baseValue = stripBaseORCID(value);
209+
210+
ObjectNode theNode = mapper.createObjectNode();
211+
212+
if (baseValue == null) {
213+
theNode.put("original", value);
214+
return theNode;
215+
}
216+
217+
String dashedValue = String.format("%1$s-%2$s-%3$s-%4$s", baseValue.substring(0,4), baseValue.substring(4,8), baseValue.substring(8,12), baseValue.substring(12));
218+
219+
theNode.put("base", baseValue);
220+
theNode.put("dashed", dashedValue);
221+
theNode.put("url", "https://orcid.org/" + dashedValue);
222+
223+
return theNode;
224+
}
225+
226+
/**
227+
* Return a specific format of the ORCID value
228+
* @param value the value to format.
229+
* @param format the specific format to return. Valid values [base, dashed, url].
230+
* @return string that matches the requested format, or NULL if value is invalid ORCID.
231+
*/
232+
public static String formatORCID(String value, String format) {
233+
ObjectNode theNode = formatORCID(value);
234+
235+
JsonNode originalValue = theNode.get("original");
236+
237+
// if value is invalid, we get back the original, and if it is NULL, return NULL.
238+
if (originalValue != null && originalValue.isNull())
239+
return null;
240+
241+
// otherwise, return the value of the original, or the formatted value.
242+
return (theNode.isNull()) ? null : (originalValue != null ? theNode.get("original").asText() : theNode.get(format).asText());
243+
}
160244

161245
/**
162246
* Creates a new instance of ValidationResource
@@ -283,6 +367,25 @@ public static boolean isValidDoi(String value) {
283367
return (null==value) ? false : DOI_PATTERN.matcher(value).find();
284368
}
285369

370+
/**
371+
* Check to see if ORCID is valid or not. Matches the pattern of a ORCID, and
372+
* has a valid checksum.
373+
*
374+
* @param value the ORCID to check
375+
* @return true if the ORCID is a valid pattern and passes checksum; false if not
376+
*/
377+
public static boolean isValidORCID(String value) {
378+
value = stripBaseORCID(value);
379+
if (value == null)
380+
return false;
381+
382+
// 16 digits found, now validate checksum
383+
String lastChar = (value.substring(value.length() - 1));
384+
String baseValue = (value.substring(0, value.length() - 1));
385+
386+
return lastChar.equals(generateCheckDigit(baseValue));
387+
}
388+
286389
/**
287390
* View the API documentation.
288391
*
@@ -351,6 +454,32 @@ public Response checkDoi(@QueryParam("value") String value) {
351454
ErrorResponse.badRequest("\"" + value + "\" is not a valid DOI.").build();
352455
}
353456

457+
/**
458+
* Check a ORCID.
459+
*
460+
* Response Codes:
461+
* 200 - OK, value is valid
462+
* 400 - Bad Request, value is NOT valid
463+
*
464+
* @param value the ORCID to check
465+
* @return a Response
466+
*/
467+
@GET
468+
@Produces (MediaType.APPLICATION_JSON)
469+
@Path ("/orcid")
470+
public Response checkORCID(@QueryParam("value") String value) {
471+
ObjectNode theNode = mapper.createObjectNode();
472+
473+
if (isValidORCID(value)) {
474+
theNode.put("value", "OK");
475+
theNode.set("format", formatORCID(value));
476+
477+
return Response.ok().entity(theNode.toString()).build();
478+
}
479+
else
480+
return ErrorResponse.badRequest("\"" + value + "\" is not a valid ORCID.").build();
481+
}
482+
354483
/**
355484
* Check an email address.
356485
*
@@ -457,6 +586,10 @@ public Response request(String object) throws IOException {
457586
req.setError((isValidEmail(req.getValue()) ? "" : req.getValue() + " is not a valid email address."));
458587
} else if (StringUtils.equalsIgnoreCase(req.getType(), "awardnumber")) {
459588
req.setError((isValidAwardNumber(req.getValue()) ? "" : req.getValue() + " is not a valid Award Number."));
589+
} else if (StringUtils.equalsIgnoreCase(req.getType(), "orcid")) {
590+
boolean isValid = isValidORCID(req.getValue());
591+
req.setError((isValid ? "" : req.getValue() + " is not a valid ORCID."));
592+
req.setValue((isValid ? req.getValue() : formatORCID(req.getValue(), "dashed")));
460593
} else {
461594
log.warn("Unknown validation request type: " + req.getType());
462595
return ErrorResponse

src/test/java/gov/osti/services/ValidationTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,5 +136,25 @@ public void testIsValidDoi() {
136136
assertTrue ("DOI " + doi + " should pass.", Validation.isValidDoi(doi));
137137
}
138138
}
139+
140+
@Test
141+
public void testIsValidORCID() {
142+
// some bad non-DOI patterns
143+
String[] invalid = { "12345678901234567", "000000021825009X",
144+
"http://notaorcid.com/", ""
145+
};
146+
// valid DOI patterns
147+
String[] valid = { "0000000218250097", "0000-0003-3348-0736",
148+
"HTTPS://orcid.org/0000-0001-8811-2688", " 00000003-33480736 "
149+
};
150+
151+
for ( String orcid : invalid ) {
152+
assertFalse ("Shouldn't accept: " + orcid, Validation.isValidORCID(orcid));
153+
}
154+
155+
for ( String orcid : valid ) {
156+
assertTrue ("ORCID " + orcid + " should pass.", Validation.isValidORCID(orcid));
157+
}
158+
}
139159

140160
}

0 commit comments

Comments
 (0)