Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new class to handle Multipart requests #248

Merged
merged 8 commits into from
Apr 16, 2020
Merged
Show file tree
Hide file tree
Changes from 7 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
96 changes: 22 additions & 74 deletions src/main/java/com/auth0/net/CustomRequest.java
Original file line number Diff line number Diff line change
@@ -1,41 +1,35 @@
package com.auth0.net;

import com.auth0.exception.APIException;
import com.auth0.exception.Auth0Exception;
import com.auth0.exception.RateLimitException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.MapType;
import okhttp3.*;
import okhttp3.Request;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@SuppressWarnings("WeakerAccess")
public class CustomRequest<T> extends BaseRequest<T> implements CustomizableRequest<T> {
/**
* Request class that accepts parameters to be sent as part of its body.
* The content type of this request is "application/json".
*
* @param <T> The type expected to be received as part of the response.
*/
public class CustomRequest<T> extends ExtendedBaseRequest<T> implements CustomizableRequest<T> {

private static final String CONTENT_TYPE_APPLICATION_JSON = "application/json";

private final String url;
private final String method;
private final ObjectMapper mapper;
private final TypeReference<T> tType;
private final Map<String, String> headers;
private final Map<String, Object> parameters;
private Object body;

private static final int STATUS_CODE_TOO_MANY_REQUEST = 429;

CustomRequest(OkHttpClient client, String url, String method, ObjectMapper mapper, TypeReference<T> tType) {
super(client);
this.url = url;
this.method = method;
super(client, url, method, mapper);
this.mapper = mapper;
this.tType = tType;
this.headers = new HashMap<>();
this.parameters = new HashMap<>();
}

Expand All @@ -44,34 +38,24 @@ public CustomRequest(OkHttpClient client, String url, String method, TypeReferen
}

@Override
protected Request createRequest() throws Auth0Exception {
Request.Builder builder = new Request.Builder()
.url(url)
.method(method, createBody());
for (Map.Entry<String, String> e : headers.entrySet()) {
builder.addHeader(e.getKey(), e.getValue());
protected RequestBody createRequestBody() throws IOException {
if (body == null && parameters.isEmpty()) {
return null;
}
builder.addHeader("Content-Type", CONTENT_TYPE_APPLICATION_JSON);
return builder.build();
byte[] jsonBody = mapper.writeValueAsBytes(body != null ? body : parameters);
return RequestBody.create(MediaType.parse(CONTENT_TYPE_APPLICATION_JSON), jsonBody);
}

@Override
protected T parseResponse(Response response) throws Auth0Exception {
if (!response.isSuccessful()) {
throw createResponseException(response);
}

try (ResponseBody body = response.body()) {
String payload = body.string();
return mapper.readValue(payload, tType);
} catch (IOException e) {
throw new APIException("Failed to parse json body", response.code(), e);
}
protected T readResponseBody(ResponseBody body) throws IOException {
String payload = body.string();
return mapper.readValue(payload, tType);
}

@Override
public CustomRequest<T> addHeader(String name, String value) {
headers.put(name, value);
//This is to avoid returning a different type
super.addHeader(name, value);
return this;
}

Expand All @@ -86,40 +70,4 @@ public CustomRequest<T> setBody(Object value) {
body = value;
return this;
}

protected RequestBody createBody() throws Auth0Exception {
if (body == null && parameters.isEmpty()) {
return null;
}
try {
byte[] jsonBody = mapper.writeValueAsBytes(body != null ? body : parameters);
return RequestBody.create(MediaType.parse(CONTENT_TYPE_APPLICATION_JSON), jsonBody);
} catch (JsonProcessingException e) {
throw new Auth0Exception("Couldn't create the request body.", e);
}
}

protected Auth0Exception createResponseException(Response response) {
if (response.code() == STATUS_CODE_TOO_MANY_REQUEST) {
return createRateLimitException(response);
}

String payload = null;
try (ResponseBody body = response.body()) {
payload = body.string();
MapType mapType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class);
Map<String, Object> values = mapper.readValue(payload, mapType);
return new APIException(values, response.code());
} catch (IOException e) {
return new APIException(payload, response.code(), e);
}
}

private RateLimitException createRateLimitException(Response response) {
// -1 as default value if the header could not be found.
long limit = Long.parseLong(response.header("X-RateLimit-Limit", "-1"));
long remaining = Long.parseLong(response.header("X-RateLimit-Remaining", "-1"));
long reset = Long.parseLong(response.header("X-RateLimit-Reset", "-1"));
return new RateLimitException(limit, remaining, reset);
}
}
27 changes: 27 additions & 0 deletions src/main/java/com/auth0/net/CustomizableRequest.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
package com.auth0.net;

/**
* A request class that can be customized in different ways.
* i.e. setting headers, body parameters, etc.
*
* @param <T> The type expected to be received as part of the response.
*/
@SuppressWarnings("UnusedReturnValue")
interface CustomizableRequest<T> extends Request<T> {

/**
* Adds an HTTP header to the request
*
* @param name the name of the header
* @param value the value of the header
* @return this same request instance
*/
CustomizableRequest<T> addHeader(String name, String value);

/**
* Adds an body parameter to the request
*
* @param name the name of the parameter
* @param value the value of the parameter
* @return this same request instance
*/
CustomizableRequest<T> addParameter(String name, Object value);

/**
* Sets the response's body directly
*
* @param body the value to set as body
* @return this same request instance
*/
CustomizableRequest<T> setBody(Object body);
}
10 changes: 8 additions & 2 deletions src/main/java/com/auth0/net/EmptyBodyRequest.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package com.auth0.net;

import com.auth0.exception.Auth0Exception;
import com.fasterxml.jackson.core.type.TypeReference;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;

/**
* Request class that does not accept parameters to be sent as part of its body.
* The content type of this request is "application/json".
*
* @param <T> The type expected to be received as part of the response.
* @see CustomRequest
*/
public class EmptyBodyRequest<T> extends CustomRequest<T> {

public EmptyBodyRequest(OkHttpClient client, String url, String method, TypeReference<T> tType) {
super(client, url, method, tType);
}

@Override
protected RequestBody createBody() throws Auth0Exception {
protected RequestBody createRequestBody() {
return RequestBody.create(null, new byte[0]);
}

Expand Down
141 changes: 141 additions & 0 deletions src/main/java/com/auth0/net/ExtendedBaseRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package com.auth0.net;

import com.auth0.exception.APIException;
import com.auth0.exception.Auth0Exception;
import com.auth0.exception.RateLimitException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.MapType;
import okhttp3.Request;
import okhttp3.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
* A request class that is able to interact fluently with the Auth0 server.
* The default content type of this request is "application/json".
* <p>
* //TODO: Merge with #BaseRequest on next major
*
* @param <T> The type expected to be received as part of the response.
*/
abstract class ExtendedBaseRequest<T> extends BaseRequest<T> {

private static final String CONTENT_TYPE_APPLICATION_JSON = "application/json";

private final String url;
private final String method;
private final ObjectMapper mapper;
private final Map<String, String> headers;

private static final int STATUS_CODE_TOO_MANY_REQUEST = 429;

ExtendedBaseRequest(OkHttpClient client, String url, String method, ObjectMapper mapper) {
super(client);
this.url = url;
this.method = method;
this.mapper = mapper;
this.headers = new HashMap<>();
}

@Override
protected Request createRequest() throws Auth0Exception {
RequestBody body;
try {
body = this.createRequestBody();
} catch (IOException e) {
throw new Auth0Exception("Couldn't create the request body.", e);
}
Request.Builder builder = new Request.Builder()
.url(url)
.method(method, body);
for (Map.Entry<String, String> e : headers.entrySet()) {
builder.addHeader(e.getKey(), e.getValue());
}
builder.addHeader("Content-Type", getContentType());
return builder.build();
}

@Override
protected T parseResponse(Response response) throws Auth0Exception {
if (!response.isSuccessful()) {
throw createResponseException(response);
}

try (ResponseBody body = response.body()) {
return readResponseBody(body);
} catch (IOException e) {
throw new APIException("Failed to parse the response body.", response.code(), e);
}
}

/**
* Getter for the content-type header value to use on this request
*
* @return the content-type
*/
protected String getContentType() {
return CONTENT_TYPE_APPLICATION_JSON;
}

/**
* Responsible for creating the payload that will be set as body on this request.
*
* @return the body to send as part of the request.
* @throws IOException if an error is raised during the creation of the body.
*/
protected abstract RequestBody createRequestBody() throws IOException;

/**
* Responsible for parsing the payload that is received as part of the response.
*
* @param body the received body payload. The body buffer will automatically closed.
* @return the instance of type T, result of interpreting the payload.
* @throws IOException if an error is raised during the parsing of the body.
*/
protected abstract T readResponseBody(ResponseBody body) throws IOException;

/**
* Adds an HTTP header to the request
*
* @param name the name of the header
* @param value the value of the header
* @return this same request instance
*/
public ExtendedBaseRequest<T> addHeader(String name, String value) {
headers.put(name, value);
return this;
}

/**
* Responsible for parsing an unsuccessful request (status code other than 200)
* and generating a developer-friendly exception with the error details.
*
* @param response the unsuccessful response, as received. If its body is accessed, the buffer must be closed.
* @return the exception with the error details.
*/
protected Auth0Exception createResponseException(Response response) {
if (response.code() == STATUS_CODE_TOO_MANY_REQUEST) {
return createRateLimitException(response);
}

String payload = null;
try (ResponseBody body = response.body()) {
payload = body.string();
MapType mapType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class);
Map<String, Object> values = mapper.readValue(payload, mapType);
return new APIException(values, response.code());
} catch (IOException e) {
return new APIException(payload, response.code(), e);
}
}

private RateLimitException createRateLimitException(Response response) {
// -1 as default value if the header could not be found.
long limit = Long.parseLong(response.header("X-RateLimit-Limit", "-1"));
long remaining = Long.parseLong(response.header("X-RateLimit-Remaining", "-1"));
long reset = Long.parseLong(response.header("X-RateLimit-Reset", "-1"));
return new RateLimitException(limit, remaining, reset);
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/auth0/net/FormDataRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.auth0.net;

import java.io.File;

/**
* A request class that encodes its content as a form.
* i.e. for uploading attachments.
*
* @param <T> The type expected to be received as part of the response.
*/
@SuppressWarnings("UnusedReturnValue")
interface FormDataRequest<T> extends Request<T> {
lbalmaceda marked this conversation as resolved.
Show resolved Hide resolved

/**
* Adds a key-value part to the form of this request
*
* @param name the name of the part
* @param value the value of the part
* @return this same request instance
*/
FormDataRequest<T> addPart(String name, String value);

/**
* Adds a file part to the form of this request
*
* @param name the name of the part
* @param file the file contents to send in this part
* @param mediaType the file contents media type
* @return this same request instance
*/
FormDataRequest<T> addPart(String name, File file, String mediaType);
}
Loading