org.dspace.identifier.doi.DataCiteConnector.java Source code

Java tutorial

Introduction

Here is the source code for org.dspace.identifier.doi.DataCiteConnector.java

Source

/**
 * 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.doi;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.util.Iterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
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.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.crosswalk.CrosswalkException;
import org.dspace.content.crosswalk.DisseminationCrosswalk;
import org.dspace.core.Context;
import org.dspace.core.PluginManager;
import org.dspace.handle.HandleManager;
import org.dspace.identifier.DOI;
import org.dspace.services.ConfigurationService;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.filter.ElementFilter;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;

/**
 *
 * @author Pascal-Nicolas Becker
 */
public class DataCiteConnector implements DOIConnector {

    private static final Logger log = LoggerFactory.getLogger(DataCiteConnector.class);

    // Configuration property names
    static final String CFG_USER = "identifier.doi.user";
    static final String CFG_PASSWORD = "identifier.doi.password";

    /**
     * Stores the scheme used to connect to the DataCite server. It will be set
     * by spring dependency injection.
     */
    private String SCHEME;
    /**
     * Stores the hostname of the DataCite server. Set by spring dependency
     * injection.
     */
    private String HOST;

    /**
     * Path on the DataCite server used to generate DOIs. Set by spring
     * dependency injection.
     */
    protected String DOI_PATH;
    /**
     * Path on the DataCite server used to register metadata. Set by spring
     * dependency injection.
     */
    protected String METADATA_PATH;
    /**
     * Name of crosswalk to convert metadata into DataCite Metadata Scheme. Set 
     * by spring dependency injection.
     */
    protected String CROSSWALK_NAME;
    /** 
     * DisseminationCrosswalk to map local metadata into DataCite metadata.
     * The name of the crosswalk is set by spring dependency injection using
     * {@link setDisseminationCrosswalk(String) setDisseminationCrosswalk} which
     * instantiates the crosswalk.
     */
    protected DisseminationCrosswalk xwalk;

    protected ConfigurationService configurationService;

    protected String USERNAME;
    protected String PASSWORD;

    public DataCiteConnector() {
        this.xwalk = null;
        this.USERNAME = null;
        this.PASSWORD = null;
    }

    /**
     * Used to set the scheme to connect the DataCite server. Used by spring
     * dependency injection.
     * @param DATACITE_SCHEME Probably https or http.
     */
    @Required
    public void setDATACITE_SCHEME(String DATACITE_SCHEME) {
        this.SCHEME = DATACITE_SCHEME;
    }

    /**
     * Set the hostname of the DataCite server. Used by spring dependency
     * injection.
     * @param DATACITE_HOST Hostname to connect to register DOIs (f.e. test.datacite.org).
     */
    @Required
    public void setDATACITE_HOST(String DATACITE_HOST) {
        this.HOST = DATACITE_HOST;
    }

    /**
     * Set the path on the DataCite server to register DOIs. Used by spring
     * dependency injection.
     * @param DATACITE_DOI_PATH Path to register DOIs, f.e. /doi.
     */
    @Required
    public void setDATACITE_DOI_PATH(String DATACITE_DOI_PATH) {
        if (!DATACITE_DOI_PATH.startsWith("/")) {
            DATACITE_DOI_PATH = "/" + DATACITE_DOI_PATH;
        }
        if (!DATACITE_DOI_PATH.endsWith("/")) {
            DATACITE_DOI_PATH = DATACITE_DOI_PATH + "/";
        }

        this.DOI_PATH = DATACITE_DOI_PATH;
    }

    /**
     * Set the path to register metadata on DataCite server. Used by spring
     * dependency injection.
     * @param DATACITE_METADATA_PATH Path to register metadata, f.e. /mds.
     */
    @Required
    public void setDATACITE_METADATA_PATH(String DATACITE_METADATA_PATH) {
        if (!DATACITE_METADATA_PATH.startsWith("/")) {
            DATACITE_METADATA_PATH = "/" + DATACITE_METADATA_PATH;
        }
        if (!DATACITE_METADATA_PATH.endsWith("/")) {
            DATACITE_METADATA_PATH = DATACITE_METADATA_PATH + "/";
        }

        this.METADATA_PATH = DATACITE_METADATA_PATH;
    }

    @Autowired
    @Required
    public void setConfigurationService(ConfigurationService configurationService) {
        this.configurationService = configurationService;
    }

    /**
     * Set the name of the dissemination crosswalk used to convert the metadata
     * into DataCite Metadata Schema. Used by spring dependency injection.
     * @param CROSSWALK_NAME The name of the dissemination crosswalk to use. This
     *                       crosswalk must be configured in dspace.cfg.
     */
    @Required
    public void setDisseminationCrosswalkName(String CROSSWALK_NAME) {
        this.CROSSWALK_NAME = CROSSWALK_NAME;
    }

    private void prepareXwalk() {
        if (null != this.xwalk)
            return;

        this.xwalk = (DisseminationCrosswalk) PluginManager.getNamedPlugin(DisseminationCrosswalk.class,
                this.CROSSWALK_NAME);

        if (this.xwalk == null) {
            throw new RuntimeException("Can't find crosswalk '" + CROSSWALK_NAME + "'!");
        }
    }

    private String getUsername() {
        if (null == this.USERNAME) {
            this.USERNAME = this.configurationService.getProperty(CFG_USER);
            if (null == this.USERNAME) {
                throw new RuntimeException(
                        "Unable to load username from " + "configuration. Cannot find property " + CFG_USER + ".");
            }
        }
        return this.USERNAME;
    }

    private String getPassword() {
        if (null == this.PASSWORD) {
            this.PASSWORD = this.configurationService.getProperty(CFG_PASSWORD);
            if (null == this.PASSWORD) {
                throw new RuntimeException("Unable to load password from " + "configuration. Cannot find property "
                        + CFG_PASSWORD + ".");
            }
        }
        return this.PASSWORD;
    }

    public boolean isDOIReserved(Context context, String doi) throws DOIIdentifierException {
        return isDOIReserved(context, null, doi);
    }

    @Override
    public boolean isDOIReserved(Context context, DSpaceObject dso, String doi) throws DOIIdentifierException {
        // get mds/metadata/<doi>
        DataCiteResponse resp = this.sendMetadataGetRequest(doi);

        switch (resp.getStatusCode()) {
        // 200 -> reserved
        // if (200 && dso != null) -> compare url (out of response-content) with dso
        case (200): {
            // Do we check if doi is reserved generally or for a specified dso?
            if (null == dso) {
                return true;
            }

            // check if doi belongs to dso
            String doiHandle = null;
            try {
                doiHandle = extractAlternateIdentifier(context, resp.getContent());
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }

            if (null == doiHandle) {
                // we were unable to find a handle belonging to our repository
                return false;
            }

            String dsoHandle = dso.getHandle();
            if (null == dsoHandle) {
                return false;
            }
            return dsoHandle.equals(doiHandle);
        }

        // 404 "Not Found" means DOI is neither reserved nor registered.
        case (404): {
            return false;
        }

        // 410 GONE -> DOI is set inactive
        // that means metadata has been deleted
        // it is unclear if the doi has been registered before or only reserved.
        // it is unclear for which item it has been reserved
        // we will handle this as if it reserved for an unknown object.
        case (410): {
            if (null == dso) {
                return true;
            } else {
                return false;
            }
        }

        // Catch all other http status code in case we forgot one.
        default: {
            log.warn(
                    "While checking if the DOI {} is registered, we got a "
                            + "http status code {} and the message \"{}\".",
                    new String[] { doi, Integer.toString(resp.statusCode), resp.getContent() });
            throw new DOIIdentifierException(
                    "Unable to parse an answer from " + "DataCite API. Please have a look into DSpace logs.",
                    DOIIdentifierException.BAD_ANSWER);
        }
        }
    }

    @Override
    public boolean isDOIRegistered(Context context, String doi) throws DOIIdentifierException {
        return isDOIRegistered(context, null, doi);
    }

    @Override
    public boolean isDOIRegistered(Context context, DSpaceObject dso, String doi) throws DOIIdentifierException {
        DataCiteResponse response = sendDOIGetRequest(doi);

        switch (response.getStatusCode()) {
        // status code 200 means the doi is reserved and registered
        case (200): {
            // Do we check if doi is reserved generally or for a specified dso?
            if (null == dso) {
                return true;
            }

            // DataCite returns the URL the DOI currently points to.
            // To ensure that the DOI is registered for a specified dso it
            // should be sufficient to compare the URL DataCite returns with
            // the URL of the dso.
            String doiUrl = response.getContent();
            if (null == doiUrl) {
                log.error("Received a status code 200 without a response content. DOI: {}.", doi);
                throw new DOIIdentifierException("Received a http status code 200 without a response content.",
                        DOIIdentifierException.BAD_ANSWER);
            }

            String dsoUrl = null;
            try {
                dsoUrl = HandleManager.resolveToURL(context, dso.getHandle());
            } catch (SQLException e) {
                log.error("Error in database connection: " + e.getMessage());
                throw new RuntimeException(e);
            }

            if (null == dsoUrl) {
                // the handle of the dso was not found in our db?!
                log.error("The HandleManager was unable to find the handle "
                        + "of a DSpaceObject in the database!?! " + "Type: {} ID: {}", dso.getTypeText(),
                        dso.getID());
                throw new RuntimeException(
                        "The HandleManager was unable to " + "find the handle of a DSpaceObject in the database!");
            }

            return (dsoUrl.equals(doiUrl));
        }
        // Status Code 204 "No Content" stands for a known DOI without URL.
        // A DOI that is known but does not have any associated URL is
        // reserved but not registered yet.
        case (204): {
            // we know it is reserved, but we do not know for which object.
            // won't add this to the cache.
            return false;
        }
        // 404 "Not Found" means DOI is neither reserved nor registered.
        case (404): {
            return false;
        }
        // Catch all other http status code in case we forgot one.
        default: {
            log.warn(
                    "While checking if the DOI {} is registered, we got a "
                            + "http status code {} and the message \"{}\".",
                    new String[] { doi, Integer.toString(response.statusCode), response.getContent() });
            throw new DOIIdentifierException(
                    "Unable to parse an answer from " + "DataCite API. Please have a look into DSpace logs.",
                    DOIIdentifierException.BAD_ANSWER);
        }
        }
    }

    @Override
    public void deleteDOI(Context context, String doi) throws DOIIdentifierException {
        if (!isDOIReserved(context, doi))
            return;

        // delete mds/metadata/<doi>
        DataCiteResponse resp = this.sendMetadataDeleteRequest(doi);
        switch (resp.getStatusCode()) {
        //ok
        case (200): {
            return;
        }
        // 404 "Not Found" means DOI is neither reserved nor registered.
        case (404): {
            log.error("DOI {} is at least reserved, but a delete request " + "told us that it is unknown!", doi);
            return;
        }
        // Catch all other http status code in case we forgot one.
        default: {
            log.warn("While deleting metadata of DOI {}, we got a " + "http status code {} and the message \"{}\".",
                    new String[] { doi, Integer.toString(resp.statusCode), resp.getContent() });
            throw new DOIIdentifierException(
                    "Unable to parse an answer from " + "DataCite API. Please have a look into DSpace logs.",
                    DOIIdentifierException.BAD_ANSWER);
        }
        }
    }

    @Override
    public void reserveDOI(Context context, DSpaceObject dso, String doi) throws DOIIdentifierException {
        this.prepareXwalk();

        if (!this.xwalk.canDisseminate(dso)) {
            log.error("Crosswalk " + this.CROSSWALK_NAME + " cannot disseminate DSO with type " + dso.getType()
                    + " and ID " + dso.getID() + ". Giving up reserving the DOI " + doi + ".");
            throw new DOIIdentifierException("Cannot disseminate " + dso.getTypeText() + "/" + dso.getID()
                    + " using crosswalk " + this.CROSSWALK_NAME + ".", DOIIdentifierException.CONVERSION_ERROR);
        }

        Element root = null;
        try {
            root = xwalk.disseminateElement(dso);
        } catch (AuthorizeException ae) {
            log.error("Caught an AuthorizeException while disseminating DSO " + "with type " + dso.getType()
                    + " and ID " + dso.getID() + ". Giving up to reserve DOI " + doi + ".", ae);
            throw new DOIIdentifierException(
                    "AuthorizeException occured while " + "converting " + dso.getTypeText() + "/" + dso.getID()
                            + " using crosswalk " + this.CROSSWALK_NAME + ".",
                    ae, DOIIdentifierException.CONVERSION_ERROR);
        } catch (CrosswalkException ce) {
            log.error("Caught an CrosswalkException while reserving a DOI (" + doi + ") for DSO with type "
                    + dso.getType() + " and ID " + dso.getID() + ". Won't reserve the doi.", ce);
            throw new DOIIdentifierException(
                    "CrosswalkException occured while " + "converting " + dso.getTypeText() + "/" + dso.getID()
                            + " using crosswalk " + this.CROSSWALK_NAME + ".",
                    ce, DOIIdentifierException.CONVERSION_ERROR);
        } catch (IOException ioe) {
            throw new RuntimeException(ioe);
        } catch (SQLException se) {
            throw new RuntimeException(se);
        }

        String metadataDOI = extractDOI(root);
        if (null == metadataDOI) {
            // The DOI will be saved as metadata of dso after successful
            // registration. To register a doi it has to be part of the metadata
            // sent to DataCite. So we add it to the XML we'll send to DataCite
            // and we'll add it to the DSO after successful registration.
            root = addDOI(doi, root);
        } else if (!metadataDOI.equals(doi.substring(DOI.SCHEME.length()))) {
            // FIXME: that's not an error. If at all, it is worth logging it.
            throw new DOIIdentifierException("DSO with type " + dso.getTypeText() + " and id " + dso.getID()
                    + " already has DOI " + metadataDOI + ". Won't reserve DOI " + doi + " for it.");
        }

        // send metadata as post to mds/metadata
        DataCiteResponse resp = this.sendMetadataPostRequest(doi, root);

        switch (resp.getStatusCode()) {
        // 201 -> created / ok
        case (201): {
            return;
        }
        // 400 -> invalid XML
        case (400): {
            log.warn("DataCite was unable to understand the XML we send.");
            log.warn("DataCite Metadata API returned a http status code " + "400: " + resp.getContent());
            Format format = Format.getCompactFormat();
            format.setEncoding("UTF-8");
            XMLOutputter xout = new XMLOutputter(format);
            log.info("We send the following XML:\n" + xout.outputString(root));
            throw new DOIIdentifierException("Unable to reserve DOI " + doi
                    + ". Please inform your administrator or take a look " + " into the log files.",
                    DOIIdentifierException.BAD_REQUEST);
        }
        // Catch all other http status code in case we forgot one.
        default: {
            log.warn("While reserving the DOI {}, we got a http status code " + "{} and the message \"{}\".",
                    new String[] { doi, Integer.toString(resp.statusCode), resp.getContent() });
            throw new DOIIdentifierException(
                    "Unable to parse an answer from " + "DataCite API. Please have a look into DSpace logs.",
                    DOIIdentifierException.BAD_ANSWER);
        }
        }
    }

    @Override
    public void registerDOI(Context context, DSpaceObject dso, String doi) throws DOIIdentifierException {
        log.debug("Want to register DOI {}!", doi);

        // send doi=<doi>\nurl=<url> to mds/doi
        DataCiteResponse resp = null;
        try {
            resp = this.sendDOIPostRequest(doi, HandleManager.resolveToURL(context, dso.getHandle()));
        } catch (SQLException e) {
            log.error("Caught SQL-Exception while resolving handle to URL: " + e.getMessage());
            throw new RuntimeException(e);
        }

        switch (resp.statusCode) {
        // 201 -> created/updated -> okay
        case (201): {
            return;
        }
        // 400 -> wrong domain, wrong prefix, wrong request body
        case (400): {
            log.warn("We send an irregular request to DataCite. While " + "registering a DOI they told us: "
                    + resp.getContent());
            throw new DOIIdentifierException("Currently we cannot register "
                    + "DOIs. Please inform the administrator or take a look " + " in the DSpace log file.",
                    DOIIdentifierException.BAD_REQUEST);
        }
        // 412 Precondition failed: DOI was not reserved before registration!
        case (412): {
            log.error("We tried to register a DOI {} that was not reserved "
                    + "before! The registration agency told us: {}.", doi, resp.getContent());
            throw new DOIIdentifierException(
                    "There was an error in handling " + "of DOIs. The DOI we wanted to register had not been "
                            + "reserved in advance. Please contact the administrator "
                            + "or take a look in DSpace log file.",
                    DOIIdentifierException.REGISTER_FIRST);
        }
        // Catch all other http status code in case we forgot one.
        default: {
            log.warn("While registration of DOI {}, we got a http status code " + "{} and the message \"{}\".",
                    new String[] { doi, Integer.toString(resp.statusCode), resp.getContent() });
            throw new DOIIdentifierException(
                    "Unable to parse an answer from " + "DataCite API. Please have a look into DSpace logs.",
                    DOIIdentifierException.BAD_ANSWER);
        }
        }
    }

    @Override
    public void updateMetadata(Context context, DSpaceObject dso, String doi) throws DOIIdentifierException {
        // We can use reserveDOI to update metadata. Datacite API uses the same
        // request for reservartion as for updating metadata.
        this.reserveDOI(context, dso, doi);
    }

    private DataCiteResponse sendDOIPostRequest(String doi, String url) throws DOIIdentifierException {
        // post mds/doi/
        // body must contaion "doi=<doi>\nurl=<url>}n"
        URIBuilder uribuilder = new URIBuilder();
        uribuilder.setScheme("https").setHost(HOST).setPath(DOI_PATH);

        HttpPost httppost = null;
        try {
            httppost = new HttpPost(uribuilder.build());
        } catch (URISyntaxException e) {
            log.error("The URL we constructed to check a DOI "
                    + "produced a URISyntaxException. Please check the configuration parameters!");
            log.error("The URL was {}.", "https://" + HOST + DOI_PATH + "/" + doi.substring(DOI.SCHEME.length()));
            throw new RuntimeException("The URL we constructed to check a DOI "
                    + "produced a URISyntaxException. Please check the configuration parameters!", e);
        }

        // assemble request content:
        HttpEntity reqEntity = null;
        try {
            String req = "doi=" + doi.substring(DOI.SCHEME.length()) + "\n" + "url=" + url + "\n";
            ContentType contentType = ContentType.create("text/plain", "UTF-8");
            reqEntity = new StringEntity(req, contentType);
            httppost.setEntity(reqEntity);

            return sendHttpRequest(httppost, doi);
        } finally {
            // release ressources
            try {
                EntityUtils.consume(reqEntity);
            } catch (IOException ioe) {
                log.info("Caught an IOException while releasing a HTTPEntity:" + ioe.getMessage());
            }
        }
    }

    private DataCiteResponse sendMetadataDeleteRequest(String doi) throws DOIIdentifierException {
        // delete mds/metadata/<doi>
        URIBuilder uribuilder = new URIBuilder();
        uribuilder.setScheme("https").setHost(HOST).setPath(METADATA_PATH + doi.substring(DOI.SCHEME.length()));

        HttpDelete httpdelete = null;
        try {
            httpdelete = new HttpDelete(uribuilder.build());
        } catch (URISyntaxException e) {
            log.error("The URL we constructed to check a DOI "
                    + "produced a URISyntaxException. Please check the configuration parameters!");
            log.error("The URL was {}.", "https://" + HOST + DOI_PATH + "/" + doi.substring(DOI.SCHEME.length()));
            throw new RuntimeException("The URL we constructed to check a DOI "
                    + "produced a URISyntaxException. Please check the configuration parameters!", e);
        }
        return sendHttpRequest(httpdelete, doi);
    }

    private DataCiteResponse sendDOIGetRequest(String doi) throws DOIIdentifierException {
        return sendGetRequest(doi, DOI_PATH);
    }

    private DataCiteResponse sendMetadataGetRequest(String doi) throws DOIIdentifierException {
        return sendGetRequest(doi, METADATA_PATH);
    }

    private DataCiteResponse sendGetRequest(String doi, String path) throws DOIIdentifierException {
        URIBuilder uribuilder = new URIBuilder();
        uribuilder.setScheme("https").setHost(HOST).setPath(path + doi.substring(DOI.SCHEME.length()));

        HttpGet httpget = null;
        try {
            httpget = new HttpGet(uribuilder.build());
        } catch (URISyntaxException e) {
            log.error("The URL we constructed to check a DOI "
                    + "produced a URISyntaxException. Please check the configuration parameters!");
            log.error("The URL was {}.", "https://" + HOST + DOI_PATH + "/" + doi.substring(DOI.SCHEME.length()));
            throw new RuntimeException("The URL we constructed to check a DOI "
                    + "produced a URISyntaxException. Please check the configuration parameters!", e);
        }
        return sendHttpRequest(httpget, doi);
    }

    private DataCiteResponse sendMetadataPostRequest(String doi, Element metadataRoot)
            throws DOIIdentifierException {
        Format format = Format.getCompactFormat();
        format.setEncoding("UTF-8");
        XMLOutputter xout = new XMLOutputter(format);
        return sendMetadataPostRequest(doi, xout.outputString(new Document(metadataRoot)));
    }

    private DataCiteResponse sendMetadataPostRequest(String doi, String metadata) throws DOIIdentifierException {
        // post mds/metadata/
        // body must contain metadata in DataCite-XML.
        URIBuilder uribuilder = new URIBuilder();
        uribuilder.setScheme("https").setHost(HOST).setPath(METADATA_PATH);

        HttpPost httppost = null;
        try {
            httppost = new HttpPost(uribuilder.build());
        } catch (URISyntaxException e) {
            log.error("The URL we constructed to check a DOI "
                    + "produced a URISyntaxException. Please check the configuration parameters!");
            log.error("The URL was {}.", "https://" + HOST + DOI_PATH + "/" + doi.substring(DOI.SCHEME.length()));
            throw new RuntimeException("The URL we constructed to check a DOI "
                    + "produced a URISyntaxException. Please check the configuration parameters!", e);
        }

        // assemble request content:
        HttpEntity reqEntity = null;
        try {
            ContentType contentType = ContentType.create("application/xml", "UTF-8");
            reqEntity = new StringEntity(metadata, contentType);
            httppost.setEntity(reqEntity);

            return sendHttpRequest(httppost, doi);
        } finally {
            // release ressources
            try {
                EntityUtils.consume(reqEntity);
            } catch (IOException ioe) {
                log.info("Caught an IOException while releasing an HTTPEntity:" + ioe.getMessage());
            }
        }
    }

    /**
     * 
     * @param req
     * @param doi
     * @return
     * @throws DOIIdentifierException 
     */
    private DataCiteResponse sendHttpRequest(HttpUriRequest req, String doi) throws DOIIdentifierException {
        DefaultHttpClient httpclient = new DefaultHttpClient();
        httpclient.getCredentialsProvider().setCredentials(new AuthScope(HOST, 443),
                new UsernamePasswordCredentials(this.getUsername(), this.getPassword()));

        HttpEntity entity = null;
        try {
            HttpResponse response = httpclient.execute(req);

            StatusLine status = response.getStatusLine();
            int statusCode = status.getStatusCode();

            String content = null;
            entity = response.getEntity();
            if (null != entity) {
                content = EntityUtils.toString(entity, "UTF-8");
            }

            /* While debugging it can be useful to see whitch requests are send:
             *
             * log.debug("Going to send HTTP request of type " + req.getMethod() + ".");
             * log.debug("Will be send to " + req.getURI().toString() + ".");
             * if (req instanceof HttpEntityEnclosingRequestBase)
             * {
             *     log.debug("Request contains entity!");
             *     HttpEntityEnclosingRequestBase reqee = (HttpEntityEnclosingRequestBase) req;
             *     if (reqee.getEntity() instanceof StringEntity)
             *     {
             *         StringEntity se = (StringEntity) reqee.getEntity();
             *         try {
             *             BufferedReader br = new BufferedReader(new InputStreamReader(se.getContent()));
             *             String line = null;
             *             while ((line = br.readLine()) != null)
             *             {
             *                 log.debug(line);
             *             }
             *             log.info("----");
             *         } catch (IOException ex) {
             *             
             *         }
             *     }
             * } else {
             *     log.debug("Request contains no entity!");
             * }
             * log.debug("The request got http status code {}.", Integer.toString(statusCode));
             * if (null == content)
             * {
             *     log.debug("The response did not contain any answer.");
             * } else {
             *     log.debug("DataCite says: {}", content);
             * }
             * 
             */

            // We can handle some status codes here, others have to be handled above
            switch (statusCode) {
            // we get a 401 if we forgot to send credentials or if the username
            // and password did not match.
            case (401): {
                log.info("We were unable to authenticate against the DOI registry agency.");
                log.info("The response was: {}", content);
                throw new DOIIdentifierException("Cannot authenticate at the "
                        + "DOI registry agency. Please check if username " + "and password are set correctly.",
                        DOIIdentifierException.AUTHENTICATION_ERROR);
            }

            // We get a 403 Forbidden if we are managing a DOI that belongs to
            // another party or if there is a login problem.
            case (403): {
                log.info("Managing a DOI ({}) was prohibited by the DOI " + "registration agency: {}", doi,
                        content);
                throw new DOIIdentifierException(
                        "We can check, register or " + "reserve DOIs that belong to us only.",
                        DOIIdentifierException.FOREIGN_DOI);
            }

            // 500 is documented and signals an internal server error
            case (500): {
                log.warn("Caught an http status code 500 while managing DOI " + "{}. Message was: " + content);
                throw new DOIIdentifierException(
                        "DataCite API has an internal error. " + "It is temporarily impossible to manage DOIs. "
                                + "Further information can be found in DSpace log file.",
                        DOIIdentifierException.INTERNAL_ERROR);
            }
            }

            return new DataCiteResponse(statusCode, content);
        } catch (IOException e) {
            log.warn("Caught an IOException: " + e.getMessage());
            throw new RuntimeException(e);
        } finally {
            try {
                // Release any ressources used by HTTP-Request.
                if (null != entity) {
                    EntityUtils.consume(entity);
                }
            } catch (IOException e) {
                log.warn("Can't release HTTP-Entity: " + e.getMessage());
            }
        }
    }

    // returns null or handle
    private String extractAlternateIdentifier(Context context, String content)
            throws SQLException, DOIIdentifierException {
        if (content == null) {
            return null;
        }

        // parse the XML
        SAXBuilder saxBuilder = new SAXBuilder();
        Document doc = null;
        try {
            doc = saxBuilder.build(new ByteArrayInputStream(content.getBytes("UTF-8")));
        } catch (IOException ioe) {
            throw new RuntimeException("Got an IOException while reading from a string?!", ioe);
        } catch (JDOMException jde) {
            throw new DOIIdentifierException(
                    "Got a JDOMException while parsing " + "a response from the DataCite API.", jde,
                    DOIIdentifierException.BAD_ANSWER);
        }

        String handle = null;

        Iterator<Element> it = doc.getDescendants(new ElementFilter("alternateIdentifier"));
        while (handle == null && it.hasNext()) {
            Element alternateIdentifier = it.next();
            try {
                handle = HandleManager.resolveUrlToHandle(context, alternateIdentifier.getText());
            } catch (SQLException e) {
                throw e;
            }
        }

        return handle;
    }

    private String extractDOI(Element root) {
        Element doi = root.getChild("identifier", root.getNamespace());
        return (null == doi) ? null : doi.getTextTrim();
    }

    private Element addDOI(String doi, Element root) {
        if (null != extractDOI(root)) {
            return root;
        }
        Element identifier = new Element("identifier", "http://datacite.org/schema/kernel-2.2");
        identifier.setAttribute("identifierType", "DOI");
        identifier.addContent(doi.substring(DOI.SCHEME.length()));
        return root.addContent(0, identifier);
    }

    private class DataCiteResponse {
        private final int statusCode;
        private final String content;

        protected DataCiteResponse(int statusCode, String content) {
            this.statusCode = statusCode;
            this.content = content;
        }

        protected int getStatusCode() {
            return this.statusCode;
        }

        protected String getContent() {
            return this.content;
        }
    }
}