diff --git a/pom.xml b/pom.xml
index 2f1583e..28822c7 100755
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.coscale.sdk-java
coscale-sdk-java
- 1.1.0
+ 1.2-beta-2
jar
CoScale SDK
Java SDK for integrating apps with CoScale Web Performance Monitoring platform.
@@ -144,5 +144,10 @@
jsr305
3.0.0
+
+ commons-codec
+ commons-codec
+ 1.10
+
diff --git a/src/main/java/com/coscale/sdk/client/ApiClient.java b/src/main/java/com/coscale/sdk/client/ApiClient.java
index a6fd273..00db4c1 100755
--- a/src/main/java/com/coscale/sdk/client/ApiClient.java
+++ b/src/main/java/com/coscale/sdk/client/ApiClient.java
@@ -1,12 +1,14 @@
package com.coscale.sdk.client;
import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import com.coscale.sdk.client.commons.Options;
+import com.coscale.sdk.client.data.BinaryData;
import com.coscale.sdk.client.exceptions.CoscaleApiException;
import com.coscale.sdk.client.utils.MapperSupport;
import com.fasterxml.jackson.core.JsonGenerationException;
@@ -66,6 +68,18 @@ public class ApiClient {
/** Json deserialization error counter. */
private int jsonDeserializationExceptions;
+ /** Field name in form upload for binary data. */
+ private String attachmentName = "bData";
+
+ /** CRLF. */
+ private String crlf = "\r\n";
+
+ /** Two hypens. */
+ private String twoHyphens = "--";
+
+ /** Boundary for binary file upload form. */
+ private String boundary = "*****";
+
/**
* ApiClient constructor.
* @param appId The CoScale Application id.
@@ -211,7 +225,7 @@ public int getDeserializationExceptions() {
* used by request.
* @param uri
* of the API call.
- * @param data
+ * @param payload
* in string format to pass to the request.
* @return String response for the request.
* @throws IOException
@@ -301,7 +315,7 @@ private void login() throws IOException {
: getGlobalRequestURL("/users/login/");
Credentials.TokenHelper data = call("POST", uri, credentials,
new TypeReference() {
- }, false);
+ }, false, false);
token = data.token;
}
@@ -336,7 +350,53 @@ public T callWithAuth(String method, String endpoint, Object obj, TypeRefere
login();
}
try {
- return call(method, getAppRequestURL(endpoint), obj, valueType, true);
+ return call(method, getAppRequestURL(endpoint), obj, valueType, true, false);
+ } catch (CoscaleApiException e) {
+ if (e.statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ this.token = null; // will trigger new login
+ } else {
+ throw e;
+ }
+ }
+
+ tries++;
+ } while (tries <= AUTH_RETRIES);
+ throw new CoscaleApiException(responseCode, "Authentication failed.");
+ }
+
+ /**
+ * callWithAuthBinary is used to make requests that require Authentication on
+ * CoScale API.
+ *
+ * @param method
+ * request HTTP method.
+ * @param endpoint
+ * The url for the request.
+ * @param data
+ * object with data for the request. This parameter can be null.
+ * @param valueType
+ * is the type expected.
+ * @param binary
+ * is the action a binary upload.
+ * @return The Object received as a result for the request.
+ * @throws IOException
+ */
+ public T callWithAuthBinary(String method, String endpoint, BinaryData data, TypeReference valueType,
+ boolean binary) throws IOException {
+ // Not authenticated yet, try login.
+ if (this.token == null) {
+ login();
+ }
+
+ // Do the actual request.
+ int tries = 0;
+ int responseCode = 0;
+ do {
+ if (this.token == null) {
+ login();
+ }
+ try {
+ return call(method, getAppRequestURL(endpoint), data, valueType, true, binary);
} catch (CoscaleApiException e) {
if (e.statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
this.token = null; // will trigger new login
@@ -365,13 +425,18 @@ public T callWithAuth(String method, String endpoint, Object obj, TypeRefere
* @throws IOException
*/
public T call(String method, String url, Object obj, TypeReference valueType,
- boolean auth) throws IOException {
+ boolean auth, boolean binary) throws IOException {
try {
-
- String res = this.doHttpRequest(method, url, objectToJson(obj), auth);
+ String res;
+ if (!binary) {
+ res = this.doHttpRequest(method, url, objectToJson(obj), auth);
+ } else {
+ res = this.doHttpRequestBinary(method, url, (BinaryData) obj, auth);
+ }
if (res.length() == 0) {
return null;
}
+
return MapperSupport.getInstance().readValue(res, valueType);
} catch (JsonMappingException | JsonParseException e) {
jsonDeserializationExceptions++;
@@ -381,6 +446,102 @@ public T call(String method, String url, Object obj, TypeReference valueT
}
}
+ /**
+ * Do an HTTP request with a binary upload in it.
+ * @param method The method
+ * @param uri The url
+ * @param payload The object with the data
+ * @param authenticate The authentication token.
+ * @return String response for the request.
+ * @throws IOException
+ */
+ public String doHttpRequestBinary(String method, String uri, BinaryData payload, boolean authenticate) throws IOException {
+ URL url;
+ HttpURLConnection conn = null;
+ int responseCode = -1;
+
+ try {
+ url = new URL(uri);
+ conn = (HttpURLConnection) url.openConnection();
+
+ // Set connection timeout.
+ conn.setConnectTimeout(this.apiConnTimeoutMS);
+ conn.setReadTimeout(this.apiReadTimeoutMS);
+
+ // Setup the connection.
+ conn.setDoOutput(true);
+ conn.setInstanceFollowRedirects(false);
+ conn.setRequestMethod(method);
+ conn.setRequestProperty("Accept", "application/json");
+
+ conn.setRequestProperty("Connection", "Keep-Alive");
+ conn.setRequestProperty("Cache-Control", "no-cache");
+
+ conn.setUseCaches(false);
+
+ // add request headers.
+ conn.setRequestProperty("User-Agent", this.userAgent);
+
+ if (authenticate) {
+ conn.setRequestProperty(AUTH_HEADER, this.token);
+ }
+
+ if (payload != null && ("POST".equals(method) || "PUT".equals(method))) {
+ conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + this.boundary);
+ conn.setRequestProperty("Content-Length", Integer.toString(payload.bData.length).trim());
+ conn.setRequestProperty("Content-Transfer-Encoding", "binary");
+ conn.setRequestProperty("Content-Disposition", "form-data; name=\"bData\";");
+ }
+
+ if (payload != null) {
+ DataOutputStream request = new DataOutputStream(conn.getOutputStream());
+
+ request.writeBytes(this.twoHyphens + this.boundary + this.crlf);
+ request.writeBytes("Content-Disposition: form-data; name=\"" +
+ this.attachmentName + "\";filename=\"" +
+ payload.filename + "\"" + this.crlf);
+ request.writeBytes(this.crlf);
+
+ request.write(payload.bData);
+
+ request.writeBytes(this.crlf);
+ request.writeBytes(this.twoHyphens + this.boundary +
+ this.twoHyphens + this.crlf);
+
+ request.flush();
+ request.close();
+ }
+
+ // Check the response code.
+ responseCode = conn.getResponseCode();
+ if (responseCode != HttpURLConnection.HTTP_OK) {
+ String errorMessage = convertStreamToString(conn.getErrorStream());
+
+ throw new CoscaleApiException(responseCode, "Failed : HTTP error code : "
+ + responseCode + " msg: " + conn.getResponseMessage() + " url: "
+ + conn.getURL() + " method " + conn.getRequestMethod() + " error message "
+ + errorMessage);
+ }
+
+ return convertStreamToString(conn.getInputStream());
+ } catch (IOException e) {
+ if (conn != null) {
+ // return also the response from the API
+ String errorMessage = convertStreamToString(conn.getErrorStream());
+ String message = e.getMessage();
+ if (errorMessage.length() > 0) {
+ message += " error " + errorMessage;
+ }
+ throw new CoscaleApiException(responseCode, message, e);
+ }
+ throw e;
+ } finally {
+ if (conn != null) {
+ conn.disconnect();
+ }
+ }
+ }
+
/**
* getAppRequestURL will construct the URL for a request using the end point
* provided. This method will be used to construct the URL for a specific
diff --git a/src/main/java/com/coscale/sdk/client/data/BinaryData.java b/src/main/java/com/coscale/sdk/client/data/BinaryData.java
new file mode 100644
index 0000000..ea6e29c
--- /dev/null
+++ b/src/main/java/com/coscale/sdk/client/data/BinaryData.java
@@ -0,0 +1,52 @@
+package com.coscale.sdk.client.data;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+import java.util.Arrays;
+
+import org.apache.commons.codec.binary.Base64;
+
+/**
+ * For sending binary data to an existing event.
+ * @author kdegroot
+ */
+public class BinaryData {
+
+ /** The binary data. */
+ public byte[] bData;
+
+ /** The provided filename. */
+ public String filename;
+
+ public BinaryData(byte[] bData, String filename) {
+ this.bData = bData;
+ this.filename = filename;
+ }
+
+ public BinaryData() {
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("bData", new String(Base64.decodeBase64(bData))).add("filename", filename).toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final BinaryData other = (BinaryData) obj;
+
+ return (Arrays.equals(this.bData, other.bData) && this.filename.equals(other.filename));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(bData, filename);
+ }
+}
diff --git a/src/main/java/com/coscale/sdk/client/data/DataApi.java b/src/main/java/com/coscale/sdk/client/data/DataApi.java
index b3d07af..23e04c6 100755
--- a/src/main/java/com/coscale/sdk/client/data/DataApi.java
+++ b/src/main/java/com/coscale/sdk/client/data/DataApi.java
@@ -8,7 +8,7 @@
/**
* CoScale API client used to insert data.
- *
+ *
* @author cristi
*
*/
@@ -19,7 +19,7 @@ public class DataApi {
/**
* DataApi contructor.
- *
+ *
* @param api
* ApiClient.
*/
@@ -29,7 +29,7 @@ public DataApi(ApiClient api) {
/**
* Insert data into the data-store
- *
+ *
* @param data
* @return Msg containing the api response.
* @throws IOException
diff --git a/src/main/java/com/coscale/sdk/client/events/EventsApi.java b/src/main/java/com/coscale/sdk/client/events/EventsApi.java
index a8b58f4..d273194 100755
--- a/src/main/java/com/coscale/sdk/client/events/EventsApi.java
+++ b/src/main/java/com/coscale/sdk/client/events/EventsApi.java
@@ -8,6 +8,7 @@
import com.coscale.sdk.client.ApiClient;
import com.coscale.sdk.client.commons.Msg;
import com.coscale.sdk.client.commons.Options;
+import com.coscale.sdk.client.data.BinaryData;
import com.fasterxml.jackson.core.type.TypeReference;
/**
@@ -179,4 +180,17 @@ public Msg deleteData(Long eventId, Long dataId) throws IOException {
});
}
+ /**
+ * Update an existing event with binary data.
+ * @param eventId The event id.
+ * @param dataId The event data id.
+ * @param data The data to upload.
+ * @return Msg response message.
+ * @throws IOException
+ */
+ public Msg uploadBinary(Long eventId, Long dataId, BinaryData data) throws IOException {
+ return api.callWithAuthBinary("PUT", "/events/" + eventId + "/data/" + dataId + "/binary/", data,
+ new TypeReference() {
+ }, true);
+ }
}