Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ability to reserve DOIs through DataCite #5093 #6901

Merged
merged 20 commits into from
Jun 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
49a5091
for DataCite, get PID (draft state) on create #5093
pdurbin May 8, 2020
cba83f5
add API to query PID state (e.g. draft vs. findable) #5093
pdurbin May 11, 2020
e72616f
add APIs to reserve PIDs and list unreserved PIDs #5093
pdurbin May 21, 2020
431ee35
have "get DOI" accept normal form with doi: #5093
pdurbin May 22, 2020
9cbd5d3
allow dataset to be created if DataCite is down #5093
pdurbin May 22, 2020
e8e4c98
add docs for undocumented APIs (list, reserve) #5093
pdurbin May 22, 2020
28f3222
grey out publish button if DataCite and no PID create time #5093
pdurbin May 26, 2020
8aacc92
add API to delete DOIs when possible #5093
pdurbin May 27, 2020
f58b24e
prevent publishing of unreserved PIDs #5093
pdurbin May 28, 2020
85fd5be
get deleteIdentifier working for DataCite DOIs #5093
pdurbin May 28, 2020
62509c8
only set the create time if createIdentifier succeeded #5093
pdurbin May 29, 2020
1dfe944
finish removing RuntimeException (see 62509c89f) #5093
pdurbin May 29, 2020
786a7a4
move English to bundle #5093
pdurbin May 29, 2020
27da57a
document "delete PID" API endpoint #5093
pdurbin May 29, 2020
c700f61
Merge branch 'develop' into 5093-datacite #5093
pdurbin Jun 1, 2020
e1914ee
correct docs: "doi:" required for get PID API #5093
pdurbin Jun 3, 2020
dde481c
add release note #5093
pdurbin Jun 3, 2020
c1be93f
correct PID API docs for delete and reserve #5093
pdurbin Jun 4, 2020
7821ac7
more descriptive error on bad API token than "ERROR" #5093
pdurbin Jun 4, 2020
6bc7d93
cross ref PID-related docs #5093
pdurbin Jun 4, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/release-notes/5093-datacite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
If you are using DataCite as your DOI provider you must add a new JVM option called "doi.baseurlstringnext" with a value of "https://api.datacite.org" for production environments and "https://api.test.datacite.org" for test environments. More information about this JVM option can be found in the Installation Guide.
5 changes: 5 additions & 0 deletions doc/sphinx-guides/source/admin/dataverses-datasets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ Forces update to metadata provided to the PID provider of a published dataset. O

curl -H "X-Dataverse-key: $API_TOKEN" -X POST http://$SERVER/api/datasets/$dataset-id/modifyRegistrationMetadata

Check for Unreserved PIDs and Reserve Them
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

See :ref:`pids-api` in the API Guide for details.

Make Metadata Updates Without Changing Dataset Version
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
92 changes: 92 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2519,6 +2519,98 @@ Each user can get a dump of their notifications by passing in their API token::

curl -H "X-Dataverse-key:$API_TOKEN" $SERVER_URL/api/notifications/all

.. _pids-api:

PIDs
----

PIDs is short for Persistent IDentifiers. Examples include DOI or Handle. There are some additional PID operations listed in the :doc:`/admin/dataverses-datasets` section of the Admin Guide.

Get Info on a PID
~~~~~~~~~~~~~~~~~

Get information on a PID, especially its "state" such as "draft" or "findable". Currently, this API only works on DataCite DOIs. A superuser API token is required.

.. note:: See :ref:`curl-examples-and-environment-variables` if you are unfamiliar with the use of export below.

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export PID=doi:10.70122/FK2/9BXT5O

curl -H "X-Dataverse-key:$API_TOKEN" $SERVER_URL/api/pids?persistentId=$PID

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx https://demo.dataverse.org/api/pids?persistentId=doi:10.70122/FK2/9BXT5O

List Unreserved PIDs
~~~~~~~~~~~~~~~~~~~~

Get a list of PIDs that have not been reserved on the PID provider side. This can happen, for example, if a dataset is created while the PID provider is down. A superuser API token is required.

.. note:: See :ref:`curl-examples-and-environment-variables` if you are unfamiliar with the use of export below.

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org

curl -H "X-Dataverse-key:$API_TOKEN" $SERVER_URL/api/pids/unreserved


The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx https://demo.dataverse.org/api/pids/unreserved

Reserve a PID
~~~~~~~~~~~~~

Reserved a PID for a dataset. A superuser API token is required.

.. note:: See :ref:`curl-examples-and-environment-variables` if you are unfamiliar with the use of export below.

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export PID=doi:10.70122/FK2/9BXT5O

curl -H "X-Dataverse-key:$API_TOKEN" -X POST $SERVER_URL/api/pids/:persistentId/reserve?persistentId=$PID

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -X POST https://demo.dataverse.org/api/pids/:persistentId/reserve?persistentId=doi:10.70122/FK2/9BXT5O

Delete a PID
~~~~~~~~~~~~

Delete PID from DataCite (this is only possible for PIDs that are in the "draft" state) and within Dataverse, set ``globalidcreatetime`` to null and ``identifierregistered`` to false. A superuser API token is required.

.. note:: See :ref:`curl-examples-and-environment-variables` if you are unfamiliar with the use of export below.

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export PID=doi:10.70122/FK2/9BXT5O

curl -H "X-Dataverse-key:$API_TOKEN" -X DELETE $SERVER_URL/api/pids/:persistentId/delete?persistentId=$PID

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -X DELETE https://demo.dataverse.org/api/pids/:persistentId/delete?persistentId=doi:10.70122/FK2/9BXT5O


.. _admin:

Admin
Expand Down
14 changes: 14 additions & 0 deletions doc/sphinx-guides/source/installation/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,20 @@ See also these related database settings below:
- :ref:`:Authority`
- :ref:`:Shoulder`

.. _doi.baseurlstringnext

doi.baseurlstringnext
+++++++++++++++++++++

Dataverse uses multiple APIs from DataCite:

- The DataCite MDS API is older, XML-based, and configured using :ref:`doi.baseurlstring`.
- The DataCite REST API is newer, JSON-based, and configured using ``doi.baseurlstringnext``.

In production, ``doi.baseurlstringnext`` should be set to ``https://api.datacite.org``
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is specific to DataCite, it might be better to name it for that - doi.dataciteapibaseurl or something. (FWIW - I could swear that at some point I discussed adding a second option versus swapping mds for api in baseurlstring with someone and decided against having a second option, but I can't find code nor conversation...)


While testing, ``doi.baseurlstringnext`` should be set to ``https://api.test.datacite.org``

.. _doi.mdcbaseurlstring:

doi.mdcbaseurlstring
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ private DataCiteRESTfullClient getClient() throws IOException {
return client;
}

/**
* This method is deprecated and unused. We switched away from this method
* when adjusting the code to reserve DOIs from DataCite on dataset create.
*
* Note that the DOIDataCiteRegisterCache entity/table used in this method
* might be a candidate for deprecation as well. Removing it would require
* some refactoring as it is used throughout the DataCite code.
*/
@Deprecated
public String createIdentifierLocal(String identifier, Map<String, String> metadata, DvObject dvObject) {

String xmlMetadata = getMetadataFromDvObject(identifier, metadata, dvObject);
Expand All @@ -83,6 +92,49 @@ public String createIdentifierLocal(String identifier, Map<String, String> metad
return retString;
}

/**
* This "reserveIdentifier" method is heavily based on the
* "registerIdentifier" method below but doesn't, this one doesn't doesn't
* register a URL, which causes the "state" of DOI to transition from
* "draft" to "findable". Here are some DataCite docs on the matter:
*
* "DOIs can exist in three states: draft, registered, and findable. DOIs
* are in the draft state when metadata have been registered, and will
* transition to the findable state when registering a URL." --
* https://support.datacite.org/docs/mds-api-guide#doi-states
*/
public String reserveIdentifier(String identifier, Map<String, String> metadata, DvObject dvObject) throws IOException {
String retString = "";
String xmlMetadata = getMetadataFromDvObject(identifier, metadata, dvObject);
DOIDataCiteRegisterCache rc = findByDOI(identifier);
String target = metadata.get("_target");
if (rc != null) {
rc.setDoi(identifier);
rc.setXml(xmlMetadata);
// DataCite uses the term "draft" instead of "reserved".
rc.setStatus("reserved");
if (target == null || target.trim().length() == 0) {
target = rc.getUrl();
} else {
rc.setUrl(target);
}
try {
DataCiteRESTfullClient client = getClient();
retString = client.postMetadata(xmlMetadata);
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(DOIDataCiteRegisterService.class.getName()).log(Level.SEVERE, null, ex);
}
} else {
try {
DataCiteRESTfullClient client = getClient();
retString = client.postMetadata(xmlMetadata);
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(DOIDataCiteRegisterService.class.getName()).log(Level.SEVERE, null, ex);
}
}
return retString;
}

public String registerIdentifier(String identifier, Map<String, String> metadata, DvObject dvObject) throws IOException {
String retString = "";
String xmlMetadata = getMetadataFromDvObject(identifier, metadata, dvObject);
Expand Down Expand Up @@ -116,7 +168,7 @@ public String registerIdentifier(String identifier, Map<String, String> metadata
return retString;
}

public String deactivateIdentifier(String identifier, Map<String, String> metadata, DvObject dvObject) {
public String deactivateIdentifier(String identifier, Map<String, String> metadata, DvObject dvObject) throws IOException {
String retString = "";

String metadataString = getMetadataForDeactivateIdentifier(identifier, metadata, dvObject);
Expand Down
50 changes: 15 additions & 35 deletions src/main/java/edu/harvard/iq/dataverse/DOIDataCiteServiceBean.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package edu.harvard.iq.dataverse;

import edu.harvard.iq.dataverse.pidproviders.PidUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -31,7 +33,7 @@ public DOIDataCiteServiceBean() {

@Override
public boolean registerWhenPublished() {
return true;
return false;
}

@Override
Expand Down Expand Up @@ -72,7 +74,7 @@ public String createIdentifier(DvObject dvObject) throws Exception {
Map<String, String> metadata = getMetadataForCreateIndicator(dvObject);
metadata.put("_status", "reserved");
try {
String retString = doiDataCiteRegisterService.createIdentifierLocal(identifier, metadata, dvObject);
String retString = doiDataCiteRegisterService.reserveIdentifier(identifier, metadata, dvObject);
logger.log(Level.FINE, "create DOI identifier retString : " + retString);
return retString;
} catch (Exception e) {
Expand Down Expand Up @@ -169,40 +171,18 @@ public void deleteRecordFromCache(Dataset datasetIn){
}
}

/**
* Deletes DOI from the DataCite side, if possible. Only "draft" DOIs can be
* deleted.
*/
@Override
public void deleteIdentifier(DvObject dvObject) throws Exception {
logger.log(Level.FINE,"deleteIdentifier");
String identifier = getIdentifier(dvObject);
Map<String, String> doiMetadata = new HashMap<>();
try {
doiMetadata = doiDataCiteRegisterService.getMetadata(identifier);
} catch (Exception e) {
logger.log(Level.WARNING, "deleteIdentifier: get matadata failed. " + e.getMessage(), e);
}

String idStatus = doiMetadata.get("_status");
if ( idStatus != null ) {
switch ( idStatus ) {
case RESERVED:
case DRAFT:
logger.log(Level.INFO, "Delete status is reserved..");
try {
doiDataCiteRegisterService.deleteIdentifier(identifier);
} catch (Exception e) {
logger.log(Level.WARNING, "delete failed: " + e.getMessage(), e);
}
break;

case PUBLIC:
case FINDABLE:
//if public then it has been released set to unavailable and reset target to n2t url
Map<String, String> metadata = addDOIMetadataForDestroyedDataset(dvObject);
metadata.put("_status", "registered");
metadata.put("_target", getTargetUrl(dvObject));
doiDataCiteRegisterService.deactivateIdentifier(identifier, metadata, dvObject);
break;
}
}
public void deleteIdentifier(DvObject dvObject) throws IOException {
String baseUrl = System.getProperty("doi.baseurlstringnext");
String username = System.getProperty("doi.username");
String password = System.getProperty("doi.password");
String pid = dvObject.getGlobalId().asString();
int result = PidUtil.deleteDoi(pid, baseUrl, username, password);
logger.fine("Result of deleteIdentifier for " + pid + ": " + result);
}

private boolean updateIdentifierStatus(DvObject dvObject, String statusIn) {
Expand Down
26 changes: 9 additions & 17 deletions src/main/java/edu/harvard/iq/dataverse/DataCiteRESTfullClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -175,26 +175,18 @@ public boolean testDOIExists(String doi) {
* @param metadata
* @return
*/
public String postMetadata(String metadata) {

public String postMetadata(String metadata) throws IOException {
HttpPost httpPost = new HttpPost(this.url + "/metadata");
httpPost.setHeader("Content-Type", "application/xml;charset=UTF-8");
try {
httpPost.setEntity(new StringEntity(metadata, "utf-8"));
HttpResponse response = httpClient.execute(httpPost,context);

String data = EntityUtils.toString(response.getEntity(), encoding);
if (response.getStatusLine().getStatusCode() != 201) {
String errMsg = "Response code: " + response.getStatusLine().getStatusCode() + ", " + data;
logger.log(Level.SEVERE, errMsg);
throw new RuntimeException(errMsg);
}
return data;

} catch (IOException ioe) {
logger.log(Level.SEVERE, "IOException when post metadata");
throw new RuntimeException("IOException when post metadata", ioe);
httpPost.setEntity(new StringEntity(metadata, "utf-8"));
HttpResponse response = httpClient.execute(httpPost, context);
String data = EntityUtils.toString(response.getEntity(), encoding);
if (response.getStatusLine().getStatusCode() != 201) {
String errMsg = "Response code: " + response.getStatusLine().getStatusCode() + ", " + data;
logger.log(Level.SEVERE, errMsg);
throw new IOException(errMsg);
}
return data;
}

/**
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/SettingsWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,16 @@ public boolean isDoiInstallation() {
return false;
}
}


public boolean isDataCiteInstallation() {
String protocol = getValueForKey(SettingsServiceBean.Key.DoiProvider);
if ("DataCite".equals(protocol)) {
return true;
} else {
return false;
}
}

public boolean isMakeDataCountDisplayEnabled() {
boolean safeDefaultIfKeyNotFound = (getValueForKey(SettingsServiceBean.Key.MDCLogPath)!=null); //Backward compatible
return isTrueForKey(SettingsServiceBean.Key.DisplayMDCMetrics, safeDefaultIfKeyNotFound);
Expand Down