/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at * * http://www.dspace.org/license/ */ package org.dspace.identifier.ezid; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.AbstractHttpClient; import org.apache.http.impl.client.DefaultHttpClient; import org.dspace.identifier.DOI; import org.dspace.identifier.IdentifierException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A request to EZID concerning a given (or expected) identifier. * * @author Mark H. Wood */ public class EZIDRequest { private static final Logger log = LoggerFactory.getLogger(EZIDRequest.class); private static final String ID_PATH = "/id/" + DOI.SCHEME; private static final String SHOULDER_PATH = "/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 path; private final String authority; /** * Prepare a context for requests concerning a specific identifier or * authority prefix. * * @param scheme URL scheme for access to the EZID service. * @param host Host name for access to the EZID service. * @param authority DOI authority prefix, e.g. "10.5072/FK2". * @param username an EZID user identity. * @param password user's password, or {@code null} for none. * @throws URISyntaxException if host or authority is bad. * @deprecated since 4.1 */ @Deprecated EZIDRequest(String scheme, String host, String authority, String username, String password) throws URISyntaxException { this.scheme = scheme; this.host = host; this.path = "ezid"; if (authority.charAt(authority.length()-1) == '/') { this.authority = authority.substring(0, authority.length()-1); } else { this.authority = authority; } client = new DefaultHttpClient(); if (null != username) { URI uri = new URI(scheme, host, path, null); client.getCredentialsProvider().setCredentials( new AuthScope(uri.getHost(), uri.getPort()), new UsernamePasswordCredentials(username, password)); } } /** * Prepare a context for requests concerning a specific identifier or * authority prefix. * * @param scheme URL scheme for access to the EZID service. * @param host Host name for access to the EZID service. * @param path Local-path to the EZID service. * @param authority DOI authority prefix, e.g. "10.5072/FK2". * @param username an EZID user identity. * @param password user's password, or {@code null} for none. * @throws URISyntaxException if host or authority is bad. */ EZIDRequest(String scheme, String host, String path, String authority, String username, String password) throws URISyntaxException { this.scheme = scheme; this.host = host; this.path = path; if (authority.charAt(authority.length()-1) == '/') { this.authority = authority.substring(0, authority.length()-1); } else { this.authority = authority; } client = new DefaultHttpClient(); if (null != username) { URI uri = new URI(scheme, host, path, null); client.getCredentialsProvider().setCredentials( new AuthScope(uri.getHost(), uri.getPort()), new UsernamePasswordCredentials(username, password)); } } /** * Fetch the metadata bound to an identifier. * * @param name * identifier name * @return Decoded response data evoked by a request made to EZID. * @throws IdentifierException if the response is error or body malformed. * @throws IOException if the HTTP request fails. * @throws URISyntaxException if host or authority is bad. */ public EZIDResponse lookup(String name) throws IdentifierException, IOException, URISyntaxException { // GET path HttpGet request; URI uri = new URI(scheme, host, path + ID_PATH + authority + name, null); log.debug("EZID lookup {}", uri.toASCIIString()); request = new HttpGet(uri); HttpResponse response = client.execute(request); return new EZIDResponse(response); } /** * Create an identifier with a given name. The name is the end of the * request path. Note: to "reserve" a given identifier, include "_status = * reserved" in {@code metadata}. * * @param name * identifier name * @param metadata ANVL-encoded key/value pairs. * @return Decoded response data evoked by a request made to EZID. * @throws IdentifierException if the response is error or body malformed. * @throws IOException if the HTTP request fails. * @throws URISyntaxException if host or authority is bad. */ public EZIDResponse create(String name, Map<String, String> metadata) throws IOException, IdentifierException, URISyntaxException { // PUT path [+metadata] HttpPut request; URI uri = new URI(scheme, host, path + ID_PATH + authority + '/' + name, null); log.debug("EZID create {}", uri.toASCIIString()); request = new HttpPut(uri); if (null != metadata) { request.setEntity(new StringEntity(formatMetadata(metadata), UTF_8)); } HttpResponse response = client.execute(request); return new EZIDResponse(response); } /** * Ask EZID to create a unique identifier and return its name. NOTE: to * "reserve" a unique identifier, include "_status = reserved" in {@code metadata}. * * @param metadata ANVL-encoded key/value pairs. * @return Decoded response data evoked by a request made to EZID. * @throws IdentifierException if the response is error or body malformed. * @throws IOException if the HTTP request fails. * @throws URISyntaxException if host or authority is bad. */ public EZIDResponse mint(Map<String, String> metadata) throws IOException, IdentifierException, URISyntaxException { // POST path [+metadata] HttpPost request; URI uri = new URI(scheme, host, path + SHOULDER_PATH + authority, null); log.debug("EZID mint {}", uri.toASCIIString()); request = new HttpPost(uri); if (null != metadata) { request.setEntity(new StringEntity(formatMetadata(metadata), UTF_8)); } HttpResponse response = client.execute(request); EZIDResponse myResponse = new EZIDResponse(response); return myResponse; } /** * Alter the metadata bound to an identifier. * * @param name * identifier name * @param metadata * metadata fields to be altered. Leave the value of a field's empty * to delete the field. * @return Decoded response data evoked by a request made to EZID. * @throws IdentifierException if the response is error or body malformed. * @throws IOException if the HTTP request fails. * @throws URISyntaxException if host or authority is bad. */ public EZIDResponse modify(String name, Map<String, String> metadata) throws IOException, IdentifierException, URISyntaxException { if (null == metadata) { throw new IllegalArgumentException("metadata must not be null"); } // POST path +metadata HttpPost request; URI uri = new URI(scheme, host, path + ID_PATH + authority + name, null); log.debug("EZID modify {}", uri.toASCIIString()); request = new HttpPost(uri); request.setEntity(new StringEntity(formatMetadata(metadata), UTF_8)); HttpResponse response = client.execute(request); return new EZIDResponse(response); } /** * Destroy a reserved identifier. Fails if ID was ever public. * * @param name * identifier name * @return Decoded response data evoked by a request made to EZID. * @throws IdentifierException if the response is error or body malformed. * @throws IOException if the HTTP request fails. * @throws URISyntaxException if host or authority is bad. */ public EZIDResponse delete(String name) throws IOException, IdentifierException, URISyntaxException { // DELETE path HttpDelete request; URI uri = new URI(scheme, host, path + ID_PATH + authority + name, null); log.debug("EZID delete {}", uri.toASCIIString()); request = new HttpDelete(uri); HttpResponse response = client.execute(request); return new EZIDResponse(response); } /** * Remove a public identifier from view. * * @param name * identifier name * @return Decoded response data evoked by a request made to EZID. * @throws IdentifierException if the response is error or body malformed. * @throws IOException if the HTTP request fails. * @throws URISyntaxException if host or authority is bad. */ public EZIDResponse withdraw(String name) throws IOException, IdentifierException, URISyntaxException { Map<String, String> metadata = new HashMap<String, String>(); metadata.put(MD_KEY_STATUS, "unavailable"); return modify(name, metadata); } /** * Remove a public identifier from view, with a reason. * * @param name * identifier name * @param reason * annotation for the item's unavailability. * @return Decoded response data evoked by a request made to EZID. * @throws IdentifierException if the response is error or body malformed. * @throws IOException if the HTTP request fails. * @throws URISyntaxException if host or authority is bad. */ public EZIDResponse withdraw(String name, String reason) throws IOException, IdentifierException, URISyntaxException { Map<String, String> metadata = new HashMap<String, String>(); metadata.put(MD_KEY_STATUS, "unavailable | " + escape(reason)); return modify(name, metadata); } /** * Create ANVL-formatted name/value pairs from a Map. * * @param raw * */ private static String formatMetadata(Map<String, String> raw) { StringBuilder formatted = new StringBuilder(); for (Entry<String, String> entry : raw.entrySet()) { formatted.append(escape(entry.getKey())) .append(": ") .append(escape(entry.getValue())) .append('\n'); } return formatted.toString(); } /** * Percent-encode a few EZID-specific characters. * * @return null for null input. */ private static String escape(String s) { if (null == s) { return s; } return s.replace("%", "%25") .replace("\n", "%0A") .replace("\r", "%0D") .replace(":", "%3A"); } }