All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.isuper.oauth.client.OAuthApacheHttpClient Maven / Gradle / Ivy

Go to download

OAuth Client Library includes Apache and Google based HTTP client implementation using OAuth v2.0

The newest version!
/**
 * 
 */
package org.isuper.oauth.client;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.isuper.common.utils.Preconditions;
import org.isuper.httpclient.AsyncHttpClient;
import org.isuper.oauth.OAuthClientConfig;
import org.isuper.oauth.OAuthCredential;
import org.isuper.oauth.client.exceptions.InvalidOAuthCredentialException;
import org.isuper.oauth.client.exceptions.RefreshTokenRevokedException;
import org.isuper.oauth.v20.GrantType;
import org.isuper.oauth.v20.OAuth20;
import org.isuper.oauth.v20.ParameterStyle;
import org.isuper.oauth.v20.ResponseType;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * @author Super Wang
 *
 */
public class OAuthApacheHttpClient implements OAuthHttpClient {

	private static final Logger LOG = LogManager.getLogger("org.isuper.oauth.client.apache");
	
	public static final DateFormat DEFAULT_DATE_FORMAT = new SimpleDateFormat("E MMM dd HH:mm:ss Z yyyy", Locale.US);
	
	private static final JsonFactory JSON_FACTORY = new JsonFactory();
	private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(JSON_FACTORY);

	protected final OAuthClientConfig clientConfig;
	protected final AsyncHttpClient httpclient;
	
	private OAuthApacheHttpClient(final OAuthClientConfig clientConfig) {
		this.clientConfig = clientConfig;
		try {
			this.httpclient = AsyncHttpClient.newInstance(this.clientConfig.proxyHostname == null || "".equals(this.clientConfig.proxyHostname.trim()) ? null : this.clientConfig.proxyHostname, this.clientConfig.proxyPort);
		} catch (Throwable e) {
			throw new RuntimeException("Failed to init " + OAuthApacheHttpClient.class.getSimpleName(), e);
		}
	}
	
	/**
	 * @param clientConfigFile
	 * 				The OAuth client configuration JSON file
	 * @return
	 * 				OAuth HTTP client
	 */
	public static OAuthHttpClient newInstance(final String clientConfigFile) {
		return newInstance(clientConfigFile, DEFAULT_DATE_FORMAT);
	}
	
	/**
	 * @param clientConfigFile
	 * 				The OAuth client configuration JSON file
	 * @param dateFormat
	 * 				The date format for JSON parser
	 * @return
	 * 				OAuth HTTP client
	 */
	public static OAuthHttpClient newInstance(final String clientConfigFile, final DateFormat dateFormat) {
		OBJECT_MAPPER.setDateFormat(dateFormat);
		OAuthClientConfig clientConfig;
		try {
			clientConfig = OAuthClientConfig.readFromClasspath(clientConfigFile);
			return new OAuthApacheHttpClient(clientConfig);
		} catch (Throwable e) {
			throw new RuntimeException("Failed to init " + OAuthApacheHttpClient.class.getSimpleName(), e);
		}
	}
	
	/* (non-Javadoc)
	 * @see org.isuper.oauth.client.OAuthHttpClient#getAuthorizeURL(org.isuper.oauth.v20.ResponseType, java.lang.String, java.lang.String, java.lang.String, java.util.Map)
	 */
	@Override
	public String getAuthorizeURL(ResponseType responseType, String redirectURL, String state, String scope, Map props) {
		if (responseType == null) {
			responseType = ResponseType.CODE;
		}
		Preconditions.notEmptyString(redirectURL, "Can not get access token with null or empty redirect URL!");
		try {
			URIBuilder builder = new URIBuilder(this.clientConfig.authUri);
			builder.addParameter(OAuth20.OAUTH_RESPONSE_TYPE, responseType.name().toLowerCase());
			builder.addParameter(OAuth20.OAUTH_CLIENT_ID, this.clientConfig.clientId);
			builder.addParameter(OAuth20.OAUTH_REDIRECT_URI, redirectURL);
			if (!Preconditions.isEmptyString(state)) {
				builder.addParameter(OAuth20.OAUTH_STATE, state);
			}
			if (!Preconditions.isEmptyString(scope)) {
				builder.addParameter(OAuth20.OAUTH_SCOPE, scope);
			}
			if (props != null && !props.isEmpty()) {
				for (Map.Entry entry : props.entrySet()) {
					String key = entry.getKey();
					String value = entry.getValue();
					builder.addParameter(key, value == null ? "" : value);
				}
			}
			return builder.build().toURL().toExternalForm();
		} catch (URISyntaxException | MalformedURLException e) {
			throw new RuntimeException(String.format("Invalid authorization URL: %s", this.clientConfig.authUri));
		}
	}

	/* (non-Javadoc)
	 * @see org.isuper.oauth.client.OAuthHttpClient#retrieveAccessToken(org.isuper.oauth.v20.ParameterStyle, java.lang.String, org.isuper.oauth.v20.GrantType, java.lang.String)
	 */
	@Override
	public OAuthCredential retrieveAccessToken(ParameterStyle style, String code, GrantType grantType, String redirectURL) throws IOException {
		if (grantType == null) {
			grantType = GrantType.AUTHORIZATION_CODE;
		}
		Preconditions.notEmptyString(code, "Can not get access token with null or empty OAuth authorization code!");
		HttpRequestBase request;
		switch (style) {
		case QUERY_STRING:
			try {
				URIBuilder builder = new URIBuilder(this.clientConfig.authUri);
				builder.addParameter(OAuth20.OAUTH_CODE, code);
				builder.addParameter(OAuth20.OAUTH_CLIENT_ID, this.clientConfig.clientId);
				builder.addParameter(OAuth20.OAUTH_CLIENT_SECRET, this.clientConfig.clientSecret);
				builder.addParameter(OAuth20.OAUTH_REDIRECT_URI, redirectURL);
				builder.addParameter(OAuth20.OAUTH_GRANT_TYPE, grantType.name().toLowerCase());
				request = new HttpGet(builder.build());
			} catch (URISyntaxException e) {
				throw new RuntimeException(String.format("Invalid token URL: %s", this.clientConfig.tokenUri));
			}
			break;
		default:
			HttpPost post = new HttpPost(this.clientConfig.tokenUri);
			List nameValuePairs = new ArrayList<>(5);
			nameValuePairs.add(new BasicNameValuePair(OAuth20.OAUTH_CODE, code));
			nameValuePairs.add(new BasicNameValuePair(OAuth20.OAUTH_CLIENT_ID, this.clientConfig.clientId));
			nameValuePairs.add(new BasicNameValuePair(OAuth20.OAUTH_CLIENT_SECRET, this.clientConfig.clientSecret));
			nameValuePairs.add(new BasicNameValuePair(OAuth20.OAUTH_REDIRECT_URI, redirectURL));
			nameValuePairs.add(new BasicNameValuePair(OAuth20.OAUTH_GRANT_TYPE, grantType.name().toLowerCase()));
			post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
			request = post;
			break;
		}

		LOG.trace(request.getRequestLine());

		try {
			Future future = this.httpclient.execute(request, null);
			HttpResponse resp = future.get();
			StatusLine status = resp.getStatusLine();
			HttpEntity entity = resp.getEntity();
			String contentType = entity.getContentType().getValue();
			String content = EntityUtils.toString(entity);
			
			LOG.trace(resp.getStatusLine());
			LOG.trace(contentType);
			LOG.trace(content);
			
			if (status.getStatusCode() != 200) {
				throw new IOException("Failed response from server: " + status.toString());
			}
			
			ObjectMapper objectMapper = new ObjectMapper();
			return objectMapper.readValue(content, OAuthCredential.class);
		} catch (InterruptedException | ExecutionException e) {
			LOG.error(e.getLocalizedMessage(), e);
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see org.isuper.oauth.client.OAuthHttpClient#refreshToken(org.isuper.oauth.v20.ParameterStyle, java.lang.String, org.isuper.oauth.v20.GrantType)
	 */
	@Override
	public OAuthCredential refreshToken(ParameterStyle style, String refreshToken, GrantType grantType) throws RefreshTokenRevokedException, IOException {
		if (grantType == null) {
			grantType = GrantType.REFRESH_TOKEN;
		}
		Preconditions.notEmptyString(refreshToken, "Can not get a new access token with null or empty OAuth refresh token!");
		HttpRequestBase request;
		switch (style) {
		case QUERY_STRING:
			try {
				URIBuilder builder = new URIBuilder(this.clientConfig.tokenUri);
				builder.addParameter(OAuth20.OAUTH_REFRESH_TOKEN, refreshToken);
				builder.addParameter(OAuth20.OAUTH_CLIENT_ID, this.clientConfig.clientId);
				builder.addParameter(OAuth20.OAUTH_CLIENT_SECRET, this.clientConfig.clientSecret);
				builder.addParameter(OAuth20.OAUTH_GRANT_TYPE, grantType.name().toLowerCase());
				request = new HttpGet(builder.build());
			} catch (URISyntaxException e) {
				throw new RuntimeException(String.format("Invalid token URL: %s", this.clientConfig.tokenUri));
			}
			break;
		default:
			HttpPost post = new HttpPost(this.clientConfig.tokenUri);
			List nameValuePairs = new ArrayList<>(4);
			nameValuePairs.add(new BasicNameValuePair(OAuth20.OAUTH_REFRESH_TOKEN, refreshToken));
			nameValuePairs.add(new BasicNameValuePair(OAuth20.OAUTH_CLIENT_ID, this.clientConfig.clientId));
			nameValuePairs.add(new BasicNameValuePair(OAuth20.OAUTH_CLIENT_SECRET, this.clientConfig.clientSecret));
			nameValuePairs.add(new BasicNameValuePair(OAuth20.OAUTH_GRANT_TYPE, grantType.name().toLowerCase()));
			post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
			request = post;
			break;
		}

		LOG.trace(request.getRequestLine());

		try {
			Future future = this.httpclient.execute(request, null);
			HttpResponse resp = future.get();
			StatusLine status = resp.getStatusLine();
			HttpEntity entity = resp.getEntity();
			String contentType = entity.getContentType().getValue();
			String content = EntityUtils.toString(entity);
			
			LOG.trace(resp.getStatusLine());
			LOG.trace(contentType);
			LOG.trace(content);
			
			if (status.getStatusCode() == 400) {
				throw new RefreshTokenRevokedException(content);
			} else if (status.getStatusCode() != 200) {
				throw new IOException(status.toString() + ": " + content);
			}
			
			ObjectMapper objectMapper = new ObjectMapper();
			OAuthCredential credential = objectMapper.readValue(content, OAuthCredential.class);
			if (Preconditions.isEmptyString(credential.getRefreshToken())) {
				credential.setRefreshToken(refreshToken);
			}
			return credential;
		} catch (InterruptedException | ExecutionException e) {
			LOG.error(e.getLocalizedMessage(), e);
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see org.isuper.oauth.http.OAuthHttpClient#getJSONResources(java.lang.Class, org.isuper.oauth.OAuthCredential, java.lang.String, java.lang.String[])
	 */
	@Override
	public  T getResource(Class resultClass, OAuthCredential credential, String endpoint, String... params) throws InvalidOAuthCredentialException, IOException {
		return OBJECT_MAPPER.treeToValue(getRawResource(credential, endpoint, params), resultClass);
	}

	/* (non-Javadoc)
	 * @see org.isuper.oauth.http.OAuthHttpClient#getResources(java.lang.Class, org.isuper.oauth.OAuthCredential, java.lang.String, java.lang.String[])
	 */
	@Override
	public  List getResources(Class resultClass, String treeKey, OAuthCredential credential, String endpoint, String... params) throws InvalidOAuthCredentialException, IOException {
		JsonNode node = getRawResource(credential, endpoint, params);
		if (Preconditions.isEmptyString(treeKey)) {
			return OBJECT_MAPPER.readValue(node.traverse(), OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, resultClass));
		}
		node = node.get(treeKey);
		return OBJECT_MAPPER.readValue(node.traverse(), OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, resultClass));
	}

	/* (non-Javadoc)
	 * @see org.isuper.oauth.client.OAuthHttpClient#getRawResource(org.isuper.oauth.OAuthCredential, java.lang.String, java.lang.String[])
	 */
	@Override
	public JsonNode getRawResource(OAuthCredential credential, String endpoint, String... params) throws InvalidOAuthCredentialException, IOException {
		if (Preconditions.isEmptyString(credential.accessToken)) {
			throw new InvalidOAuthCredentialException("Missing access token in credential: " + credential);
		}
		HttpRequestBase request;
		try {
			URIBuilder builder = new URIBuilder(endpoint);
			if (params != null && params.length > 0) {
				String key, value;
				for (int p = 0; p + 1 < params.length; p += 2) {
					key = params[p];
					if (Preconditions.isEmptyString(key)) {
						continue;
					}
					value = params[p + 1];
					builder.addParameter(key, value == null ? "" : value);
				}
			}
			request = new HttpGet(builder.build());
		} catch (URISyntaxException e) {
			throw new RuntimeException(String.format("Invalid token URL: %s", this.clientConfig.tokenUri));
		}
		request.addHeader("Authorization", "Bearer " + credential.accessToken);
		
		LOG.trace(request.getRequestLine());

		try {
			Future future = this.httpclient.execute(request, null);
			HttpResponse resp = future.get();
			StatusLine status = resp.getStatusLine();
			HttpEntity entity = resp.getEntity();
			String contentType = entity.getContentType().getValue();
			String content = EntityUtils.toString(entity);
			
			LOG.trace(resp.getStatusLine());
			LOG.trace(contentType);
			LOG.trace(content);
			
			if (status.getStatusCode() == 401) {
				throw new InvalidOAuthCredentialException("Invalid credential: " + credential);
			} else if (status.getStatusCode() != 200) {
				throw new IOException("Failed response from server: " + status.toString());
			}
			
			return OBJECT_MAPPER.readTree(content);
		} catch (InterruptedException | ExecutionException e) {
			LOG.error(e.getLocalizedMessage(), e);
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see java.io.Closeable#close()
	 */
	@Override
	public void close() throws IOException {
		this.httpclient.close();
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy