diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..d4789c2 --- /dev/null +++ b/build.sbt @@ -0,0 +1,12 @@ +name := "force-rest-api" +organization := "com.frejo" +version := "1.0.1" +scalaVersion := "2.11.8" + +libraryDependencies ++= Seq( + "org.slf4j" % "slf4j-api" % "1.7.9", + "com.fasterxml.jackson.core" % "jackson-core" % "2.9.0", + "com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % "2.9.0" +) + +publishTo := Some("ForceRestApi" at "s3://s3-us-east-1.amazonaws.com/docurated-build/force-rest-api") \ No newline at end of file diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..c091b86 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.16 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..a5b3904 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.frugalmechanic" % "fm-sbt-s3-resolver" % "0.9.0") \ No newline at end of file diff --git a/src/main/java/com/force/api/ForceApi.java b/src/main/java/com/force/api/ForceApi.java index 0e96528..550ea41 100644 --- a/src/main/java/com/force/api/ForceApi.java +++ b/src/main/java/com/force/api/ForceApi.java @@ -12,6 +12,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,22 +43,23 @@ * */ public class ForceApi { + private final static String SFORCE_LIMIT_HEADER = "Sforce-Limit-Info"; + private final static Pattern sforceLimitPattern = Pattern.compile("api-usage=(\\d+)/(\\d+)"); + private static final Logger logger = LoggerFactory.getLogger(ForceApi.class); private final ObjectMapper jsonMapper; - - private static final Logger logger = LoggerFactory.getLogger(ForceApi.class); - final ApiConfig config; - ApiSession session; - private boolean autoRenew = false; + private final boolean autoRenew; + private final AtomicInteger apiCallsUsed = new AtomicInteger(-1); + private final AtomicInteger apiLimit = new AtomicInteger(-1); + + ApiSession session; public ForceApi(ApiConfig config, ApiSession session) { this.config = config; jsonMapper = config.getObjectMapper(); this.session = session; - if(session.getRefreshToken()!=null) { - autoRenew = true; - } + this.autoRenew = session.getRefreshToken() != null; } public ForceApi(ApiSession session) { @@ -86,6 +90,12 @@ public ResourceRepresentation get(String path) { jsonMapper); } + public HttpResponse getResponse(String completePath) { + return apiRequest(new HttpRequest() + .url(session.getApiEndpoint() + completePath) + .method("GET")); + } + /** * sends a custom REST API DELETE request * @@ -416,6 +426,26 @@ public DescribeSObject describeSObject(String sobject) { throw new ResourceException(e); } } + + /** + * This value is initialized to -1 and then is set to a non-negative value + * after the first Limit Info Header is received from a Salesforce HTTP + * response. + * @return + */ + public int getApiCallsUsed() { + return apiCallsUsed.get(); + } + + /** + * This value is initialized to -1 and then is set to a non-negative value + * after the first Limit Info Header is received from a Salesforce HTTP + * response. + * @return + */ + public int getApiLimit() { + return apiLimit.get(); + } private final String uriBase() { return(session.getApiEndpoint()+"/services/data/"+config.getApiVersionString()); @@ -424,7 +454,7 @@ private final String uriBase() { private final HttpResponse apiRequest(HttpRequest req) { req.setAuthorization("Bearer "+session.getAccessToken()); HttpResponse res = Http.send(req); - if(res.getResponseCode()==401) { + if (res.getResponseCode()==401) { // Perform one attempt to auto renew session if possible if (autoRenew) { logger.debug("Session expired. Refreshing session..."); @@ -440,19 +470,34 @@ private final HttpResponse apiRequest(HttpRequest req) { res = Http.send(req); } } - if(res.getResponseCode()>299) { - if(res.getResponseCode()==401) { + if (res.getResponseCode()>299) { + if (res.getResponseCode()==401) { throw new ApiTokenException(res.getString()); } else { throw new ApiException(res.getResponseCode(), res.getString()); } - } else if(req.getExpectedCode()!=-1 && res.getResponseCode()!=req.getExpectedCode()) { + } else if (req.getExpectedCode()!=-1 && res.getResponseCode()!=req.getExpectedCode()) { throw new RuntimeException("Unexpected response from Force API. Got response code "+res.getResponseCode()+ ". Was expecting "+req.getExpectedCode()); } else { + setApiLimitInfo(res.getHeaders()); return res; } } + + private void setApiLimitInfo(Map> headers) { + if (!headers.containsKey(SFORCE_LIMIT_HEADER)) { + return; + } + String header = headers.get(SFORCE_LIMIT_HEADER).get(0); + Matcher matcher = sforceLimitPattern.matcher(header); + if (matcher.find()) { + apiCallsUsed.set(Integer.parseInt(matcher.group(1))); + apiLimit.set(Integer.parseInt(matcher.group(2))); + } else { + logger.warn("Unable to find API limit info in header {}", header); + } + } /** * Normalizes the JSON response in case it contains responses from diff --git a/src/main/java/com/force/api/http/Http.java b/src/main/java/com/force/api/http/Http.java index 0e1293a..9cc8ccb 100644 --- a/src/main/java/com/force/api/http/Http.java +++ b/src/main/java/com/force/api/http/Http.java @@ -91,13 +91,14 @@ public static final HttpResponse send(HttpRequest req) { switch (req.getResponseFormat()) { case BYTE: return new HttpResponse().setByte(readResponse(conn.getInputStream())) - .setResponseCode(code); + .setResponseCode(code).setHeaders(conn.getHeaderFields()); case STRING: return new HttpResponse().setString( new String(readResponse(conn.getInputStream()), "UTF-8")).setResponseCode( - code); + code).setHeaders(conn.getHeaderFields()); default: - return new HttpResponse().setStream(conn.getInputStream()).setResponseCode(code); + return new HttpResponse().setStream(conn.getInputStream()).setResponseCode(code) + .setHeaders(conn.getHeaderFields()); } } else { logger.info("Bad response code: {} on request: {}", code, req); diff --git a/src/main/java/com/force/api/http/HttpResponse.java b/src/main/java/com/force/api/http/HttpResponse.java index cf01f7f..42b0445 100644 --- a/src/main/java/com/force/api/http/HttpResponse.java +++ b/src/main/java/com/force/api/http/HttpResponse.java @@ -1,6 +1,8 @@ package com.force.api.http; import java.io.InputStream; +import java.util.List; +import java.util.Map; public class HttpResponse { @@ -8,6 +10,7 @@ public class HttpResponse { private byte[] byteResponse; private InputStream streamResponse; private int responseCode; + private Map> headers; public int getResponseCode() { return responseCode; @@ -21,6 +24,7 @@ public byte[] getByte() { public InputStream getStream() { return streamResponse; } + public Map> getHeaders() { return headers; } public HttpResponse setString(String stringResponse) { this.stringResponse = stringResponse; return this; @@ -38,8 +42,9 @@ public HttpResponse setResponseCode(int value) { responseCode = value; return this; } - - - + public HttpResponse setHeaders(Map> headers) { + this.headers = headers; + return this; + } }