Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.coscale.sdk-java</groupId>
<artifactId>coscale-sdk-java</artifactId>
<version>1.1.0</version>
<version>1.2-beta-1</version>
<packaging>jar</packaging>
<name>CoScale SDK</name>
<description>Java SDK for integrating apps with CoScale Web Performance Monitoring platform.</description>
Expand Down Expand Up @@ -144,5 +144,10 @@
<artifactId>jsr305</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
</dependencies>
</project>
173 changes: 167 additions & 6 deletions src/main/java/com/coscale/sdk/client/ApiClient.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -301,7 +315,7 @@ private void login() throws IOException {
: getGlobalRequestURL("/users/login/");
Credentials.TokenHelper data = call("POST", uri, credentials,
new TypeReference<Credentials.TokenHelper>() {
}, false);
}, false, false);

token = data.token;
}
Expand Down Expand Up @@ -336,7 +350,53 @@ public <T> 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> T callWithAuthBinary(String method, String endpoint, BinaryData data, TypeReference<T> 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
Expand Down Expand Up @@ -365,13 +425,18 @@ public <T> T callWithAuth(String method, String endpoint, Object obj, TypeRefere
* @throws IOException
*/
public <T> T call(String method, String url, Object obj, TypeReference<T> 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++;
Expand All @@ -381,6 +446,102 @@ public <T> T call(String method, String url, Object obj, TypeReference<T> 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);
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where the filename gets set for the upload.

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
Expand Down
52 changes: 52 additions & 0 deletions src/main/java/com/coscale/sdk/client/data/BinaryData.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
6 changes: 3 additions & 3 deletions src/main/java/com/coscale/sdk/client/data/DataApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

/**
* CoScale API client used to insert data.
*
*
* @author cristi
*
*/
Expand All @@ -19,7 +19,7 @@ public class DataApi {

/**
* DataApi contructor.
*
*
* @param api
* ApiClient.
*/
Expand All @@ -29,7 +29,7 @@ public DataApi(ApiClient api) {

/**
* Insert data into the data-store
*
*
* @param data
* @return Msg containing the api response.
* @throws IOException
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/coscale/sdk/client/events/EventsApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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<Msg>() {
}, true);
}
}