From 162ef6c880ab17c0ab74ad56e5f4b6fc1b876729 Mon Sep 17 00:00:00 2001 From: Koen De Groote Date: Mon, 18 Jul 2016 17:02:21 +0200 Subject: [PATCH 1/5] Added support for updating event data instances with binary data. --- pom.xml | 5 + .../com/coscale/sdk/client/ApiClient.java | 176 +++++++++++++++++- .../coscale/sdk/client/data/BinaryData.java | 48 +++++ .../com/coscale/sdk/client/data/DataApi.java | 6 +- .../coscale/sdk/client/events/EventsApi.java | 14 ++ 5 files changed, 240 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/coscale/sdk/client/data/BinaryData.java diff --git a/pom.xml b/pom.xml index 2f1583e..16c961f 100755 --- a/pom.xml +++ b/pom.xml @@ -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 257f325..9c40bd0 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; @@ -61,6 +63,21 @@ public class ApiClient { /** Json deserialization error counter. */ private int jsonDeserializationExceptions; + /** Field name in form upload for binary data. */ + private String attachmentName = "bData"; + + /** Filename. */ + private String attachmentFileName = "upload"; + + /** CRLF. */ + private String crlf = "\r\n"; + + /** Two hypens. */ + private String twoHyphens = "--"; + + /** Boundary for binary file upload form. */ + private String boundary = "*****"; + /** * ApiClient constructor. * @@ -199,7 +216,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 @@ -289,7 +306,7 @@ private void login() throws IOException { : getGlobalRequestURL("/users/login/"); Credentials.TokenHelper data = call("POST", uri, credentials, new TypeReference() { - }, false); + }, false, false); token = data.token; } @@ -324,7 +341,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 @@ -353,13 +416,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++; @@ -369,6 +437,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(API_CONN_TIMEOUT_MS); + conn.setReadTimeout(API_READ_TIMEOUT_MS); + + // 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=\"" + + this.attachmentFileName + "\"" + 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..ec7ed3e --- /dev/null +++ b/src/main/java/com/coscale/sdk/client/data/BinaryData.java @@ -0,0 +1,48 @@ +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; + + public BinaryData(byte[] bData) { + this.bData = bData; + } + + public BinaryData() { + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("bData", new String(Base64.decodeBase64(bData))).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); + } + + @Override + public int hashCode() { + return Objects.hashCode(bData); + } +} 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); + } } From cf16408852715112f1b6c6650d65046f91c78a7d Mon Sep 17 00:00:00 2001 From: fryckbos Date: Wed, 27 Jul 2016 15:25:26 +0200 Subject: [PATCH 2/5] Updated version number. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 16c961f..6f560d4 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.coscale.sdk-java coscale-sdk-java - 1.1.0 + 1.2-beta-1 jar CoScale SDK Java SDK for integrating apps with CoScale Web Performance Monitoring platform. From a306d886d740a189f8e51ff7a82e9d34c92bc162 Mon Sep 17 00:00:00 2001 From: fryckbos Date: Wed, 27 Jul 2016 15:41:33 +0200 Subject: [PATCH 3/5] Fixed compilation issue. --- src/main/java/com/coscale/sdk/client/ApiClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/coscale/sdk/client/ApiClient.java b/src/main/java/com/coscale/sdk/client/ApiClient.java index 1c7c6f2..3d0a4ac 100755 --- a/src/main/java/com/coscale/sdk/client/ApiClient.java +++ b/src/main/java/com/coscale/sdk/client/ApiClient.java @@ -468,8 +468,8 @@ public String doHttpRequestBinary(String method, String uri, BinaryData payload, conn = (HttpURLConnection) url.openConnection(); // Set connection timeout. - conn.setConnectTimeout(API_CONN_TIMEOUT_MS); - conn.setReadTimeout(API_READ_TIMEOUT_MS); + conn.setConnectTimeout(this.apiConnTimeoutMS); + conn.setReadTimeout(this.apiReadTimeoutMS); // Setup the connection. conn.setDoOutput(true); From 7e2c5ec476b2878dd96331c497c7b792d88a0e0a Mon Sep 17 00:00:00 2001 From: Koen De Groote Date: Mon, 1 Aug 2016 12:35:03 +0200 Subject: [PATCH 4/5] Added support for filename on binary upload. Filename is mandatory and cannot be an empty string. --- src/main/java/com/coscale/sdk/client/ApiClient.java | 7 ++----- .../java/com/coscale/sdk/client/data/BinaryData.java | 12 ++++++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/coscale/sdk/client/ApiClient.java b/src/main/java/com/coscale/sdk/client/ApiClient.java index 3d0a4ac..00db4c1 100755 --- a/src/main/java/com/coscale/sdk/client/ApiClient.java +++ b/src/main/java/com/coscale/sdk/client/ApiClient.java @@ -71,9 +71,6 @@ public class ApiClient { /** Field name in form upload for binary data. */ private String attachmentName = "bData"; - /** Filename. */ - private String attachmentFileName = "upload"; - /** CRLF. */ private String crlf = "\r\n"; @@ -498,11 +495,11 @@ public String doHttpRequestBinary(String method, String uri, BinaryData payload, 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=\"" + - this.attachmentFileName + "\"" + this.crlf); + payload.filename + "\"" + this.crlf); request.writeBytes(this.crlf); request.write(payload.bData); diff --git a/src/main/java/com/coscale/sdk/client/data/BinaryData.java b/src/main/java/com/coscale/sdk/client/data/BinaryData.java index ec7ed3e..ea6e29c 100644 --- a/src/main/java/com/coscale/sdk/client/data/BinaryData.java +++ b/src/main/java/com/coscale/sdk/client/data/BinaryData.java @@ -16,8 +16,12 @@ public class BinaryData { /** The binary data. */ public byte[] bData; - public BinaryData(byte[] bData) { + /** The provided filename. */ + public String filename; + + public BinaryData(byte[] bData, String filename) { this.bData = bData; + this.filename = filename; } public BinaryData() { @@ -25,7 +29,7 @@ public BinaryData() { @Override public String toString() { - return MoreObjects.toStringHelper(this).add("bData", new String(Base64.decodeBase64(bData))).toString(); + return MoreObjects.toStringHelper(this).add("bData", new String(Base64.decodeBase64(bData))).add("filename", filename).toString(); } @Override @@ -38,11 +42,11 @@ public boolean equals(Object obj) { } final BinaryData other = (BinaryData) obj; - return Arrays.equals(this.bData, other.bData); + return (Arrays.equals(this.bData, other.bData) && this.filename.equals(other.filename)); } @Override public int hashCode() { - return Objects.hashCode(bData); + return Objects.hashCode(bData, filename); } } From c83982f40b6605ab9e3731ad6070c287a9329513 Mon Sep 17 00:00:00 2001 From: fryckbos Date: Mon, 1 Aug 2016 15:02:11 +0200 Subject: [PATCH 5/5] Updated version number. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6f560d4..28822c7 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.coscale.sdk-java coscale-sdk-java - 1.2-beta-1 + 1.2-beta-2 jar CoScale SDK Java SDK for integrating apps with CoScale Web Performance Monitoring platform.