org.ambraproject.service.orcid.OrcidServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.ambraproject.service.orcid.OrcidServiceImpl.java

Source

/*
 * Copyright (c) 2007-2014 by Public Library of Science
 *
 * http://plos.org
 * http://ambraproject.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ambraproject.service.orcid;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import org.ambraproject.views.OrcidAuthorization;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.net.URLEncoder;

/**
 * {@inheritDoc}
 */
public class OrcidServiceImpl implements OrcidService {
    private static final Logger log = LoggerFactory.getLogger(OrcidServiceImpl.class);

    private String tokenEndPoint;
    private HttpClient httpClient;
    private String clientID;
    private String clientSecret;

    /**
     * This sets the access level to request of ORCiD
     *
     * http://support.orcid.org/knowledgebase/articles/120162-orcid-scopes
     *
     * We'll only want to READ data so I've chosen: "/orcid-profile/read-limited"
     *
     */
    public static String API_SCOPE = "/orcid-profile/read-limited";

    private Gson gson;

    public OrcidServiceImpl() {
        /**
         * This never changes, lets only create it once.
         */
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(OrcidAuthorization.class, new JsonDeserializer<OrcidAuthorization>() {
            @Override
            public OrcidAuthorization deserialize(JsonElement jsonElement, Type type,
                    JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
                JsonObject json = (JsonObject) jsonElement;

                return OrcidAuthorization.builder().setAccessToken(json.get("access_token").getAsString())
                        .setTokenType(json.get("token_type").getAsString())
                        .setRefreshToken(json.get("refresh_token").getAsString())
                        .setExpiresIn(json.get("expires_in").getAsInt()).setScope(json.get("scope").getAsString())
                        .setOrcid(json.get("orcid").getAsString()).build();
            }
        });

        gson = gsonBuilder.create();
    }

    /**
     * {@inheritDoc}
     */
    public OrcidAuthorization authorizeUser(String authorizationCode) throws Exception {
        //Currently we authorize the account, but all we really want is the ORCiD
        //which comes down along with the authorization token, so we
        //never actually have to use the returned accessToken.  However
        //I am capturing it for future use.

        PostMethod post = createOrcidAccessTokenQuery(authorizationCode);

        try {
            long timestamp = System.currentTimeMillis();
            int response = httpClient.executeMethod(post);

            log.debug("Http post finished in {} ms", System.currentTimeMillis() - timestamp);

            //The error handling here is a bit weird

            //ORCiD will return a 500 error if an invalid token is present
            //It will also return this code if the application throws an exception on their end
            //The only way to seemingly disambiguate would be to parse the response text if we
            //want to improve on this later
            //For now, I log the error and return null.

            //ORCiD will return 401 when an invalid client-id or client-secret is present
            //ORCiD returns 200 on success

            if (response == 200) {
                String result = post.getResponseBodyAsString();
                if (result != null) {

                    /** Example response:
                     {
                       "access_token": "8056b6d5-f96d-42c8-8df9-41f70908ad33",
                       "token_type": "bearer",
                       "refresh_token": "d8c72880-00ac-4447-a2af-e90f1673f4bc",
                       "expires_in": 631138286,
                       "scope": "/orcid-profile/read-limited",
                       "orcid": "0000-0003-4954-7894"
                     }*/
                    log.trace("Response received: {}", result);

                    return gson.fromJson(result, OrcidAuthorization.class);
                }

                log.error("Received empty response, response code {}, when executing query  {}", response,
                        post.getURI().toString());
                throw new IOException("Received empty response from ORCiD");
            } else if (response == 401) {
                throw new OrcidAuthorizationException(
                        "Invalid client ID or secret not defined for ORCiD, check your ambra configuration");
            } else {
                log.error("Received response code {} when executing query {}", response, post.getURI().toString());
                log.error("Received response body: {}", post.getResponseBodyAsString());
                return null;
            }
        } finally {
            // be sure the connection is released back to the connection manager
            post.releaseConnection();
        }
    }

    private PostMethod createOrcidAccessTokenQuery(String authorizationCode) throws UnsupportedEncodingException {
        final String query = "code=" + authorizationCode + "&client_id=" + this.clientID + "&scope="
                + URLEncoder.encode(API_SCOPE, "UTF-8") + "&client_secret=" + this.clientSecret
                + "&grant_type=authorization_code";

        return new PostMethod(this.tokenEndPoint) {
            {
                setRequestEntity(new RequestEntity() {
                    @Override
                    public boolean isRepeatable() {
                        return false;
                    }

                    @Override
                    public void writeRequest(OutputStream outputStream) throws IOException {
                        outputStream.write(query.getBytes());
                    }

                    @Override
                    public long getContentLength() {
                        return query.getBytes().length;
                    }

                    @Override
                    public String getContentType() {
                        return "application/x-www-form-urlencoded";
                    }
                });
            }
        };
    }

    public void setTokenEndPoint(String tokenEndPoint) {
        this.tokenEndPoint = tokenEndPoint;
    }

    @Required
    public void setHttpClient(HttpClient httpClient) {
        this.httpClient = httpClient;
    }

    public void setClientID(String clientID) {
        this.clientID = clientID;
    }

    public void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
    }

    /**
     * {@inheritDoc}
     */
    public String getClientID() {
        return clientID;
    }

    /**
     * {@inheritDoc}
     */
    public String getScope() {
        return API_SCOPE;
    }
}