Fixes from testing

Conflicts:
	dspace-api/src/main/java/org/dspace/identifier/DataCiteIdentifierProvider.java
	dspace-api/src/test/java/org/dspace/identifier/DataCiteIdentifierProviderTest.java
This commit is contained in:
Mark H. Wood
2012-11-09 15:09:08 -05:00
committed by Pascal-Nicolas Becker
parent 98ee2d7e43
commit e41eb134ed
7 changed files with 473 additions and 247 deletions

View File

@@ -16,4 +16,5 @@ package org.dspace.identifier;
public class DOI public class DOI
implements Identifier implements Identifier
{ {
public static final String SCHEME = "doi:";
} }

View File

@@ -11,10 +11,11 @@ package org.dspace.identifier;
import java.io.IOException; import java.io.IOException;
import java.net.*; import java.net.*;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.logging.Level;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCValue; import org.dspace.content.DCValue;
import org.dspace.content.DSpaceObject; import org.dspace.content.DSpaceObject;
@@ -60,15 +61,17 @@ public class DataCiteIdentifierProvider
private static final Logger log = LoggerFactory.getLogger(DataCiteIdentifierProvider.class); private static final Logger log = LoggerFactory.getLogger(DataCiteIdentifierProvider.class);
// Configuration property names // Configuration property names
private static final String CFG_SHOULDER = "identifier.doi.ezid.shoulder"; static final String CFG_SHOULDER = "identifier.doi.ezid.shoulder";
private static final String CFG_USER = "identifier.doi.ezid.user"; static final String CFG_USER = "identifier.doi.ezid.user";
private static final String CFG_PASSWORD = "identifier.doi.ezid.password"; static final String CFG_PASSWORD = "identifier.doi.ezid.password";
// Metadata field name elements // Metadata field name elements
// XXX move these to MetadataSchema or some such // XXX move these to MetadataSchema or some such
public static final String MD_SCHEMA_DSPACE = "dspace"; public static final String MD_SCHEMA = "dc";
public static final String DSPACE_DOI_ELEMENT = "identifier"; public static final String DOI_ELEMENT = "identifier";
public static final String DSPACE_DOI_QUALIFIER = "doi"; public static final String DOI_QUALIFIER = null;
private static final String DOI_SCHEME = "doi:";
/** Map DataCite metadata into local metadata. */ /** Map DataCite metadata into local metadata. */
private static Map<String, String> crosswalk = new HashMap<String, String>(); private static Map<String, String> crosswalk = new HashMap<String, String>();
@@ -85,7 +88,10 @@ public class DataCiteIdentifierProvider
@Override @Override
public boolean supports(String identifier) public boolean supports(String identifier)
{ {
return identifier.startsWith("doi:"); // XXX more thorough test? if (null == identifier)
return false;
else
return identifier.startsWith(DOI_SCHEME); // XXX more thorough test?
} }
@Override @Override
@@ -94,22 +100,20 @@ public class DataCiteIdentifierProvider
{ {
log.debug("register {}", dso); log.debug("register {}", dso);
Item item; if (!(dso instanceof Item))
if (dso instanceof Item)
item = (Item)dso;
else
throw new IdentifierException("Unsupported object type " + dso.getTypeText()); throw new IdentifierException("Unsupported object type " + dso.getTypeText());
String id; Item item = (Item)dso;
DCValue[] previous = item.getMetadata(MD_SCHEMA_DSPACE, DSPACE_DOI_ELEMENT, DSPACE_DOI_QUALIFIER, null); DCValue[] identifiers = item.getMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null);
if ((previous.length > 0) && (null != previous[0].value)) for (DCValue identifier : identifiers)
return previous[0].value; if ((null != identifier.value) && (identifier.value.startsWith(DOI_SCHEME)))
return identifier.value;
id = mint(context, item); String id = mint(context, item);
item.addMetadata(MD_SCHEMA_DSPACE, DSPACE_DOI_ELEMENT, DSPACE_DOI_QUALIFIER, null, id); item.addMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, id);
try { try {
item.update(); item.update();
context.commit();
} catch (SQLException ex) { } catch (SQLException ex) {
throw new IdentifierException("New identifier not stored", ex); throw new IdentifierException("New identifier not stored", ex);
} catch (AuthorizeException ex) { } catch (AuthorizeException ex) {
@@ -132,30 +136,29 @@ public class DataCiteIdentifierProvider
} }
EZIDResponse response; EZIDResponse response;
String doi = "unknown"; // In case we can't even build a name
try { try {
doi = getShoulder() + identifier; EZIDRequest request = requestFactory.getInstance(loadAuthority(),
EZIDRequest request = requestFactory.getInstance(doi, loadUser(), loadPassword());
getUser(), getPassword()); response = request.create(identifier, crosswalkMetadata(object));
response = request.create(crosswalkMetadata(object));
} catch (IdentifierException e) { } catch (IdentifierException e) {
log.error("doi:{} not registered: {}", doi, e.getMessage()); log.error("Identifier '{}' not registered: {}", identifier, e.getMessage());
return; return;
} catch (IOException e) { } catch (IOException e) {
log.error("doi:{} not registered: {}", doi, e.getMessage()); log.error("Identifier '{}' not registered: {}", identifier, e.getMessage());
return; return;
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
log.error("doi:{} not registered: {}", doi, e.getMessage()); log.error("Identifier '{}' not registered: {}", identifier, e.getMessage());
return; return;
} }
if (response.isSuccess()) if (response.isSuccess())
{ {
Item item = (Item)object; Item item = (Item)object;
item.addMetadata(MD_SCHEMA_DSPACE, DSPACE_DOI_ELEMENT,
DSPACE_DOI_QUALIFIER, null, identifier);
try { try {
item.addMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null,
idToDOI(identifier));
item.update(); item.update();
context.commit();
log.info("registered {}", identifier); log.info("registered {}", identifier);
} catch (SQLException ex) { } catch (SQLException ex) {
// TODO throw new IdentifierException("New identifier not stored", ex); // TODO throw new IdentifierException("New identifier not stored", ex);
@@ -163,12 +166,14 @@ public class DataCiteIdentifierProvider
} catch (AuthorizeException ex) { } catch (AuthorizeException ex) {
// TODO throw new IdentifierException("New identifier not stored", ex); // TODO throw new IdentifierException("New identifier not stored", ex);
log.error("New identifier not stored", ex); log.error("New identifier not stored", ex);
} catch (IdentifierException ex) {
log.error("New identifier not stored", ex);
} }
} }
else else
{ {
log.error("doi:{} not registered -- EZID returned: {}", doi, log.error("Identifier '{}' not registered -- EZID returned: {}",
response.getEZIDStatusValue()); identifier, response.getEZIDStatusValue());
} }
} }
@@ -179,29 +184,27 @@ public class DataCiteIdentifierProvider
log.debug("reserve {}", identifier); log.debug("reserve {}", identifier);
EZIDResponse response; EZIDResponse response;
String doi = "unknown"; // In case we can't even build a name
try { try {
doi = getShoulder() + identifier; EZIDRequest request = requestFactory.getInstance(loadAuthority(),
EZIDRequest request = requestFactory.getInstance(doi, loadUser(), loadPassword());
getUser(), getPassword());
Map<String, String> metadata = crosswalkMetadata(dso); Map<String, String> metadata = crosswalkMetadata(dso);
metadata.put("_status", "reserved"); metadata.put("_status", "reserved");
response = request.create(metadata); response = request.create(identifier, metadata);
} catch (IOException e) { } catch (IOException e) {
log.error("doi:{} not registered: {}", doi, e.getMessage()); log.error("Identifier '{}' not registered: {}", identifier, e.getMessage());
return; return;
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
log.error("doi:{} not registered: {}", doi, e.getMessage()); log.error("Identifier '{}' not registered: {}", identifier, e.getMessage());
return; return;
} }
if (response.isSuccess()) if (response.isSuccess())
{ {
Item item = (Item)dso; Item item = (Item)dso;
item.addMetadata(MD_SCHEMA_DSPACE, DSPACE_DOI_ELEMENT, item.addMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null, idToDOI(identifier));
DSPACE_DOI_QUALIFIER, null, identifier);
try { try {
item.update(); item.update();
context.commit();
log.info("reserved {}", identifier); log.info("reserved {}", identifier);
} catch (SQLException ex) { } catch (SQLException ex) {
throw new IdentifierException("New identifier not stored", ex); throw new IdentifierException("New identifier not stored", ex);
@@ -211,8 +214,8 @@ public class DataCiteIdentifierProvider
} }
else else
{ {
log.error("doi:{} not registered -- EZID returned: {}", doi, log.error("Identifier '{}' not registered -- EZID returned: {}",
response.getEZIDStatusValue()); identifier, response.getEZIDStatusValue());
} }
} }
@@ -225,7 +228,7 @@ public class DataCiteIdentifierProvider
// Compose the request // Compose the request
EZIDRequest request; EZIDRequest request;
try { try {
request = requestFactory.getInstance(getShoulder(), getUser(), getPassword()); request = requestFactory.getInstance(loadAuthority(), loadUser(), loadPassword());
} catch (URISyntaxException ex) { } catch (URISyntaxException ex) {
log.error(ex.getMessage()); log.error(ex.getMessage());
throw new IdentifierException("DOI request not sent: " + ex.getMessage()); throw new IdentifierException("DOI request not sent: " + ex.getMessage());
@@ -236,8 +239,10 @@ public class DataCiteIdentifierProvider
try try
{ {
response = request.mint(crosswalkMetadata(dso)); response = request.mint(crosswalkMetadata(dso));
} catch (IOException ex) } catch (IOException ex) {
{ log.error("Failed to send EZID request: {}", ex.getMessage());
throw new IdentifierException("DOI request not sent: " + ex.getMessage());
} catch (URISyntaxException ex) {
log.error("Failed to send EZID request: {}", ex.getMessage()); log.error("Failed to send EZID request: {}", ex.getMessage());
throw new IdentifierException("DOI request not sent: " + ex.getMessage()); throw new IdentifierException("DOI request not sent: " + ex.getMessage());
} }
@@ -275,27 +280,33 @@ public class DataCiteIdentifierProvider
{ {
log.debug("resolve {}", identifier); log.debug("resolve {}", identifier);
try ItemIterator found;
{ try {
ItemIterator found = Item.findByMetadataField(context, MD_SCHEMA_DSPACE, DSPACE_DOI_ELEMENT, DSPACE_DOI_QUALIFIER, found = Item.findByMetadataField(context,
identifier); MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER,
idToDOI(identifier));
} catch (IdentifierException ex) {
log.error(ex.getMessage());
throw new IdentifierNotResolvableException(ex);
} catch (SQLException ex) {
log.error(ex.getMessage());
throw new IdentifierNotResolvableException(ex);
} catch (AuthorizeException ex) {
log.error(ex.getMessage());
throw new IdentifierNotResolvableException(ex);
} catch (IOException ex) {
log.error(ex.getMessage());
throw new IdentifierNotResolvableException(ex);
}
try {
if (!found.hasNext()) if (!found.hasNext())
throw new IdentifierNotFoundException("No Item bound to DOI " + identifier); throw new IdentifierNotFoundException("No object bound to " + identifier);
Item found1 = found.next(); Item found1 = found.next();
if (found.hasNext()) if (found.hasNext())
log.error("DOI {} multiply bound!", identifier); log.error("More than one object bound to {}!", identifier);
log.debug("Resolved to {}", found1); log.debug("Resolved to {}", found1);
return found1; return found1;
} catch (SQLException ex) } catch (SQLException ex) {
{
log.error(ex.getMessage());
throw new IdentifierNotResolvableException(ex);
} catch (AuthorizeException ex)
{
log.error(ex.getMessage());
throw new IdentifierNotResolvableException(ex);
} catch (IOException ex)
{
log.error(ex.getMessage()); log.error(ex.getMessage());
throw new IdentifierNotResolvableException(ex); throw new IdentifierNotResolvableException(ex);
} }
@@ -307,16 +318,21 @@ public class DataCiteIdentifierProvider
{ {
log.debug("lookup {}", object); log.debug("lookup {}", object);
Item item;
if (!(object instanceof Item)) if (!(object instanceof Item))
throw new IllegalArgumentException("Unsupported type " + object.getTypeText()); throw new IllegalArgumentException("Unsupported type " + object.getTypeText());
item = (Item)object; Item item = (Item)object;
DCValue[] metadata = item.getMetadata(MD_SCHEMA_DSPACE, DSPACE_DOI_ELEMENT, DSPACE_DOI_QUALIFIER, null); DCValue found = null;
if (metadata.length > 0) for (DCValue candidate : item.getMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null))
if (candidate.value.startsWith(DOI_SCHEME))
{
found = candidate;
break;
}
if (null != found)
{ {
log.debug("Found {}", metadata[0].value); log.debug("Found {}", found.value);
return metadata[0].value; return found.value;
} }
else else
throw new IdentifierNotFoundException(object.getTypeText() + " " throw new IdentifierNotFoundException(object.getTypeText() + " "
@@ -332,34 +348,62 @@ public class DataCiteIdentifierProvider
if (!(dso instanceof Item)) if (!(dso instanceof Item))
throw new IllegalArgumentException("Unsupported type " + dso.getTypeText()); throw new IllegalArgumentException("Unsupported type " + dso.getTypeText());
String username = configurationService.getProperty(CFG_USER);
String password = configurationService.getProperty(CFG_PASSWORD);
if (null == username || null == password)
throw new IdentifierException("Unconfigured: define " + CFG_USER
+ " and " + CFG_PASSWORD);
Item item = (Item)dso; Item item = (Item)dso;
// delete from EZID // delete from EZID
for (DCValue id : item.getMetadata(MD_SCHEMA_DSPACE, DSPACE_DOI_ELEMENT, DCValue[] metadata = item.getMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null);
DSPACE_DOI_QUALIFIER, null)) List<String> remainder = new ArrayList<String>();
int skipped = 0;
for (DCValue id : metadata)
{ {
if (!id.value.startsWith(DOI_SCHEME))
{
remainder.add(id.value);
continue;
}
EZIDResponse response; EZIDResponse response;
try { try {
EZIDRequest request = requestFactory.getInstance(id.value, username, password); EZIDRequest request = requestFactory.getInstance(loadAuthority(),
response = request.delete(); loadUser(), loadPassword());
response = request.delete(DOIToId(id.value));
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
throw new IdentifierException("Bad URI in metadata value", e); log.error("Bad URI in metadata value: {}", e.getMessage());
remainder.add(id.value);
skipped++;
continue;
} catch (IOException e) { } catch (IOException e) {
throw new IdentifierException("Failed request to EZID", e); log.error("Failed request to EZID: {}", e.getMessage());
remainder.add(id.value);
skipped++;
continue;
} }
if (!response.isSuccess()) if (!response.isSuccess())
throw new IdentifierException("Unable to delete " + id.value {
+ "from DataCite: " + response.getEZIDStatusValue()); log.error("Unable to delete {} from DataCite: {}", id.value,
response.getEZIDStatusValue());
remainder.add(id.value);
skipped++;
continue;
}
log.info("Deleted {}", id.value);
} }
// delete from item // delete from item
item.clearMetadata(MD_SCHEMA_DSPACE, DSPACE_DOI_ELEMENT, DSPACE_DOI_QUALIFIER, null); item.clearMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null);
item.addMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null,
remainder.toArray(new String[remainder.size()]));
try {
item.update();
context.commit();
} catch (SQLException e) {
log.error("Failed to re-add identifiers: {}", e.getMessage());
} catch (AuthorizeException e) {
log.error("Failed to re-add identifiers: {}", e.getMessage());
}
if (skipped > 0)
throw new IdentifierException(skipped + " identifiers could not be deleted.");
} }
@Override @Override
@@ -368,14 +412,98 @@ public class DataCiteIdentifierProvider
{ {
log.debug("delete {} from {}", identifier, dso); log.debug("delete {} from {}", identifier, dso);
throw new UnsupportedOperationException("Not supported yet."); // TODO implement delete(specific) if (!(dso instanceof Item))
// TODO find metadata value == identifier throw new IllegalArgumentException("Unsupported type " + dso.getTypeText());
// TODO delete from EZID
// TODO delete from item NOTE!!! can't delete single MD values! Item item = (Item)dso;
DCValue[] metadata = item.getMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null);
List<String> remainder = new ArrayList<String>();
int skipped = 0;
for (DCValue id : metadata)
{
if (!id.value.equals(idToDOI(identifier)))
{
remainder.add(id.value);
continue;
}
EZIDResponse response;
try {
EZIDRequest request = requestFactory.getInstance(loadAuthority(),
loadUser(), loadPassword());
response = request.delete(DOIToId(id.value));
} catch (URISyntaxException e) {
log.error("Bad URI in metadata value {}: {}", id.value, e.getMessage());
remainder.add(id.value);
skipped++;
continue;
} catch (IOException e) {
log.error("Failed request to EZID: {}", e.getMessage());
remainder.add(id.value);
skipped++;
continue;
}
if (!response.isSuccess())
{
log.error("Unable to delete {} from DataCite: {}", id.value,
response.getEZIDStatusValue());
remainder.add(id.value);
skipped++;
continue;
}
log.info("Deleted {}", id.value);
}
// delete from item
item.clearMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null);
item.addMetadata(MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null,
remainder.toArray(new String[remainder.size()]));
try {
item.update();
context.commit();
} catch (SQLException e) {
log.error("Failed to re-add identifiers: {}", e.getMessage());
} catch (AuthorizeException e) {
log.error("Failed to re-add identifiers: {}", e.getMessage());
}
if (skipped > 0)
throw new IdentifierException(identifier + " could not be deleted.");
} }
private String getUser() /**
* Format a naked identifier as a DOI with our configured authority prefix.
*
* @throws IdentifierException if authority prefix is not configured.
*/
String idToDOI(String id)
throws IdentifierException
{
return "doi:" + loadAuthority() + id;
}
/**
* Remove scheme and our configured authority prefix from a doi: URI string.
* @return naked local identifier.
* @throws IdentifierException if authority prefix is not configured.
*/
String DOIToId(String DOI)
throws IdentifierException
{
String prefix = "doi:" + loadAuthority();
if (DOI.startsWith(prefix))
return DOI.substring(prefix.length());
else
return DOI;
}
/**
* Get configured value of EZID username.
* @throws IdentifierException
*/
private String loadUser()
throws IdentifierException throws IdentifierException
{ {
String user = configurationService.getProperty(CFG_USER); String user = configurationService.getProperty(CFG_USER);
@@ -385,7 +513,11 @@ public class DataCiteIdentifierProvider
throw new IdentifierException("Unconfigured: define " + CFG_USER); throw new IdentifierException("Unconfigured: define " + CFG_USER);
} }
private String getPassword() /**
* Get configured value of EZID password.
* @throws IdentifierException
*/
private String loadPassword()
throws IdentifierException throws IdentifierException
{ {
String password = configurationService.getProperty(CFG_PASSWORD); String password = configurationService.getProperty(CFG_PASSWORD);
@@ -395,7 +527,11 @@ public class DataCiteIdentifierProvider
throw new IdentifierException("Unconfigured: define " + CFG_PASSWORD); throw new IdentifierException("Unconfigured: define " + CFG_PASSWORD);
} }
private String getShoulder() /**
* Get configured value of EZID "shoulder".
* @throws IdentifierException
*/
private String loadAuthority()
throws IdentifierException throws IdentifierException
{ {
String shoulder = configurationService.getProperty(CFG_SHOULDER); String shoulder = configurationService.getProperty(CFG_SHOULDER);
@@ -423,21 +559,18 @@ public class DataCiteIdentifierProvider
for (DCValue value : values) for (DCValue value : values)
mapped.put(datum.getKey(), value.value); mapped.put(datum.getKey(), value.value);
} }
// TODO find a way to get a current direct URL to the object and set _target
// mapped.put("_target", url);
return mapped; return mapped;
} }
/**
* @param aCrosswalk the crosswalk to set
*/
@Required @Required
public void setCrosswalk(Map<String, String> aCrosswalk) public void setCrosswalk(Map<String, String> aCrosswalk)
{ {
crosswalk = aCrosswalk; crosswalk = aCrosswalk;
} }
/**
* @param aRequestFactory the requestFactory to set
*/
@Required @Required
public static void setRequestFactory(EZIDRequestFactory aRequestFactory) public static void setRequestFactory(EZIDRequestFactory aRequestFactory)
{ {

View File

@@ -11,7 +11,6 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -25,6 +24,7 @@ import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.AbstractHttpClient; import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.dspace.identifier.DOI;
import org.dspace.identifier.IdentifierException; import org.dspace.identifier.IdentifierException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -38,42 +38,63 @@ public class EZIDRequest
{ {
private static final Logger log = LoggerFactory.getLogger(EZIDRequest.class); private static final Logger log = LoggerFactory.getLogger(EZIDRequest.class);
private URI url; private static final String ID_PATH = "/ezid/id/" + DOI.SCHEME;
private AbstractHttpClient client; private static final String SHOULDER_PATH = "/ezid/shoulder/" + DOI.SCHEME;
private static final String UTF_8 = "UTF-8";
private static final String MD_KEY_STATUS = "_status";
private final AbstractHttpClient client;
private final String scheme;
private final String host;
private final String authority;
/** /**
* Prepare a context for requests concerning a specific identifier or * Prepare a context for requests concerning a specific identifier or
* authority prefix. * authority prefix.
* *
* @param url EZID API service point (and object) for this request. * @param scheme
* @param host
* @param authority DOI authority prefix.
* @param username an EZID user identity. * @param username an EZID user identity.
* @param password user's password, or null for none. * @param password user's password, or {@code null} for none.
* @throws URISyntaxException if host or authority is bad.
*/ */
EZIDRequest(URI url, String username, String password) EZIDRequest(String scheme, String host, String authority, String username, String password)
throws URISyntaxException throws URISyntaxException
{ {
this.url = url; this.scheme = scheme;
this.host = host;
this.authority = authority;
client = new DefaultHttpClient(); client = new DefaultHttpClient();
if (null != username) if (null != username)
{
URI uri = new URI(scheme, host, null, null);
client.getCredentialsProvider().setCredentials( client.getCredentialsProvider().setCredentials(
new AuthScope(url.getHost(), url.getPort()), new AuthScope(uri.getHost(), uri.getPort()),
new UsernamePasswordCredentials(username, password)); new UsernamePasswordCredentials(username, password));
}
} }
/** /**
* Fetch an identifier's metadata. * Fetch the metadata bound to an identifier.
* *
* @return
* @throws IdentifierException if the response is error or body malformed. * @throws IdentifierException if the response is error or body malformed.
* @throws IOException if the HTTP request fails. * @throws IOException if the HTTP request fails.
* @throws URISyntaxException
*/ */
public EZIDResponse lookup() public EZIDResponse lookup(String name)
throws IdentifierException, IOException throws IdentifierException, IOException, URISyntaxException
{ {
// GET path // GET path
HttpGet request; HttpGet request;
request = new HttpGet(url); URI uri = new URI(scheme, host, ID_PATH + authority + name, null);
request = new HttpGet(uri);
HttpResponse response = client.execute(request); HttpResponse response = client.execute(request);
return new EZIDResponse(response); return new EZIDResponse(response);
} }
@@ -86,16 +107,17 @@ public class EZIDRequest
* @param metadata ANVL-encoded key/value pairs. * @param metadata ANVL-encoded key/value pairs.
* @return * @return
*/ */
public EZIDResponse create(Map<String, String> metadata) public EZIDResponse create(String name, Map<String, String> metadata)
throws IOException, IdentifierException throws IOException, IdentifierException, URISyntaxException
{ {
// PUT path [+metadata] // PUT path [+metadata]
HttpPut request; HttpPut request;
request = new HttpPut(url); URI uri = new URI(scheme, host, ID_PATH + authority + name, null);
request = new HttpPut(uri);
if (null != metadata) if (null != metadata)
{ {
try { try {
request.setEntity(new StringEntity(formatMetadata(metadata), "UTF-8")); request.setEntity(new StringEntity(formatMetadata(metadata), UTF_8));
} catch (UnsupportedEncodingException ex) { /* SNH */ } } catch (UnsupportedEncodingException ex) { /* SNH */ }
} }
HttpResponse response = client.execute(request); HttpResponse response = client.execute(request);
@@ -110,30 +132,30 @@ public class EZIDRequest
* @return * @return
*/ */
public EZIDResponse mint(Map<String, String> metadata) public EZIDResponse mint(Map<String, String> metadata)
throws IOException, IdentifierException throws IOException, IdentifierException, URISyntaxException
{ {
// POST path [+metadata] // POST path [+metadata]
HttpPost request; HttpPost request;
request = new HttpPost(url); URI uri = new URI(scheme, host, SHOULDER_PATH + authority, null);
request = new HttpPost(uri);
if (null != metadata) if (null != metadata)
{ {
request.setEntity(new StringEntity(formatMetadata(metadata), "UTF-8")); request.setEntity(new StringEntity(formatMetadata(metadata), UTF_8));
} }
HttpResponse response = client.execute(request); HttpResponse response = client.execute(request);
EZIDResponse myResponse = new EZIDResponse(response); EZIDResponse myResponse = new EZIDResponse(response);
// TODO add the identifier to the path for subsequent operations?
return myResponse; return myResponse;
} }
/** /**
* Alter the identifier's metadata. * Alter the metadata bound to an identifier.
* *
* @param metadata fields to be altered. Leave a field's value empty to * @param metadata fields to be altered. Leave the value of a field's empty
* delete the field. * to delete the field.
* @return * @return
*/ */
public EZIDResponse modify(Map<String, String> metadata) public EZIDResponse modify(String name, Map<String, String> metadata)
throws IOException, IdentifierException throws IOException, IdentifierException, URISyntaxException
{ {
if (null == metadata) if (null == metadata)
{ {
@@ -141,8 +163,9 @@ public class EZIDRequest
} }
// POST path +metadata // POST path +metadata
HttpPost request; HttpPost request;
request = new HttpPost(url); URI uri = new URI(scheme, host, ID_PATH + authority + name, null);
request.setEntity(new StringEntity(formatMetadata(metadata), "UTF-8")); request = new HttpPost(uri);
request.setEntity(new StringEntity(formatMetadata(metadata), UTF_8));
HttpResponse response = client.execute(request); HttpResponse response = client.execute(request);
return new EZIDResponse(response); return new EZIDResponse(response);
} }
@@ -150,12 +173,13 @@ public class EZIDRequest
/** /**
* Destroy a reserved identifier. Fails if ID was ever public. * Destroy a reserved identifier. Fails if ID was ever public.
*/ */
public EZIDResponse delete() public EZIDResponse delete(String name)
throws IOException, IdentifierException throws IOException, IdentifierException, URISyntaxException
{ {
// DELETE path // DELETE path
HttpDelete request; HttpDelete request;
request = new HttpDelete(url); URI uri = new URI(scheme, host, ID_PATH + authority + name, null);
request = new HttpDelete(uri);
HttpResponse response = client.execute(request); HttpResponse response = client.execute(request);
return new EZIDResponse(response); return new EZIDResponse(response);
} }
@@ -163,12 +187,12 @@ public class EZIDRequest
/** /**
* Remove a public identifier from view. * Remove a public identifier from view.
*/ */
public EZIDResponse withdraw() public EZIDResponse withdraw(String name)
throws IOException, IdentifierException throws IOException, IdentifierException, URISyntaxException
{ {
Map<String, String> metadata = new HashMap<String, String>(); Map<String, String> metadata = new HashMap<String, String>();
metadata.put("_status", "unavailable"); metadata.put(MD_KEY_STATUS, "unavailable");
return modify(metadata); return modify(name, metadata);
} }
/** /**
@@ -176,38 +200,39 @@ public class EZIDRequest
* *
* @param reason annotation for the item's unavailability. * @param reason annotation for the item's unavailability.
*/ */
public EZIDResponse withdraw(String reason) public EZIDResponse withdraw(String name, String reason)
throws IOException, IdentifierException throws IOException, IdentifierException, URISyntaxException
{ {
String reasonEncoded = null;
try {
reasonEncoded = URLEncoder.encode(reason, "UTF-8");
} catch (UnsupportedEncodingException e) { /* XXX SNH */ }
Map<String, String> metadata = new HashMap<String, String>(); Map<String, String> metadata = new HashMap<String, String>();
metadata.put("_status", "unavailable | " + reasonEncoded); metadata.put(MD_KEY_STATUS, "unavailable | " + escape(reason));
return modify(metadata); return modify(name, metadata);
} }
/** /**
* Create ANVL-formatted name/value pairs from a Map. * Create ANVL-formatted name/value pairs from a Map.
*/ */
private String formatMetadata(Map<String, String> raw) private static String formatMetadata(Map<String, String> raw)
{ {
StringBuilder formatted = new StringBuilder(); StringBuilder formatted = new StringBuilder();
for (Entry<String, String> entry : raw.entrySet()) for (Entry<String, String> entry : raw.entrySet())
formatted.append(entry.getKey()) {
formatted.append(escape(entry.getKey()))
.append(": ") .append(": ")
.append(entry.getValue()) .append(escape(entry.getValue()))
.append('\n'); .append('\n');
// Body should be percent-encoded
String body = null;
try {
body = URLEncoder.encode(formatted.toString(), "UTF-8");
} catch (UnsupportedEncodingException ex) { // XXX SNH
log.error(ex.getMessage());
} finally {
return body;
} }
return formatted.toString();
}
/**
* Percent-encode a few EZID-specific characters.
*/
private static String escape(String s)
{
return s.replace("%", "%25")
.replace("\n", "%0A")
.replace("\r", "%0D")
.replace(":", "%3A");
} }
} }

View File

@@ -9,7 +9,6 @@
package org.dspace.identifier.ezid; package org.dspace.identifier.ezid;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import org.apache.http.client.utils.URIBuilder;
import org.springframework.beans.factory.annotation.Required; import org.springframework.beans.factory.annotation.Required;
/** /**
@@ -32,42 +31,19 @@ public class EZIDRequestFactory
{ {
private static String EZID_SCHEME; private static String EZID_SCHEME;
private static String EZID_HOST; private static String EZID_HOST;
private static String EZID_PATH;
/** /**
* Configure an EZID request. * Configure an EZID request.
* *
* @param requestPath specific request (DOI, shoulder). * @param authority our DOI authority number.
* @param username * @param username EZID user name.
* @param password * @param password {@code username}'s password.
* @throws URISyntaxException * @throws URISyntaxException
*/ */
public EZIDRequest getInstance(String requestPath, String username, String password) public EZIDRequest getInstance(String authority, String username, String password)
throws URISyntaxException throws URISyntaxException
{ {
URIBuilder uri = new URIBuilder(); return new EZIDRequest(EZID_SCHEME, EZID_HOST, authority, username, password);
uri.setScheme(EZID_SCHEME);
uri.setHost(EZID_HOST);
String head, tail;
if (EZID_PATH.endsWith("/"))
head = EZID_PATH.substring(0, EZID_PATH.length() - 1);
else
head = EZID_PATH;
if (requestPath.startsWith("/"))
tail = requestPath.substring( 0, requestPath.length() - 1);
else
tail = requestPath;
StringBuilder path = new StringBuilder();
path.append(head);
path.append('/');
path.append(tail);
uri.setPath(path.toString());
return new EZIDRequest(uri.build(), username, password);
} }
/** /**
@@ -87,13 +63,4 @@ public class EZIDRequestFactory
{ {
EZID_HOST = aEZID_HOST; EZID_HOST = aEZID_HOST;
} }
/**
* @param aEZID_PATH the EZID path to set
*/
@Required
public static void setEZID_PATH(String aEZID_PATH)
{
EZID_PATH = aEZID_PATH;
}
} }

View File

@@ -29,6 +29,8 @@ public class EZIDResponse
{ {
private static final Logger log = LoggerFactory.getLogger(EZIDResponse.class); private static final Logger log = LoggerFactory.getLogger(EZIDResponse.class);
private static final String UTF_8 = "UTF-8";
private final String status; private final String status;
private final String statusValue; private final String statusValue;
@@ -48,7 +50,7 @@ public class EZIDResponse
String body; String body;
try try
{ {
body = EntityUtils.toString(responseBody, "UTF-8"); body = EntityUtils.toString(responseBody, UTF_8);
} catch (IOException ex) } catch (IOException ex)
{ {
log.error(ex.getMessage()); log.error(ex.getMessage());
@@ -83,10 +85,10 @@ public class EZIDResponse
parts = lines[i].split(":", 2); parts = lines[i].split(":", 2);
String key = null, value = null; String key = null, value = null;
try { try {
key = URLDecoder.decode(parts[0], "UTF-8").trim(); key = URLDecoder.decode(parts[0], UTF_8).trim();
if (parts.length > 1) if (parts.length > 1)
{ {
value = URLDecoder.decode(parts[1], "UTF-8").trim(); value = URLDecoder.decode(parts[1], UTF_8).trim();
} }
else else
{ {
@@ -120,7 +122,7 @@ public class EZIDResponse
} }
/** /**
* Value associated with the EZID status (identifier, error text, etc.) * Value associated with the EZID status (identifier, error text, etc.).
*/ */
public String getEZIDStatusValue() public String getEZIDStatusValue()
{ {

View File

@@ -22,12 +22,11 @@
<bean class='org.dspace.identifier.ezid.EZIDRequestFactory'> <bean class='org.dspace.identifier.ezid.EZIDRequestFactory'>
<property name='EZID_SCHEME' value='https'/> <property name='EZID_SCHEME' value='https'/>
<property name='EZID_HOST' value='n2t.net'/> <property name='EZID_HOST' value='n2t.net'/>
<property name='EZID_PATH' value='/ezid/shoulder/'/>
</bean> </bean>
</property> </property>
<property name='crosswalk'> <property name='crosswalk'>
<map> <map>
<entry key='datacite.creator' value='dc.creator.author'/> <entry key='datacite.creator' value='dc.contributor.author'/>
<entry key='datacite.title' value='dc.title'/> <entry key='datacite.title' value='dc.title'/>
<entry key='datacite.publisher' value='dc.publisher'/> <entry key='datacite.publisher' value='dc.publisher'/>
<entry key='datacite.publicationyear' value='dc.date.published'/> <entry key='datacite.publicationyear' value='dc.date.published'/>

View File

@@ -8,16 +8,17 @@
package org.dspace.identifier; package org.dspace.identifier;
import java.util.List; import java.io.IOException;
import java.sql.SQLException;
import java.util.UUID;
import org.dspace.AbstractUnitTest; import org.dspace.AbstractUnitTest;
import org.dspace.content.Collection; import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Community; import org.dspace.content.*;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.WorkspaceItem;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.kernel.ServiceManager; import org.dspace.kernel.ServiceManager;
import org.dspace.services.ConfigurationService; import org.dspace.services.ConfigurationService;
import org.dspace.workflow.WorkflowItem;
import org.dspace.workflow.WorkflowManager;
import org.junit.*; import org.junit.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@@ -28,29 +29,85 @@ import static org.junit.Assert.*;
public class DataCiteIdentifierProviderTest public class DataCiteIdentifierProviderTest
extends AbstractUnitTest extends AbstractUnitTest
{ {
private static final String TEST_SHOULDER = "doi:10.5072/FK2"; /** Name of the reserved EZID test authority */
private static final String TEST_SHOULDER = "10.5072/FK2";
private static ServiceManager sm = null; private static ServiceManager sm = null;
private static ConfigurationService config = null; private static ConfigurationService config = null;
private static Item item = null; private static Community community;
private static Collection collection;
/** The most recently created test Item's ID */
private static int itemID;
public DataCiteIdentifierProviderTest() public DataCiteIdentifierProviderTest()
{ {
} }
private static void dumpMetadata(Item eyetem)
{
DCValue[] metadata = eyetem.getMetadata("dc", Item.ANY, Item.ANY, Item.ANY);
for (DCValue metadatum : metadata)
System.out.printf("Metadata: %s.%s.%s(%s) = %s\n",
metadatum.schema,
metadatum.element,
metadatum.qualifier,
metadatum.language,
metadatum.value);
}
/**
* Create a fresh Item, installed in the repository.
*
* @throws SQLException
* @throws AuthorizeException
* @throws IOException
*/
private Item newItem(Context ctx)
throws SQLException, AuthorizeException, IOException
{
ctx.turnOffAuthorisationSystem();
ctx.setCurrentUser(eperson);
WorkspaceItem wsItem = WorkspaceItem.create(ctx, collection, false);
WorkflowItem wfItem = WorkflowManager.start(ctx, wsItem);
WorkflowManager.advance(ctx, wfItem, ctx.getCurrentUser());
Item item = wfItem.getItem();
item.addMetadata("dc", "contributor", "author", null, "Author, A. N.");
item.addMetadata("dc", "title", null, null, "A Test Object");
item.addMetadata("dc", "publisher", null, null, "DSpace Test Harness");
item.update();
itemID = item.getID();
ctx.commit();
ctx.restoreAuthSystemState();
return item;
}
@BeforeClass @BeforeClass
public static void setUpClass() public static void setUpClass()
throws Exception throws Exception
{ {
// Create an object to work with
Context ctx = new Context(); Context ctx = new Context();
ctx.turnOffAuthorisationSystem(); ctx.turnOffAuthorisationSystem();
Community community = Community.create(null, ctx);
Collection collection = community.createCollection(); ctx.setCurrentUser(eperson);
WorkspaceItem wsItem = WorkspaceItem.create(ctx, collection, false);
item = wsItem.getItem(); // Create an environment for our test objects to live in.
community = Community.create(null, ctx);
community.setMetadata("name", "A Test Community");
community.update();
collection = community.createCollection();
collection.setMetadata("name", "A Test Collection");
collection.update();
ctx.complete(); ctx.complete();
// Find the usual kernel services // Find the usual kernel services
@@ -58,26 +115,35 @@ public class DataCiteIdentifierProviderTest
config = kernelImpl.getConfigurationService(); config = kernelImpl.getConfigurationService();
// Configure the service under test // Configure the service under test.
config.setProperty("identifier.doi.ezid.shoulder", TEST_SHOULDER); config.setProperty(DataCiteIdentifierProvider.CFG_SHOULDER, TEST_SHOULDER);
config.setProperty("identifier.doi.ezid.user", "apitest"); config.setProperty(DataCiteIdentifierProvider.CFG_USER, "apitest");
config.setProperty("identifier.doi.ezid.password", "apitest"); config.setProperty(DataCiteIdentifierProvider.CFG_PASSWORD, "apitest");
// Don't try to send mail.
config.setProperty("mail.server.disabled", "true");
} }
@AfterClass @AfterClass
public static void tearDownClass() public static void tearDownClass()
throws Exception throws Exception
{ {
System.out.print("Tearing down\n\n");
Context ctx = new Context();
dumpMetadata(Item.find(ctx, itemID));
} }
@Before @Before
public void setUp() public void setUp()
{ {
context.setCurrentUser(eperson);
context.turnOffAuthorisationSystem();
} }
@After @After
public void tearDown() public void tearDown()
{ {
context.restoreAuthSystemState();
} }
/** /**
@@ -103,7 +169,7 @@ public class DataCiteIdentifierProviderTest
System.out.println("supports"); System.out.println("supports");
DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider(); DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider();
String identifier = TEST_SHOULDER; String identifier = "doi:" + TEST_SHOULDER;
boolean result = instance.supports(identifier); boolean result = instance.supports(identifier);
assertTrue(identifier + " should be supported", result); assertTrue(identifier + " should be supported", result);
} }
@@ -115,16 +181,16 @@ public class DataCiteIdentifierProviderTest
public void testRegister_Context_DSpaceObject() public void testRegister_Context_DSpaceObject()
throws Exception throws Exception
{ {
System.out.println("register"); System.out.println("register 2");
List<DataCiteIdentifierProvider> instance List<DataCiteIdentifierProvider> instance
= (List<DataCiteIdentifierProvider>) = (List<DataCiteIdentifierProvider>)
sm.getServicesByType(DataCiteIdentifierProvider.class); sm.getServicesByType(DataCiteIdentifierProvider.class);
DSpaceObject dso = item; DSpaceObject dso = newItem(context);
String result = instance.get(0).register(context, dso); String result = instance.register(context, dso);
assertTrue("Didn't get a DOI back", result.startsWith("doi:10.5072/")); assertTrue("Didn't get a DOI back", result.startsWith("doi:" + TEST_SHOULDER));
System.out.println(" got identifier: " + result); System.out.println(" got identifier: " + result);
} }
@@ -133,18 +199,17 @@ public class DataCiteIdentifierProviderTest
*/ */
@Test @Test
public void testRegister_3args() public void testRegister_3args()
throws SQLException, AuthorizeException, IOException
{ {
System.out.println("register"); System.out.println("register 3");
// TODO review the generated test code and remove the default call to fail.
fail("The test case is a prototype.");
List<DataCiteIdentifierProvider> instances List<DataCiteIdentifierProvider> instances
= (List<DataCiteIdentifierProvider>) = (List<DataCiteIdentifierProvider>)
sm.getServicesByType(DataCiteIdentifierProvider.class); sm.getServicesByType(DataCiteIdentifierProvider.class);
DSpaceObject object = item; DSpaceObject object = newItem(context);
String identifier = TEST_SHOULDER + "blarg"; // TODO a unique value String identifier = UUID.randomUUID().toString();
instances.get(0).register(context, object, identifier); instances.get(0).register(context, object, identifier);
} }
@@ -157,13 +222,11 @@ public class DataCiteIdentifierProviderTest
throws Exception throws Exception
{ {
System.out.println("reserve"); System.out.println("reserve");
// TODO review the generated test code and remove the default call to fail.
fail("The test case is a prototype.");
DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider(); DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider();
DSpaceObject dso = item; DSpaceObject dso = newItem(context);
String identifier = ""; String identifier = UUID.randomUUID().toString();
instance.reserve(context, dso, identifier); instance.reserve(context, dso, identifier);
} }
@@ -180,8 +243,7 @@ public class DataCiteIdentifierProviderTest
DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider(); DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider();
DSpaceObject dso = item; DSpaceObject dso = newItem(context);
String expResult = "";
String result = instance.mint(context, dso); String result = instance.mint(context, dso);
assertEquals(expResult, result); assertEquals(expResult, result);
} }
@@ -194,18 +256,18 @@ public class DataCiteIdentifierProviderTest
throws Exception throws Exception
{ {
System.out.println("resolve"); System.out.println("resolve");
// TODO review the generated test code and remove the default call to fail.
fail("The test case is a prototype.");
DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider(); DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider();
String identifier = ""; String identifier = UUID.randomUUID().toString();
DSpaceObject expResult = newItem(context);
instance.register(context, expResult, identifier);
String[] attributes = null; String[] attributes = null;
DSpaceObject expResult = null;
DSpaceObject result = instance.resolve(context, identifier, attributes); DSpaceObject result = instance.resolve(context, identifier, attributes);
assertEquals(expResult, result); assertEquals(expResult, result);
} }
/** /**
* Test of lookup method, of class DataCiteIdentifierProvider. * Test of lookup method, of class DataCiteIdentifierProvider.
*/ */
@@ -219,8 +281,10 @@ public class DataCiteIdentifierProviderTest
DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider(); DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider();
DSpaceObject object = item; String identifier = UUID.randomUUID().toString();
String expResult = ""; DSpaceObject object = newItem(context);
instance.register(context, object, identifier);
String result = instance.lookup(context, object); String result = instance.lookup(context, object);
assertEquals(expResult, result); assertEquals(expResult, result);
} }
@@ -232,31 +296,66 @@ public class DataCiteIdentifierProviderTest
public void testDelete_Context_DSpaceObject() public void testDelete_Context_DSpaceObject()
throws Exception throws Exception
{ {
System.out.println("delete"); System.out.println("delete 2");
// TODO review the generated test code and remove the default call to fail.
fail("The test case is a prototype.");
DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider(); DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider();
DSpaceObject dso = item; DSpaceObject dso = newItem(context);
instance.delete(context, dso);
// Ensure that it has multiple DOIs (ooo, bad boy!)
String id1 = UUID.randomUUID().toString();
String id2 = UUID.randomUUID().toString();
instance.reserve(context, dso, id1);
instance.reserve(context, dso, id2);
// Test deletion
try {
instance.delete(context, dso);
} catch (IdentifierException e) {
// Creation of the Item registers a "public" identifier, which can't be deleted.
assertEquals("Unexpected exception", "1 identifiers could not be deleted.", e.getMessage());
}
// See if those identifiers were really deleted.
ItemIterator found;
found = Item.findByMetadataField(context,
DataCiteIdentifierProvider.MD_SCHEMA,
DataCiteIdentifierProvider.DOI_ELEMENT,
DataCiteIdentifierProvider.DOI_QUALIFIER, id1);
assertFalse("A test identifier is still present", found.hasNext());
found = Item.findByMetadataField(context,
DataCiteIdentifierProvider.MD_SCHEMA,
DataCiteIdentifierProvider.DOI_ELEMENT,
DataCiteIdentifierProvider.DOI_QUALIFIER, id2);
assertFalse("A test identifier is still present", found.hasNext());
} }
/** /**
* Test of delete method, of class DataCiteIdentifierProvider. * Test of delete method, of class DataCiteIdentifierProvider.
*/ */
@Test @Test()
public void testDelete_3args() public void testDelete_3args()
throws Exception throws Exception
{ {
System.out.println("delete"); System.out.println("delete 3");
// TODO review the generated test code and remove the default call to fail.
fail("The test case is a prototype.");
DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider(); DataCiteIdentifierProvider instance = new DataCiteIdentifierProvider();
DSpaceObject dso = item; DSpaceObject dso = newItem(context);
String identifier = ""; String identifier = UUID.randomUUID().toString();
// Set a known identifier on the object
instance.reserve(context, dso, identifier);
// Test deletion
instance.delete(context, dso, identifier); instance.delete(context, dso, identifier);
// See if it is gone
ItemIterator found = Item.findByMetadataField(context,
DataCiteIdentifierProvider.MD_SCHEMA,
DataCiteIdentifierProvider.DOI_ELEMENT,
DataCiteIdentifierProvider.DOI_QUALIFIER, identifier);
assertFalse("Test identifier is still present", found.hasNext());
} }
} }