/**
* Copyright 2014 Global Crop Diversity Trust
*
* 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.genesys2.client.oauth;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.genesys2.client.oauth.api.GenesysApi;
import org.scribe.builder.ServiceBuilder;
import org.scribe.exceptions.OAuthConnectionException;
import org.scribe.exceptions.OAuthException;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class GenesysClient {
private static final Logger _log = LogManager.getLogger(GenesysClient.class);
public static final Token EMPTY_TOKEN = null;
private static final String SCOPE = "read,write";
private static ObjectMapper mapper = new ObjectMapper();
private OAuthService service;
private Token accessToken;
private Token refreshToken;
private GenesysApi genesysApi;
private String apiKey;
private String apiSecret;
public GenesysClient() {
}
public GenesysClient setBaseUrl(String baseUrl) {
genesysApi.setBaseUrl(baseUrl);
return this;
}
public void setGenesysApi(GenesysApi genesysApi) {
this.genesysApi = genesysApi;
}
public GenesysApi getGenesysApi() {
return genesysApi;
}
/**
* Load client configuration from {@link Properties}
*
* @param properties
*/
public void loadProperties(Properties properties) {
final String baseUrl = properties.getProperty("base.url");
genesysApi.setBaseUrl(baseUrl);
final String httpAuth = properties.getProperty("http.auth");
if (StringUtils.isNotBlank(httpAuth) && httpAuth.contains(":")) {
_log.warn("Using HTTP AUTH " + httpAuth);
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(httpAuth.split(":", 2)[0], httpAuth.split(":", 2)[1].toCharArray());
}
});
}
setAccessToken(properties.getProperty("access.token"));
setRefreshToken(properties.getProperty("refresh.token"));
// CropHub auth service
connect(properties.getProperty("client.key"), properties.getProperty("client.secret"), properties.getProperty("client.callback"));
}
public void connect(String clientId, String clientSecret, String callback) {
this.apiKey = clientId;
this.apiSecret = clientSecret;
this.service = new ServiceBuilder().provider(this.genesysApi).apiKey(clientId).apiSecret(clientSecret).callback(callback).scope(SCOPE).build();
}
public GenesysClient setAccessToken(String tokenKey) {
accessToken = new Token(tokenKey, "");
return this;
}
public GenesysClient setRefreshToken(String tokenKey) {
refreshToken = new Token(tokenKey, "");
return this;
}
public String query(String url) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.GET, url, null, null);
}
public String query(Verb method, String url, Map<String, String> queryString, String postBody) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
if (accessToken == null) {
refreshAccessToken();
}
for (int i = 0; i < 2; i++) {
OAuthRequest request = new OAuthRequest(method, getApiUrl(url));
if (queryString != null && queryString.size() > 0) {
for (String key : queryString.keySet()) {
request.addQuerystringParameter(key, queryString.get(key));
}
}
if (_log.isDebugEnabled()) {
_log.debug(method + " " + request.getCompleteUrl());
}
if (postBody != null) {
// System.err.println("Adding data: " + data);
request.addPayload(postBody);
request.addHeader("Content-Type", "application/json;charset=utf-8");
}
service.signRequest(accessToken, request);
request.setConnectionKeepAlive(true);
request.setConnectTimeout(10, TimeUnit.SECONDS);
request.setReadTimeout(30, TimeUnit.SECONDS);
request.setCharset("UTF-8");
Response response = null;
try {
response = request.send();
} catch (OAuthConnectionException e) {
throw e;
}
String responseBody = response.getBody();
if (response.isSuccessful()) {
return responseBody + "\n";
} else {
if (response.getCode() == 401) {
_log.warn("Response error: " + response.getCode());
System.err.println(responseBody);
if (i == 0) {
refreshAccessToken();
} else {
throw new OAuthAuthenticationException("Unauthorized");
}
} else {
_log.error(method + " " + request.getCompleteUrl());
_log.error(postBody);
_log.error("HTTP response code: " + response.getCode());
_log.error("Response: " + responseBody);
if (responseBody.contains("Deadlock found when trying to get lock; try restarting transaction")
|| responseBody.contains("nested exception is org.hibernate.exception.LockAcquisitionException: could not execute statement")) {
throw new PleaseRetryException(responseBody);
} else
throw new GenesysApiException("Unexpected error: " + responseBody);
}
}
}
return null;
}
private String getApiUrl(String url) {
return genesysApi.getBaseUrl().concat("/api/v0").concat(url);
}
/**
* Refresh accessToken with refreshToken
*
* @throws OAuthAuthenticationException
*/
public void refreshAccessToken() throws OAuthAuthenticationException {
if (this.refreshToken != null) {
_log.info("Using Refresh Token to get new access token");
try {
accessToken = genesysApi.getAccessToken(this.apiKey, this.apiSecret, this.refreshToken);
_log.info("Got new Access Token!");
} catch (OAuthException e) {
_log.info("Refresh token didn't work: " + e.getMessage());
throw new OAuthAuthenticationException("Refresh token not valid, please re-authenticate");
}
} else {
throw new OAuthAuthenticationException("No refresh token, please re-authenticate");
}
}
public String accessionExists(String instCode, String acceNumb, String genus) throws GenesysApiException {
try {
HashMap<String, String> queryString = new HashMap<String, String>();
queryString.put("acceNumb", acceNumb);
return query(Verb.GET, new URI(null, null, "/acn/exists/" + instCode + "/" + genus, null).toString(), queryString, null);
} catch (URISyntaxException e) {
e.printStackTrace();
return null;
}
}
public static ObjectNode makeAid3(String instCode, String genus, String acceNumb) {
ObjectNode json = mapper.createObjectNode();
json.put("instCode", instCode);
json.put("acceNumb", acceNumb);
json.put("genus", genus);
return json;
}
public String updateMLS(String instCode, Collection<ObjectNode> accns) throws GenesysApiException {
ArrayNode arr = mapper.createArrayNode();
for (ObjectNode accn : accns) {
arr.add(accn);
}
_log.debug("Sending: " + arr);
return query(Verb.PUT, "/acn/" + instCode + "/update", null, arr.toString());
}
public String accessionExists(String instCode, Collection<ObjectNode> accns) throws GenesysApiException {
ArrayNode arr = mapper.createArrayNode();
for (ObjectNode accn : accns) {
arr.add(accn);
}
_log.debug("Sending: " + arr);
return query(Verb.PUT, "/acn/" + instCode + "/check", null, arr.toString());
}
public String updateAccessions(String instCode, Collection<ObjectNode> accns) throws GenesysApiException, InterruptedException {
if (accns == null || accns.size() == 0) {
return null;
}
ArrayNode arr = mapper.createArrayNode();
for (ObjectNode accn : accns) {
arr.add(accn);
}
return updateAccessions(instCode, arr);
}
public String updateAccessions(String instCode, ArrayNode arr) throws OAuthAuthenticationException, GenesysApiException, InterruptedException {
_log.debug("Sending: " + arr);
for (int retry = 0; retry < 5; retry++) {
try {
return query(Verb.PUT, "/acn/" + instCode + "/upsert", null, arr.toString());
} catch (PleaseRetryException e) {
long sleepTime = (long) (Math.pow(2, retry) * 100 + Math.pow(2, retry) * 2500 * Math.random());
_log.warn("Retrying PUT after " + sleepTime + " ms.");
Thread.sleep(sleepTime);
}
}
throw new RuntimeException("All retries failed");
}
public String updateOrganizationMembers(String organizationSlug, ArrayNode institutes) throws GenesysApiException {
_log.debug("Sending: " + institutes);
try {
return query(Verb.PUT, "/org/" + organizationSlug + "/set-institutes", null, institutes.toString());
} catch (PleaseRetryException e) {
_log.warn("Retrying PUT after some time...");
try {
Thread.sleep((long) (1000 * Math.random()));
} catch (InterruptedException e1) {
e1.printStackTrace();
}
return query(Verb.PUT, "/org/" + organizationSlug + "/set-institutes", null, institutes.toString());
}
}
public String updateAccessionNames(String instCode, Collection<ObjectNode> batch) throws GenesysApiException {
_log.debug("Sending: " + batch);
try {
return query(Verb.PUT, "/acn/" + instCode + "/names", null, batch.toString());
} catch (PleaseRetryException e) {
_log.warn("Retrying PUT after some time...");
try {
Thread.sleep((long) (1000 * Math.random()));
} catch (InterruptedException e1) {
e1.printStackTrace();
}
_log.warn("Retrying PUT");
return query(Verb.PUT, "/acn/" + instCode + "/names", null, batch.toString());
}
}
public String deleteAccessions(String instCode, ArrayNode array) throws GenesysApiException {
return query(Verb.PUT, "/acn/" + instCode + "/delete", null, array.toString());
}
public String deleteAccession(String instCode, ArrayNode ids) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.POST, "/acn/" + instCode + "/delete", null, ids.toString());
}
public String getAuthorizationUrl(Token accessToken) {
return this.service.getAuthorizationUrl(accessToken);
}
/**
* Obtain access and refresh tokens with verifier code
*
* @param verifierCode
*/
public void authenticate(String verifierCode) {
Verifier verifier = new Verifier(verifierCode);
accessToken = service.getAccessToken(GenesysClient.EMPTY_TOKEN, verifier);
_log.info("ACCESS TOKEN: " + accessToken.getToken() + " sec=" + accessToken.getSecret() + " raw=" + accessToken.getRawResponse());
refreshToken = genesysApi.getRefreshToken(accessToken);
_log.info("REFRESH TOKEN: " + refreshToken.getToken() + " sec=" + refreshToken.getSecret() + " raw=" + refreshToken.getRawResponse());
}
public Token getAccessToken() {
return accessToken;
}
public Token getRefreshToken() {
return refreshToken;
}
public String me() throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query("/me");
}
public String getCrop(String shortName) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
if (!shortName.matches("^\\w+$")) {
throw new GenesysApiException("Crop shortname can only contain characters");
}
return query("/crops/" + shortName);
}
public String listParameters() throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query("/kpi/parameter/list");
}
public String putParameter(ObjectNode node) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.POST, "/kpi/parameter", null, node.toString());
}
public String getParameter(String name) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query("/kpi/parameter/" + name);
}
public String listDimensions() throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query("/kpi/dimension/list");
}
public String getDimension(long id) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query("/kpi/dimension/" + id);
}
public String putDimension(ObjectNode node) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.POST, "/kpi/dimension", null, node.toString());
}
public String listExecutions() throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query("/kpi/execution/list");
}
public String getExecution(String name) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query("/kpi/execution/" + name);
}
public String putExecution(ObjectNode node) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.POST, "/kpi/execution", null, node.toString());
}
public String kpiExecute(String name) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.POST, "/kpi/execution/" + name + "/execute", null, null);
}
public String deleteDimension(long id) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.DELETE, "/kpi/dimension/" + id, null, null);
}
public String deleteExecution(String name) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.DELETE, "/kpi/execution/" + name, null, null);
}
public String deleteParameter(String name) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.DELETE, "/kpi/parameter/" + name, null, null);
}
public String listCrops() throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query("/crops");
}
public String putCrop(ObjectNode node) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.POST, "/crops", null, node.toString());
}
public String deleteCrop(String shortName) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.DELETE, "/crops/" + shortName, null, null);
}
public String getCropRules(String shortName) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query("/crops/" + shortName + "/rules");
}
public String putCropRules(String shortName, ArrayNode currentCropRules) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.PUT, "/crops/" + shortName + "/rules", null, currentCropRules.toString());
}
public String rebuildCropTaxa() throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.POST, "/crops/rebuild", null, null);
}
public String rebuildCropTaxa(String shortName) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.POST, "/crops/" + shortName + "/rebuild", null, null);
}
public String listOrganizations(int page) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
Map<String, String> qs = new HashMap<String, String>();
qs.put("page", String.valueOf(page));
return query(Verb.GET, "/org", qs, null);
}
public String getOrganization(String slug) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query("/org/" + slug);
}
public String updateOrganization(ObjectNode org) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.POST, "/org", null, org.toString());
}
public String deleteOrganization(String slug) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.DELETE, "/org/" + slug, null, null);
}
public String getOrganizationMembers(String slug) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query("/org/" + slug + "/institutes");
}
public String putOrganizationMembers(String slug, ArrayNode currentMembers) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.PUT, "/org/" + slug + "/set-institutes", null, currentMembers.toString());
}
public String getOrganizationBlurp(String slug, String language) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query("/org/" + slug + "/blurp/" + language);
}
public String updateOrganizationBlurp(String slug, ObjectNode blurp) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
return query(Verb.PUT, "/org/" + slug + "/blurp", null, blurp.toString());
}
public String listObservations(String executionName, String dimensionFilter, int page) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
Map<String, String> qs = new HashMap<String, String>();
qs.put("page", String.valueOf(page));
return query(Verb.POST, "/kpi/observation/" + executionName + "/", qs, StringUtils.defaultIfBlank(dimensionFilter, ""));
}
public String listAccessions(String instCode, int page, String query) throws OAuthAuthenticationException, PleaseRetryException, GenesysApiException {
Map<String, String> params = new HashMap<String, String>();
params.put("page", String.valueOf(page));
params.put("query", query);
return query(Verb.GET, "/acn/" + instCode + "/list", params, null);
}
}