Skip to content

Commit 4882aba

Browse files
committed
pass rest client builder instead of rest client in spring beans
1 parent 986ad26 commit 4882aba

File tree

5 files changed

+136
-107
lines changed

5 files changed

+136
-107
lines changed
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
package com.openelements.hedera.base.mirrornode;
22

3-
public record TransactionInfo(String transactionId) {
3+
import com.hedera.hashgraph.sdk.TransactionId;
4+
import java.util.Objects;
5+
import org.jspecify.annotations.NonNull;
6+
7+
public record TransactionInfo(@NonNull String transactionId) {
8+
9+
public TransactionInfo {
10+
Objects.requireNonNull(transactionId, "transactionId must not be null");
11+
}
412
}

hedera-spring/src/main/java/com/openelements/hedera/spring/implementation/HederaAutoConfiguration.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import com.openelements.hedera.base.implementation.SmartContractClientImpl;
2020
import com.openelements.hedera.base.mirrornode.MirrorNodeClient;
2121
import com.openelements.hedera.base.protocol.ProtocolLayerClient;
22+
import java.net.URI;
23+
import java.net.URL;
2224
import java.util.Arrays;
2325
import java.util.HashMap;
2426
import java.util.List;
@@ -30,6 +32,7 @@
3032
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3133
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3234
import org.springframework.context.annotation.Bean;
35+
import org.springframework.web.client.RestClient;
3336

3437
@AutoConfiguration
3538
@EnableConfigurationProperties({HederaProperties.class, HederaNetworkProperties.class})
@@ -159,13 +162,36 @@ NftClient nftClient(final ProtocolLayerClient protocolLayerClient, AccountId adm
159162
@ConditionalOnProperty(prefix = "spring.hedera", name = "mirrorNodeSupported",
160163
havingValue = "true", matchIfMissing = true)
161164
MirrorNodeClient mirrorNodeClient(final HederaProperties properties, HederaNetwork hederaNetwork) {
165+
final String mirrorNodeEndpoint;
162166
if (properties.getNetwork().getMirrorNode() != null) {
163-
return new MirrorNodeClientImpl(properties.getNetwork().getMirrorNode());
167+
mirrorNodeEndpoint = properties.getNetwork().getMirrorNode();
168+
} else if (hederaNetwork.getMirrornodeEndpoint() != null) {
169+
mirrorNodeEndpoint = hederaNetwork.getMirrornodeEndpoint();
170+
} else {
171+
throw new IllegalArgumentException("Mirror node endpoint must be set");
164172
}
165-
if (hederaNetwork.getMirrornodeEndpoint() != null) {
166-
return new MirrorNodeClientImpl(hederaNetwork.getMirrornodeEndpoint());
173+
174+
final String baseUri;
175+
try {
176+
URL url = new URI(mirrorNodeEndpoint).toURL();
177+
final String mirrorNodeEndpointProtocol = url.getProtocol();
178+
final String mirrorNodeEndpointHost = url.getHost();
179+
final int mirrorNodeEndpointPort;
180+
if (mirrorNodeEndpointProtocol == "https" && url.getPort() == -1) {
181+
mirrorNodeEndpointPort = 443;
182+
} else if (mirrorNodeEndpointProtocol == "http" && url.getPort() == -1) {
183+
mirrorNodeEndpointPort = 80;
184+
} else if (url.getPort() == -1) {
185+
mirrorNodeEndpointPort = 443;
186+
} else {
187+
mirrorNodeEndpointPort = url.getPort();
188+
}
189+
baseUri = mirrorNodeEndpointProtocol + "://" + mirrorNodeEndpointHost + ":" + mirrorNodeEndpointPort;
190+
} catch (Exception e) {
191+
throw new IllegalArgumentException("Error parsing mirrorNodeEndpoint '" + mirrorNodeEndpoint + "'", e);
167192
}
168-
throw new IllegalArgumentException("Mirror node endpoint must be set");
193+
RestClient.Builder builder = RestClient.builder().baseUrl(baseUri);
194+
return new MirrorNodeClientImpl(builder);
169195
}
170196

171197
@Bean

hedera-spring/src/main/java/com/openelements/hedera/spring/implementation/MirrorNodeClientImpl.java

Lines changed: 12 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import com.openelements.hedera.base.mirrornode.TransactionInfo;
1313
import java.io.IOException;
1414
import java.net.URI;
15-
import java.net.URL;
1615
import java.util.List;
1716
import java.util.Map;
1817
import java.util.Objects;
@@ -35,33 +34,16 @@ public class MirrorNodeClientImpl implements MirrorNodeClient {
3534

3635
private final RestClient restClient;
3736

38-
private final String mirrorNodeEndpointProtocol;
39-
40-
private final String mirrorNodeEndpointHost;
41-
42-
private final int mirrorNodeEndpointPort;
43-
44-
45-
public MirrorNodeClientImpl(@NonNull final String mirrorNodeEndpoint) {
46-
Objects.requireNonNull(mirrorNodeEndpoint, "mirrorNodeEndpoint must not be null");
47-
try {
48-
URL url = new URI(mirrorNodeEndpoint).toURL();
49-
mirrorNodeEndpointProtocol = url.getProtocol();
50-
mirrorNodeEndpointHost = url.getHost();
51-
if (mirrorNodeEndpointProtocol == "https" && url.getPort() == -1) {
52-
mirrorNodeEndpointPort = 443;
53-
} else if (mirrorNodeEndpointProtocol == "http" && url.getPort() == -1) {
54-
mirrorNodeEndpointPort = 80;
55-
} else if (url.getPort() == -1) {
56-
mirrorNodeEndpointPort = 443;
57-
} else {
58-
mirrorNodeEndpointPort = url.getPort();
59-
}
60-
} catch (Exception e) {
61-
throw new IllegalArgumentException("Error parsing mirrorNodeEndpoint '" + mirrorNodeEndpoint + "'", e);
62-
}
37+
/**
38+
* Constructor.
39+
*
40+
* @param restClientBuilder the builder for the REST client that must have the base URL set
41+
*/
42+
public MirrorNodeClientImpl(final RestClient.Builder restClientBuilder) {
43+
Objects.requireNonNull(restClientBuilder, "restClientBuilder must not be null");
6344
objectMapper = new ObjectMapper();
64-
restClient = RestClient.create();
45+
restClient = restClientBuilder.build();
46+
6547
}
6648

6749
@Override
@@ -82,15 +64,9 @@ public List<Nft> queryNftsByAccountAndTokenId(@NonNull final AccountId accountId
8264

8365
@Override
8466
public Page<Nft> queryNftsByTokenId(@NonNull TokenId tokenId) throws HederaException {
85-
final URI uri = URI.create(
86-
getUriPrefix()
87-
+ "/api/v1/tokens/" + tokenId + "/nfts");
67+
final String path = "/api/v1/tokens/" + tokenId + "/nfts";
8868
final Function<JsonNode, List<Nft>> dataExtractionFunction = node -> getNfts(node);
89-
final Function<JsonNode, URI> nextUriExtractionFunction = node -> getNextUri(node);
90-
91-
return new RestBasedPage<>(objectMapper, restClient,
92-
uri,
93-
dataExtractionFunction, nextUriExtractionFunction);
69+
return new RestBasedPage<>(objectMapper, restClient.mutate().clone(), path, dataExtractionFunction);
9470
}
9571

9672
@Override
@@ -138,12 +114,7 @@ private JsonNode doGetCall(String path) throws HederaException {
138114

139115
private JsonNode doGetCall(Function<UriBuilder, URI> uriFunction) throws HederaException {
140116
final ResponseEntity<String> responseEntity = restClient.get()
141-
.uri(uriBuilder -> {
142-
final UriBuilder withEndpoint = uriBuilder.scheme(mirrorNodeEndpointProtocol)
143-
.host(mirrorNodeEndpointHost)
144-
.port(mirrorNodeEndpointPort);
145-
return uriFunction.apply(withEndpoint);
146-
})
117+
.uri(uriBuilder -> uriFunction.apply(uriBuilder))
147118
.accept(MediaType.APPLICATION_JSON)
148119
.retrieve()
149120
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
@@ -219,34 +190,4 @@ private List<Nft> getNfts(final JsonNode jsonNode) {
219190
}
220191
}).toList();
221192
}
222-
223-
private String getUriPrefix() {
224-
return mirrorNodeEndpointProtocol + "://" + mirrorNodeEndpointHost + ":" + mirrorNodeEndpointPort;
225-
}
226-
227-
private URI getNextUri(final JsonNode jsonNode) {
228-
if (!jsonNode.has("links")) {
229-
return null;
230-
}
231-
final JsonNode linksNode = jsonNode.get("links");
232-
if (linksNode.isNull()) {
233-
return null;
234-
}
235-
if (!linksNode.has("next")) {
236-
return null;
237-
}
238-
final JsonNode nextNode = linksNode.get("next");
239-
if (nextNode.isNull()) {
240-
return null;
241-
}
242-
if (!nextNode.isTextual()) {
243-
throw new IllegalArgumentException("Next link is not a string: " + nextNode);
244-
}
245-
try {
246-
return new URI(getUriPrefix() + nextNode.asText());
247-
} catch (Exception e) {
248-
throw new IllegalArgumentException("Error parsing next link '" + nextNode.asText() + "'", e);
249-
}
250-
}
251-
252193
}

hedera-spring/src/main/java/com/openelements/hedera/spring/implementation/RestBasedPage.java

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import com.fasterxml.jackson.databind.JsonNode;
77
import com.fasterxml.jackson.databind.ObjectMapper;
88
import com.openelements.hedera.base.mirrornode.Page;
9-
import java.net.URI;
109
import java.util.Collections;
1110
import java.util.List;
1211
import java.util.Objects;
@@ -29,45 +28,50 @@ public class RestBasedPage<T> implements Page<T> {
2928

3029
private final Function<JsonNode, List<T>> dataExtractionFunction;
3130

32-
private final Function<JsonNode, URI> nextUriExtractionFunction;
33-
3431
private final int number;
3532

3633
private final List<T> data;
3734

38-
private final URI nextUri;
35+
private final String nextPath;
3936

40-
private final URI firstUri;
37+
private final String rootPath;
4138

42-
private final URI currentUri;
39+
private final String currentPath;
4340

44-
public RestBasedPage(final @NonNull ObjectMapper objectMapper, final @NonNull RestClient restClient,
45-
final @NonNull URI uri,
46-
final @NonNull Function<JsonNode, List<T>> dataExtractionFunction,
47-
final @NonNull Function<JsonNode, URI> nextUriExtractionFunction) {
48-
this(objectMapper, restClient, uri, 0, dataExtractionFunction, nextUriExtractionFunction, uri);
41+
public RestBasedPage(final @NonNull ObjectMapper objectMapper, final RestClient.Builder restClient,
42+
final @NonNull String path,
43+
final @NonNull Function<JsonNode, List<T>> dataExtractionFunction) {
44+
this(objectMapper, restClient, path, 0, dataExtractionFunction, path);
4945
}
5046

51-
public RestBasedPage(final @NonNull ObjectMapper objectMapper, final @NonNull RestClient restClient,
52-
final @NonNull URI uri, int number,
47+
public RestBasedPage(final @NonNull ObjectMapper objectMapper, final RestClient.Builder restClientBuilder,
48+
final @NonNull String path, int number,
5349
final @NonNull Function<JsonNode, List<T>> dataExtractionFunction,
54-
final @NonNull Function<JsonNode, URI> nextUriExtractionFunction,
55-
final @NonNull URI firstUri) {
50+
final @NonNull String rootPath) {
5651
this.objectMapper = Objects.requireNonNull(objectMapper, "objectMapper must not be null");
57-
this.restClient = Objects.requireNonNull(restClient, "restClient must not be null");
52+
Objects.requireNonNull(restClientBuilder, "restClientBuilder must not be null");
5853
this.dataExtractionFunction = Objects.requireNonNull(dataExtractionFunction,
5954
"dataExtractionFunction must not be null");
60-
this.nextUriExtractionFunction = Objects.requireNonNull(nextUriExtractionFunction,
61-
"nextUriExtractionFunction must not be null");
62-
this.firstUri = Objects.requireNonNull(firstUri, "firstUri must not be null");
63-
this.currentUri = Objects.requireNonNull(uri, "uri must not be null");
55+
this.rootPath = Objects.requireNonNull(rootPath, "rootPath must not be null");
56+
this.currentPath = Objects.requireNonNull(path, "path must not be null");
6457
this.number = number;
6558
if (number < 0) {
6659
throw new IllegalArgumentException("number must be non-negative");
6760
}
68-
log.debug("Fetching data from URI: {}", uri);
61+
log.debug("Fetching data from PATH: {}", path);
62+
restClient = restClientBuilder.build();
63+
String[] pathParts = path.split("\\?");
64+
final String requestPath = pathParts[0];
65+
final String requestQuery;
66+
if (pathParts.length > 1) {
67+
requestQuery = pathParts[1];
68+
} else {
69+
requestQuery = null;
70+
}
71+
6972
final ResponseEntity<String> response = restClient.get()
70-
.uri(uri).accept(APPLICATION_JSON)
73+
.uri(uriBuilder -> uriBuilder.path(requestPath).query(requestQuery).build())
74+
.accept(APPLICATION_JSON)
7175
.retrieve()
7276
.toEntity(String.class);
7377
final HttpStatusCode statusCode = response.getStatusCode();
@@ -81,12 +85,37 @@ public RestBasedPage(final @NonNull ObjectMapper objectMapper, final @NonNull Re
8185
try {
8286
final JsonNode jsonNode = objectMapper.readTree(body);
8387
data = Collections.unmodifiableList(dataExtractionFunction.apply(jsonNode));
84-
nextUri = nextUriExtractionFunction.apply(jsonNode);
88+
nextPath = getNextPath(jsonNode);
8589
} catch (JsonProcessingException e) {
8690
throw new RuntimeException("JSON parsing error", e);
8791
}
8892
}
8993

94+
private String getNextPath(final JsonNode jsonNode) {
95+
if (!jsonNode.has("links")) {
96+
return null;
97+
}
98+
final JsonNode linksNode = jsonNode.get("links");
99+
if (linksNode.isNull()) {
100+
return null;
101+
}
102+
if (!linksNode.has("next")) {
103+
return null;
104+
}
105+
final JsonNode nextNode = linksNode.get("next");
106+
if (nextNode.isNull()) {
107+
return null;
108+
}
109+
if (!nextNode.isTextual()) {
110+
throw new IllegalArgumentException("Next link is not a string: " + nextNode);
111+
}
112+
try {
113+
return nextNode.asText();
114+
} catch (Exception e) {
115+
throw new IllegalArgumentException("Error parsing next link '" + nextNode.asText() + "'", e);
116+
}
117+
}
118+
90119
@Override
91120
public int getNumber() {
92121
return number;
@@ -104,26 +133,25 @@ public List<T> getData() {
104133

105134
@Override
106135
public boolean hasNext() {
107-
return nextUri != null;
136+
return nextPath != null;
108137
}
109138

110139
@Override
111140
public Page<T> next() {
112-
if (nextUri == null) {
113-
throw new IllegalStateException("No next URI");
141+
if (nextPath == null) {
142+
throw new IllegalStateException("No next Page");
114143
}
115-
return new RestBasedPage<>(objectMapper, restClient, nextUri, number + 1, dataExtractionFunction,
116-
nextUriExtractionFunction, firstUri);
144+
return new RestBasedPage<>(objectMapper, restClient.mutate().clone(), nextPath, number + 1,
145+
dataExtractionFunction, rootPath);
117146
}
118147

119148
@Override
120149
public Page<T> first() {
121-
return new RestBasedPage<>(objectMapper, restClient, firstUri, dataExtractionFunction,
122-
nextUriExtractionFunction);
150+
return new RestBasedPage<>(objectMapper, restClient.mutate().clone(), rootPath, dataExtractionFunction);
123151
}
124152

125153
@Override
126154
public boolean isFirst() {
127-
return Objects.equals(firstUri, currentUri);
155+
return Objects.equals(rootPath, currentPath);
128156
}
129157
}

hedera-spring/src/test/java/com/openelements/hedera/spring/test/NftRepositoryTests.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,32 @@ void findByTokenId() throws Exception {
8383
Assertions.assertTrue(result.stream().anyMatch(nft -> nft.serial() == serial.get(1)));
8484
}
8585

86+
@Test
87+
void findByTokenIdForSomePages() throws Exception {
88+
//given
89+
final String name = "Tokemon cards";
90+
final String symbol = "TOK";
91+
final List<String> metadata = IntStream.range(0, 40)
92+
.mapToObj(i -> "metadata" + i)
93+
.toList();
94+
final TokenId tokenId = nftClient.createNftType(name, symbol);
95+
final int batchSize = 10;
96+
for (int i = 0; i < metadata.size(); i += batchSize) {
97+
final int start = i;
98+
final int end = Math.min(i + batchSize, metadata.size());
99+
nftClient.mintNfts(tokenId, metadata.subList(start, end));
100+
}
101+
hederaTestUtils.waitForMirrorNodeRecords();
102+
103+
//when
104+
final Page<Nft> slice = nftRepository.findByType(tokenId);
105+
final List<Nft> result = getAll(slice);
106+
107+
//then
108+
Assertions.assertNotNull(result);
109+
Assertions.assertEquals(metadata.size(), result.size());
110+
}
111+
86112
@Test
87113
void findByTokenIdForManyTokens() throws Exception {
88114
//given

0 commit comments

Comments
 (0)