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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.hiero.microprofile;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Disposes;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperties;
Expand Down Expand Up @@ -118,14 +119,23 @@ ContractVerificationClient createContractVerificationClient(
@NonNull
@Produces
@ApplicationScoped
MirrorNodeClient createMirrorNodeClient(@NonNull final HieroConfig hieroConfig) {
MirrorNodeRestClientImpl createMirrorNodeRestClient(@NonNull final HieroConfig hieroConfig) {
final String target =
hieroConfig.getMirrorNodeAddresses().stream()
.findFirst()
.orElseThrow(() -> new IllegalStateException("No mirror node addresses configured"));
final MirrorNodeRestClientImpl restClient = new MirrorNodeRestClientImpl(target);
final MirrorNodeJsonConverterImpl jsonConverter = new MirrorNodeJsonConverterImpl();
return new MirrorNodeClientImpl(restClient, jsonConverter);
return new MirrorNodeRestClientImpl(target);
}

void disposeMirrorNodeRestClient(@Disposes @NonNull final MirrorNodeRestClientImpl restClient) {
restClient.close();
}

@NonNull
@Produces
@ApplicationScoped
MirrorNodeClient createMirrorNodeClient(@NonNull final MirrorNodeRestClientImpl restClient) {
return new MirrorNodeClientImpl(restClient, new MirrorNodeJsonConverterImpl());
}

@NonNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public MirrorNodeClientImpl(
final String path = "/api/v1/tokens?account.id=" + accountId;
final Function<JsonObject, List<TransactionInfo>> dataExtractionFunction =
node -> jsonConverter.toTransactionInfos(node);
return new RestBasedPage<>(restClient.getTarget(), dataExtractionFunction, path);
return new RestBasedPage<>(restClient.getRootTarget(), dataExtractionFunction, path);
}

@Override
Expand All @@ -79,7 +79,7 @@ public MirrorNodeClientImpl(
final String path = "/api/v1/tokens?account.id=" + accountId + "&transactiontype=" + type;
final Function<JsonObject, List<TransactionInfo>> dataExtractionFunction =
node -> jsonConverter.toTransactionInfos(node);
return new RestBasedPage<>(restClient.getTarget(), dataExtractionFunction, path);
return new RestBasedPage<>(restClient.getRootTarget(), dataExtractionFunction, path);
}

@Override
Expand All @@ -90,7 +90,7 @@ public MirrorNodeClientImpl(
final String path = "/api/v1/tokens?account.id=" + accountId + "&result=" + result;
final Function<JsonObject, List<TransactionInfo>> dataExtractionFunction =
node -> jsonConverter.toTransactionInfos(node);
return new RestBasedPage<>(restClient.getTarget(), dataExtractionFunction, path);
return new RestBasedPage<>(restClient.getRootTarget(), dataExtractionFunction, path);
}

@Override
Expand All @@ -101,7 +101,7 @@ public MirrorNodeClientImpl(
final String path = "/api/v1/tokens?account.id=" + accountId + "&type=" + type;
final Function<JsonObject, List<TransactionInfo>> dataExtractionFunction =
node -> jsonConverter.toTransactionInfos(node);
return new RestBasedPage<>(restClient.getTarget(), dataExtractionFunction, path);
return new RestBasedPage<>(restClient.getRootTarget(), dataExtractionFunction, path);
}

@Override
Expand All @@ -110,7 +110,7 @@ public Page<Token> queryTokensForAccount(@NonNull AccountId accountId) throws Hi
final String path = "/api/v1/tokens?account.id=" + accountId;
final Function<JsonObject, List<Token>> dataExtractionFunction =
node -> jsonConverter.toTokens(node);
return new RestBasedPage<>(restClient.getTarget(), dataExtractionFunction, path);
return new RestBasedPage<>(restClient.getRootTarget(), dataExtractionFunction, path);
}

@Override
Expand All @@ -119,7 +119,7 @@ public Page<Token> queryTokensForAccount(@NonNull AccountId accountId) throws Hi
final String path = "/api/v1/tokens/" + tokenId + "/balances";
final Function<JsonObject, List<Balance>> dataExtractionFunction =
node -> jsonConverter.toBalances(node);
return new RestBasedPage<>(restClient.getTarget(), dataExtractionFunction, path);
return new RestBasedPage<>(restClient.getRootTarget(), dataExtractionFunction, path);
}

@Override
Expand All @@ -130,7 +130,7 @@ public Page<Token> queryTokensForAccount(@NonNull AccountId accountId) throws Hi
final String path = "/api/v1/tokens/" + tokenId + "/balances?account.id=" + accountId;
final Function<JsonObject, List<Balance>> dataExtractionFunction =
node -> jsonConverter.toBalances(node);
return new RestBasedPage<>(restClient.getTarget(), dataExtractionFunction, path);
return new RestBasedPage<>(restClient.getRootTarget(), dataExtractionFunction, path);
}

@Override
Expand All @@ -139,7 +139,7 @@ public Page<Token> queryTokensForAccount(@NonNull AccountId accountId) throws Hi
final String path = "/api/v1/topics/" + topicId + "/messages";
final Function<JsonObject, List<TopicMessage>> dataExtractionFunction =
node -> jsonConverter.toTopicMessages(node);
return new RestBasedPage<>(restClient.getTarget(), dataExtractionFunction, path);
return new RestBasedPage<>(restClient.getRootTarget(), dataExtractionFunction, path);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,80 @@
package org.hiero.microprofile.implementation;

import jakarta.json.JsonObject;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.Objects;
import org.hiero.base.HieroException;
import org.hiero.base.implementation.MirrorNodeRestClient;
import org.jspecify.annotations.NonNull;

public class MirrorNodeRestClientImpl implements MirrorNodeRestClient<JsonObject> {
public class MirrorNodeRestClientImpl implements MirrorNodeRestClient<JsonObject>, AutoCloseable {

private final String target;
private static final int HTTP_NOT_FOUND = Response.Status.NOT_FOUND.getStatusCode();

public MirrorNodeRestClientImpl(String target) {
this.target = target;
private final Client client;

private final WebTarget rootTarget;

public MirrorNodeRestClientImpl(@NonNull final String target) {
Objects.requireNonNull(target, "target must not be null");
this.client = ClientBuilder.newClient();
this.rootTarget = client.target(target);
}

@Override
public @NonNull JsonObject doGetCall(@NonNull String path) throws HieroException {
Client client = ClientBuilder.newClient();
Response response = client.target(target).path(path).request(MediaType.APPLICATION_JSON).get();

if (response.getStatus() == 404 || !response.hasEntity()) {
return JsonObject.EMPTY_JSON_OBJECT;
public @NonNull JsonObject doGetCall(@NonNull final String path) throws HieroException {
Objects.requireNonNull(path, "path must not be null");
final WebTarget requestTarget = buildTarget(rootTarget, path);
try (final Response response = requestTarget.request(MediaType.APPLICATION_JSON).get()) {
final int status = response.getStatus();
if (status == HTTP_NOT_FOUND) {
return JsonObject.EMPTY_JSON_OBJECT;
}
if (status >= 400) {
throw new HieroException(
"Mirror node returned error " + status + " for path '" + path + "'");
}
if (!response.hasEntity()) {
return JsonObject.EMPTY_JSON_OBJECT;
}
return response.readEntity(JsonObject.class);
} catch (final ProcessingException e) {
throw new HieroException("Error calling mirror node for path '" + path + "'", e);
}
return response.readEntity(JsonObject.class);
}

public String getTarget() {
@NonNull
public WebTarget getRootTarget() {
return rootTarget;
}

@Override
public void close() {
client.close();
}

/**
* Builds a {@link WebTarget} for the given path, parsing any query string that follows a single
* {@code ?} into {@link WebTarget#queryParam(String, Object...)} calls.
*/
static WebTarget buildTarget(@NonNull final WebTarget base, @NonNull final String path) {
final String[] parts = path.split("\\?", 2);
WebTarget target = base.path(parts[0]);
if (parts.length == 2 && !parts[1].isEmpty()) {
for (final String param : parts[1].split("&")) {
if (param.isEmpty()) {
continue;
}
final String[] kv = param.split("=", 2);
final String value = kv.length == 2 ? kv[1] : "";
target = target.queryParam(kv[0], value);
}
}
return target;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package org.hiero.microprofile.implementation;

import jakarta.json.JsonObject;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
Expand All @@ -14,7 +13,10 @@
import org.jspecify.annotations.NonNull;

public class RestBasedPage<T> implements Page<T> {
private final String restTarget;

private static final int HTTP_NOT_FOUND = Response.Status.NOT_FOUND.getStatusCode();

private final WebTarget rootTarget;
private final Function<JsonObject, List<T>> dataExtractionFunction;
private final List<T> data;
private final String rootPath;
Expand All @@ -23,66 +25,59 @@ public class RestBasedPage<T> implements Page<T> {
private final int number;

public RestBasedPage(
@NonNull String restTarget,
@NonNull WebTarget rootTarget,
@NonNull Function<JsonObject, @NonNull List<T>> dataExtractionFunction,
@NonNull String path) {
this(restTarget, dataExtractionFunction, path, path, 0);
this(rootTarget, dataExtractionFunction, path, path, 0);
}

public RestBasedPage(
@NonNull String restTarget,
@NonNull WebTarget rootTarget,
@NonNull Function<JsonObject, List<T>> dataExtractionFunction,
@NonNull String path,
@NonNull String rootPath,
int number) {
this.restTarget = Objects.requireNonNull(restTarget, "restTarget must not be null");
this.rootTarget = Objects.requireNonNull(rootTarget, "rootTarget must not be null");
this.dataExtractionFunction =
Objects.requireNonNull(dataExtractionFunction, "dataExtractionFunction must not be null");
this.rootPath = Objects.requireNonNull(rootPath, "rootPath must not be null");
this.currentPath = Objects.requireNonNull(path, "path must not be null");
this.number = number;

String[] pathParts = currentPath.split("\\?");
final String requestPath = pathParts[0];

try {
Client client = ClientBuilder.newClient();
WebTarget target = client.target(restTarget).path(requestPath);
if (pathParts.length > 1) {
String[] params = pathParts[1].split("&");
for (String param : params) {
String[] p = param.split("=");
target = target.queryParam(p[0], p[1]);
}
final WebTarget requestTarget = MirrorNodeRestClientImpl.buildTarget(rootTarget, path);
try (final Response response = requestTarget.request(MediaType.APPLICATION_JSON).get()) {
final int status = response.getStatus();
if (status >= 400 && status != HTTP_NOT_FOUND) {
throw new IllegalStateException(
"Mirror node returned error " + status + " for path '" + path + "'");
}
final JsonObject jsonObject;
if (status == HTTP_NOT_FOUND || !response.hasEntity()) {
jsonObject = JsonObject.EMPTY_JSON_OBJECT;
} else {
jsonObject = response.readEntity(JsonObject.class);
}
Response response = target.request(MediaType.APPLICATION_JSON).get();

final JsonObject jsonObject = response.readEntity(JsonObject.class);
this.data = Collections.unmodifiableList(dataExtractionFunction.apply(jsonObject));
this.nextPath = getNextPath(jsonObject);
} catch (Exception e) {
throw new IllegalStateException("Can not parse JSON: " + e);
} catch (final ProcessingException e) {
throw new IllegalStateException("Error calling mirror node for path '" + path + "'", e);
}
}

private String getNextPath(final JsonObject jsonObject) {
private static String getNextPath(final JsonObject jsonObject) {
if (!jsonObject.containsKey("links")) {
return null;
}
final JsonObject linksObject = jsonObject.getJsonObject("links");
if (linksObject == null || !linksObject.containsKey("next")) {
return null;
}

try {
final String next;
if (!linksObject.isNull("next")) {
next = linksObject.getString("next");
} else {
next = null;
if (linksObject.isNull("next")) {
return null;
}
return next;
} catch (Exception e) {
return linksObject.getString("next");
} catch (final Exception e) {
throw new IllegalArgumentException("Error parsing next link '" + linksObject + "'", e);
}
}
Expand Down Expand Up @@ -112,12 +107,12 @@ public Page<T> next() {
if (nextPath == null) {
throw new IllegalStateException("No next Page");
}
return new RestBasedPage<T>(restTarget, dataExtractionFunction, nextPath, rootPath, number + 1);
return new RestBasedPage<T>(rootTarget, dataExtractionFunction, nextPath, rootPath, number + 1);
}

@Override
public Page<T> first() {
return new RestBasedPage<T>(restTarget, dataExtractionFunction, rootPath);
return new RestBasedPage<T>(rootTarget, dataExtractionFunction, rootPath);
}

@Override
Expand Down
Loading
Loading