package com.asana;
import com.asana.dispatcher.AccessTokenDispatcher;
import com.asana.dispatcher.BasicAuthDispatcher;
import com.asana.dispatcher.Dispatcher;
import com.asana.dispatcher.OAuthDispatcher;
import com.asana.errors.AsanaError;
import com.asana.errors.RateLimitEnforcedError;
import com.asana.errors.RetryableAsanaError;
import com.asana.requests.Request;
import com.asana.resources.*;
import com.google.api.client.http.*;
import com.google.common.base.Joiner;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Main Asana client class
*/
public class Client {
/**
* Number of milliseconds to delay before retrying
*/
public static double RETRY_DELAY = 1000.0;
/**
* Back off retrying by this factor (e.x. 2 would result in doubling of delay each retry)
*/
public static double RETRY_BACKOFF = 2.0;
/**
* Map of default options, from string key to primitive value types (string, numeric, boolean)
*/
public static final Map<String, Object> DEFAULTS = new HashMap<String, Object>() {{
put("base_url", "https://app.asana.com/api/1.0");
put("item_limit", -1);
put("page_size", 50);
put("poll_interval", 5);
put("max_retries", 5);
}};
public HashMap<String, Object> options;
public Dispatcher dispatcher;
public Attachments attachments;
public Events events;
public Projects projects;
public Stories stories;
public Tags tags;
public Tasks tasks;
public Teams teams;
public Users users;
public Webhooks webhooks;
public Workspaces workspaces;
private static final String[] QUERY_OPTIONS = new String[]{"limit", "offset", "sync"};
private static final String[] API_OPTIONS = new String[]{"pretty", "fields", "expand"};
private static final String CLIENT_VERSION_HEADER_NAME = "X-Asana-Client-Lib";
/**
* @param dispatcher Dispatcher to handle authentication
*/
public Client(Dispatcher dispatcher) {
this(dispatcher, null);
}
/**
* @param dispatcher Dispatcher to handle authentication
* @param options Map of client options, overrides Client.DEFAULTS, overridden by request options
*/
public Client(Dispatcher dispatcher, Map<String, Object> options) {
this.dispatcher = dispatcher;
this.options = new HashMap<String, Object>();
this.options.putAll(DEFAULTS);
if (options != null) {
this.options.putAll(options);
}
this.attachments = new Attachments(this);
this.events = new Events(this);
this.projects = new Projects(this);
this.stories = new Stories(this);
this.tags = new Tags(this);
this.tasks = new Tasks(this);
this.teams = new Teams(this);
this.users = new Users(this);
this.webhooks = new Webhooks(this);
this.workspaces = new Workspaces(this);
}
/**
* @param request Asana client request object
* @return Raw HttpResponse object
* @throws IOException
*/
public HttpResponse request(Request request) throws IOException {
GenericUrl url = new GenericUrl(request.options.get("base_url") + request.path);
HttpContent content = null;
Map<String, Object> body = new HashMap<String, Object>();
// API options
if (request.method.equals("GET")) {
for (String key : API_OPTIONS) {
if (request.options.containsKey(key) && !request.query.containsKey("opt_" + key)) {
request.query.put("opt_" + key, request.options.get(key));
}
}
for (String key : QUERY_OPTIONS) {
if (request.options.containsKey(key) && !request.query.containsKey(key)) {
request.query.put(key, request.options.get(key));
}
}
} else if (request.method.equals("POST") || request.method.equals("PUT")) {
Map<String, Object> opts = new HashMap<String, Object>();
for (String key : API_OPTIONS) {
if (request.options.containsKey(key)) {
opts.put(key, request.options.get(key));
}
}
if (opts.size() > 0) {
body.put("options", opts);
}
}
// Query string
for (Map.Entry<String, Object> entry : request.query.entrySet()) {
Object value = entry.getValue();
if (value == null || value == "") {
continue;
}
if (value instanceof List) {
value = Joiner.on(",").join((List) value);
}
url.put(entry.getKey(), value);
}
if (request.content != null) {
// Multipart, etc body
content = request.content;
} else if (request.method.equals("POST") || request.method.equals("PUT")) {
// JSON body
body.put("data", request.data);
String json = Json.getInstance().toJson(body);
content = new ByteArrayContent("application/json", json.getBytes("UTF-8"));
}
int retryCount = 0;
int maxRetries = (Integer) request.options.get("max_retries");
while (true) {
try {
HttpRequest httpRequest = this.dispatcher.buildRequest(request.method, url, content);
httpRequest.getHeaders().set(CLIENT_VERSION_HEADER_NAME, versionHeader());
try {
return httpRequest.execute();
} catch (HttpResponseException e) {
throw AsanaError.mapException(e);
}
} catch (RetryableAsanaError e) {
if (retryCount < maxRetries) {
this.handleRetryableError(e, retryCount);
retryCount++;
} else {
throw e;
}
}
}
}
/**
* @param error Error thrown by Asana request
* @param retryCount Number of times this request has been retried
*/
private void handleRetryableError(RetryableAsanaError error, int retryCount) {
if (error instanceof RateLimitEnforcedError) {
this.dispatcher.sleep(((RateLimitEnforcedError) error).retryAfter);
} else {
this.dispatcher.sleep((long) (RETRY_DELAY * Math.pow(RETRY_BACKOFF, retryCount)));
}
}
private String clientVersion() {
String version = getClass().getPackage().getImplementationVersion();
// This will be null if the class is executed from outside its packaged
// jar, such as during tests.
return (version != null) ? version : "0.0.0";
}
private String versionHeader() {
StringBuilder builder = new StringBuilder();
HashMap<String, String> values = new HashMap<String, String>();
values.put("version", clientVersion());
values.put("language", "Java");
values.put("language_version", System.getProperty("java.version"));
values.put("os", System.getProperty("os.name"));
values.put("os_version", System.getProperty("os.version"));
for (Map.Entry<String, String> entry : values.entrySet()) {
//some of the values above aren't defined on all platforms; URLEncoder chokes on null
if (entry.getValue() != null) {
builder.append("&");
builder.append(URLEncoder.encode(entry.getKey()));
builder.append("=");
builder.append(URLEncoder.encode(entry.getValue()));
}
}
return builder.toString().substring(1);
}
/**
* @param apiKey Basic Auth API key
* @return Client instance
*/
public static Client basicAuth(String apiKey) {
return new Client(new BasicAuthDispatcher(apiKey));
}
/**
* @param apiKey Basic Auth API key
* @param httpTransport HttpTransport implementation to use for requests
* @return Client instance
*/
public static Client basicAuth(String apiKey, HttpTransport httpTransport) {
return new Client(new BasicAuthDispatcher(apiKey, httpTransport));
}
/**
* @param accessToken Personal Access Token
* @return Client instance
*/
public static Client accessToken(String accessToken) {
return new Client(new AccessTokenDispatcher(accessToken));
}
/**
* @param accessToken Personal Access Token
* @param httpTransport HttpTransort implementation to use for requests
* @return Client instance
*/
public static Client accessToken(String accessToken, HttpTransport httpTransport) {
return new Client(new AccessTokenDispatcher(accessToken, httpTransport));
}
/**
* @param app OAuth application instance
* @return Client instance
*/
public static Client oauth(OAuthApp app) {
return new Client(new OAuthDispatcher(app));
}
/**
* @param app OAuth application instance
* @param httpTransport HttpTransport implementation to use for requests
* @return Client instance
*/
public static Client oauth(OAuthApp app, HttpTransport httpTransport) {
return new Client(new OAuthDispatcher(app, httpTransport));
}
}