Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ dependencies {
implementation('org.hibernate.validator:hibernate-validator:8.0.0.Final')
implementation 'jakarta.validation:jakarta.validation-api:3.0.0'
implementation('org.glassfish:jakarta.el:4.0.2')
// Jakarta XML Binding API
implementation 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.0'
// Jakarta XML Binding Implementation
implementation 'org.glassfish.jaxb:jaxb-runtime:4.0.3'

api("org.web3j:utils:${web3jVersion}")
api("org.web3j:core:${web3jVersion}")
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/apro/sdk/auth/Auth.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.apro.sdk.auth;

/**
* Auth interface for signing requests
*/
public interface Auth {
/**
* Generate signature for the given content
* @param content Content to sign
* @return Generated signature
* @throws Exception if signing fails
*/
String sign(String content) throws Exception;
}
24 changes: 24 additions & 0 deletions src/main/java/org/apro/sdk/auth/Credential.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.apro.sdk.auth;

import lombok.AllArgsConstructor;

/**
* Credential class for authentication
*/
@AllArgsConstructor
public class Credential {
private final Auth signer;

/**
* Generate authorization header value
* @param content Content to generate authorization for
* @return Authorization header value
* @throws Exception if authorization generation fails
*/
public String generateAuthorization(String content) throws Exception {
if (signer == null) {
return "";
}
return signer.sign(content);
}
}
35 changes: 35 additions & 0 deletions src/main/java/org/apro/sdk/auth/DefaultSigner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.apro.sdk.auth;

import lombok.AllArgsConstructor;
import lombok.Builder;

/**
* Default implementation of Auth interface
*/
@Builder
@AllArgsConstructor
public class DefaultSigner implements Auth {
private final String accessKey;
private final String secretKey;

@Override
public String sign(String content) throws Exception {
// Implement your signing logic here
// This is just a placeholder implementation
if (content == null || content.isEmpty()) {
return "";
}

// Example format: "AccessKey=xxx,Signature=yyy"
return String.format("AccessKey=%s,Signature=%s",
accessKey,
calculateSignature(content)
);
}

private String calculateSignature(String content) {
// Implement your actual signature calculation logic here
// This might involve HMAC-SHA256 or other cryptographic functions
return "signature-placeholder";
}
}
29 changes: 29 additions & 0 deletions src/main/java/org/apro/sdk/models/APIError.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.apro.sdk.models;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import java.io.IOException;
import java.util.List;
import java.util.Map;

@Getter
public class APIError extends IOException {
@JsonIgnore
private final int statusCode;

@JsonIgnore
private final Map<String, List<String>> header;

@JsonIgnore
private final String body;

private String code;
private String message;

public APIError(int statusCode, Map<String, List<String>> header, String body) {
super(String.format("Status Code: %d, Body: %s", statusCode, body));
this.statusCode = statusCode;
this.header = header;
this.body = body;
}
}
56 changes: 56 additions & 0 deletions src/main/java/org/apro/sdk/models/APIResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.apro.sdk.models;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Getter;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;

/**
* API request result containing both request and response
*/
@Getter
@AllArgsConstructor
public class APIResult {
/**
* The HTTP request used for this API call
*/
private final Request request;

/**
* The HTTP response received from this API call
*/
private final Response response;

/**
* Get response body as string. This will consume the response body.
*
* @return Response body as string
* @throws IOException if reading the response body fails
*/
public String getBodyAsString() throws IOException {
if (response.body() == null) {
return "";
}
return response.body().string();
}

/**
* Parse response body into the specified type
*
* @param responseType Class of the response type
* @return Parsed response object
* @throws IOException if parsing fails
*/
public <T> T parseBody(Class<T> responseType) throws IOException {
if (response.body() == null) {
return null;
}

String bodyString = response.body().string();
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(bodyString, responseType);
}
}
158 changes: 158 additions & 0 deletions src/main/java/org/apro/sdk/util/HttpClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package org.apro.sdk.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import okhttp3.*;
import okio.Buffer;
import org.apro.sdk.auth.Credential;
import org.apro.sdk.vrf.constant.Constants;
import org.apro.sdk.models.APIError;
import org.apro.sdk.models.APIResult;

import java.io.*;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

public class HttpClient {
private static final Pattern JSON_TYPE_PATTERN =
Pattern.compile("(?i:(?:application|text)/(?:vnd\\.[^;]+\\+)?json)");
private static final Pattern XML_TYPE_PATTERN =
Pattern.compile("(?i:(?:application|text)/xml)");

private final OkHttpClient httpClient;
private final Credential credential;
private final ObjectMapper objectMapper;

public static HttpClient newDefaultHttpClient() {
return new HttpClient(null, null);
}

public HttpClient(Credential credential, OkHttpClient httpClient) {
this.credential = credential;
this.httpClient = httpClient != null ? httpClient : createDefaultHttpClient();
this.objectMapper = new ObjectMapper();
}

private OkHttpClient createDefaultHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.build();
}

/**
* Send request with full control over HTTP method, path, headers, query parameters and body
*/
public APIResult request(
String method,
String requestPath,
Map<String, String> headers,
Map<String, String> queryParams,
Object postBody,
String contentType
) throws Exception {
// Build URL with query parameters
HttpUrl.Builder urlBuilder = HttpUrl.parse(requestPath).newBuilder();
if (queryParams != null) {
for (Map.Entry<String, String> entry : queryParams.entrySet()) {
urlBuilder.addQueryParameter(entry.getKey(), entry.getValue());
}
}

// Build request body
RequestBody body = null;
String signBody = "";
if (postBody != null) {
if (contentType == null || contentType.isEmpty()) {
contentType = Constants.APPLICATION_JSON;
}
body = buildRequestBody(postBody, contentType);
signBody = bodyToString(body);
}

// Build request
Request.Builder requestBuilder = new Request.Builder()
.url(urlBuilder.build())
.method(method, body);

// Add headers
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
requestBuilder.addHeader(entry.getKey(), entry.getValue());
}
}

// Add fixed headers
requestBuilder.addHeader(Constants.ACCEPT, "*/*");
requestBuilder.addHeader(Constants.CONTENT_TYPE, contentType);
String userAgent = String.format(Constants.USER_AGENT_FORMAT,
Constants.VERSION, System.getProperty("os.name"), System.getProperty("java.version"));
requestBuilder.addHeader(Constants.USER_AGENT, userAgent);

// Add auth header
if (credential != null) {
String authHeader = credential.generateAuthorization(signBody);
if (authHeader != null && !authHeader.isEmpty()) {
requestBuilder.addHeader(Constants.AUTHORIZATION, authHeader);
}
}

// Execute request
Request request = requestBuilder.build();

Response response = httpClient.newCall(request).execute();

// Check response
checkResponse(response);

return new APIResult(request, response);
}

private RequestBody buildRequestBody(Object body, String contentType) throws IOException, JAXBException {
if (body instanceof String) {
return RequestBody.create(MediaType.parse(contentType), (String) body);
} else if (body instanceof byte[]) {
return RequestBody.create(MediaType.parse(contentType), (byte[]) body);
} else if (body instanceof File) {
return RequestBody.create(MediaType.parse(contentType), (File) body);
} else if (JSON_TYPE_PATTERN.matcher(contentType).matches()) {
String json = objectMapper.writeValueAsString(body);
return RequestBody.create(MediaType.parse(contentType), json);
} else if (XML_TYPE_PATTERN.matcher(contentType).matches()) {
StringWriter writer = new StringWriter();
JAXBContext context = JAXBContext.newInstance(body.getClass());
Marshaller marshaller = context.createMarshaller();
marshaller.marshal(body, writer);
return RequestBody.create(MediaType.parse(contentType), writer.toString());
}
throw new IllegalArgumentException("Unsupported body type: " + body.getClass());
}

private String bodyToString(RequestBody body) throws IOException {
if (body == null) return "";
Buffer buffer = new Buffer();
body.writeTo(buffer);
return buffer.readUtf8();
}

private void checkResponse(Response response) throws IOException {
if (!response.isSuccessful()) {
String responseBody = response.body().string();
APIError apiError = new APIError(
response.code(),
response.headers().toMultimap(),
responseBody
);
try {
objectMapper.readerForUpdating(apiError).readValue(responseBody);
} catch (IOException ignored) {
// Ignore JSON parsing errors
}
throw apiError;
}
}
}
Loading