1414import java .io .IOException ;
1515import java .net .URI ;
1616import java .util .List ;
17+ import java .util .Map ;
1718import java .util .Objects ;
1819import java .util .Optional ;
1920import java .util .Spliterator ;
@@ -33,22 +34,99 @@ public class MirrorNodeClientImpl implements MirrorNodeClient {
3334
3435 private final RestClient restClient ;
3536
36- private final String mirrorNodeEndpoint ;
37+ private final String mirrorNodeEndpointScheme = "https" ;
38+
39+ private final String mirrorNodeEndpointHost ;
40+
41+ private final String mirrorNodeEndpointPort ;
42+
3743
3844 public MirrorNodeClientImpl (@ NonNull final Client client ) {
3945 Objects .requireNonNull (client , "client must not be null" );
4046 final List <String > mirrorNetwork = client .getMirrorNetwork ();
4147 if (mirrorNetwork .isEmpty ()) {
4248 throw new IllegalArgumentException ("No mirror network is configured" );
4349 }
44- mirrorNodeEndpoint = "https://" + mirrorNetwork .get (0 );
50+ mirrorNodeEndpointHost = mirrorNetwork .get (0 ).split ("\\ :" )[0 ];
51+ mirrorNodeEndpointPort = mirrorNetwork .get (0 ).split ("\\ :" )[1 ];
4552 objectMapper = new ObjectMapper ();
4653 restClient = RestClient .create ();
4754 }
4855
56+ @ Override
57+ public List <Nft > queryNftsByAccount (@ NonNull final AccountId accountId ) throws HederaException {
58+ Objects .requireNonNull (accountId , "accountId must not be null" );
59+ final JsonNode jsonNode = doGetCall ("/api/v1/accounts/" + accountId + "/nfts" );
60+ return jsonNodeToNftList (jsonNode );
61+ }
62+
63+ @ Override
64+ public List <Nft > queryNftsByAccountAndTokenId (@ NonNull final AccountId accountId , @ NonNull final TokenId tokenId )
65+ throws HederaException {
66+ Objects .requireNonNull (accountId , "accountId must not be null" );
67+ Objects .requireNonNull (tokenId , "tokenId must not be null" );
68+ final JsonNode jsonNode = doGetCall ("/api/v1/tokens/" + tokenId + "/nfts" , Map .of ("account.id" , accountId ));
69+ return jsonNodeToNftList (jsonNode );
70+ }
71+
72+ @ Override
73+ public List <Nft > queryNftsByTokenId (@ NonNull TokenId tokenId ) throws HederaException {
74+ Objects .requireNonNull (tokenId , "tokenId must not be null" );
75+ final JsonNode jsonNode = doGetCall ("/api/v1/tokens/" + tokenId + "/nfts" );
76+ return jsonNodeToNftList (jsonNode );
77+ }
78+
79+ @ Override
80+ public Optional <Nft > queryNftsByTokenIdAndSerial (@ NonNull final TokenId tokenId , @ NonNull final long serialNumber )
81+ throws HederaException {
82+ Objects .requireNonNull (tokenId , "tokenId must not be null" );
83+ if (serialNumber <= 0 ) {
84+ throw new IllegalArgumentException ("serialNumber must be positive" );
85+ }
86+ final JsonNode jsonNode = doGetCall ("/api/v1/tokens/" + tokenId + "/nfts/" + serialNumber );
87+ return jsonNodeToOptionalNft (jsonNode );
88+ }
89+
90+ @ Override
91+ public Optional <Nft > queryNftsByAccountAndTokenIdAndSerial (@ NonNull final AccountId accountId ,
92+ @ NonNull final TokenId tokenId , final long serialNumber ) throws HederaException {
93+ Objects .requireNonNull (accountId , "accountId must not be null" );
94+ return queryNftsByTokenIdAndSerial (tokenId , serialNumber )
95+ .filter (nft -> Objects .equals (nft .owner (), accountId ));
96+ }
97+
98+ @ Override
99+ public Optional <TransactionInfo > queryTransaction (@ NonNull final String transactionId ) throws HederaException {
100+ Objects .requireNonNull (transactionId , "transactionId must not be null" );
101+ final JsonNode jsonNode = doGetCall ("/api/v1/transactions/" + transactionId );
102+ if (jsonNode == null || !jsonNode .fieldNames ().hasNext ()) {
103+ return Optional .empty ();
104+ }
105+ return Optional .of (new TransactionInfo (transactionId ));
106+ }
107+
108+ private JsonNode doGetCall (String path , Map <String , ?> params ) throws HederaException {
109+ return doGetCall (builder -> {
110+ UriBuilder uriBuilder = builder .path (path );
111+ for (Map .Entry <String , ?> entry : params .entrySet ()) {
112+ uriBuilder = uriBuilder .queryParam (entry .getKey (), entry .getValue ());
113+ }
114+ return uriBuilder .build ();
115+ });
116+ }
117+
118+ private JsonNode doGetCall (String path ) throws HederaException {
119+ return doGetCall (builder -> builder .path (path ).build ());
120+ }
121+
49122 private JsonNode doGetCall (Function <UriBuilder , URI > uriFunction ) throws HederaException {
50123 final ResponseEntity <String > responseEntity = restClient .get ()
51- .uri (uriFunction )
124+ .uri (uriBuilder -> {
125+ final UriBuilder withEndpoint = uriBuilder .scheme (mirrorNodeEndpointScheme )
126+ .host (mirrorNodeEndpointHost )
127+ .port (mirrorNodeEndpointPort );
128+ return uriFunction .apply (withEndpoint );
129+ })
52130 .accept (MediaType .APPLICATION_JSON )
53131 .retrieve ()
54132 .onStatus (HttpStatusCode ::is4xxClientError , (request , response ) -> {
@@ -59,7 +137,7 @@ private JsonNode doGetCall(Function<UriBuilder, URI> uriFunction) throws HederaE
59137 .toEntity (String .class );
60138 final String body = responseEntity .getBody ();
61139 try {
62- if (HttpStatus .NOT_FOUND .equals (responseEntity .getStatusCode ())) {
140+ if (HttpStatus .NOT_FOUND .equals (responseEntity .getStatusCode ())) {
63141 return objectMapper .readTree ("{}" );
64142 }
65143 return objectMapper .readTree (body );
@@ -68,55 +146,8 @@ private JsonNode doGetCall(Function<UriBuilder, URI> uriFunction) throws HederaE
68146 }
69147 }
70148
71- @ Override
72- public List <Nft > queryNftsByAccount (@ NonNull final AccountId accountId ) throws HederaException {
73- Objects .requireNonNull (accountId , "accountId must not be null" );
74- final String host = mirrorNodeEndpoint .substring (8 ).split ("\\ :" )[0 ];
75- final String port = mirrorNodeEndpoint .substring (8 ).split ("\\ :" )[1 ];
76-
77- final JsonNode jsonNode = doGetCall (builder ->
78- builder
79- .scheme ("https" )
80- .host (host )
81- .port (port )
82- .path ("/api/v1/accounts/" + accountId + "/nfts" )
83- .build ()
84- );
85- return parseJsonToList (jsonNode );
86- }
87-
88- @ Override
89- public List <Nft > queryNftsByAccountAndTokenId (@ NonNull final AccountId accountId , @ NonNull final TokenId tokenId ) throws HederaException {
90- Objects .requireNonNull (accountId , "accountId must not be null" );
91- Objects .requireNonNull (tokenId , "tokenId must not be null" );
92- final String host = mirrorNodeEndpoint .substring (8 ).split ("\\ :" )[0 ];
93- final String port = mirrorNodeEndpoint .substring (8 ).split ("\\ :" )[1 ];
94- final JsonNode jsonNode = doGetCall (builder -> builder
95- .scheme ("https" )
96- .host (host )
97- .port (port )
98- .path ("/api/v1/tokens/" + tokenId + "/nfts" )
99- .queryParam ("account.id" , accountId )
100- .build ());
101- return parseJsonToList (jsonNode );
102- }
103-
104- @ Override
105- public List <Nft > queryNftsByTokenId (@ NonNull TokenId tokenId ) throws HederaException {
106- Objects .requireNonNull (tokenId , "tokenId must not be null" );
107- final String host = mirrorNodeEndpoint .substring (8 ).split ("\\ :" )[0 ];
108- final String port = mirrorNodeEndpoint .substring (8 ).split ("\\ :" )[1 ];
109- final JsonNode jsonNode = doGetCall (builder -> builder
110- .scheme ("https" )
111- .host (host )
112- .port (port )
113- .path ("/api/v1/tokens/" + tokenId + "/nfts" )
114- .build ());
115- return parseJsonToList (jsonNode );
116- }
117-
118- private List <Nft > parseJsonToList (final JsonNode rootNode ) {
119- if (rootNode == null || !rootNode .fieldNames ().hasNext ()) {
149+ private List <Nft > jsonNodeToNftList (final JsonNode rootNode ) {
150+ if (rootNode == null || !rootNode .fieldNames ().hasNext ()) {
120151 return List .of ();
121152 }
122153 return StreamSupport .stream (
@@ -130,64 +161,27 @@ private List<Nft> parseJsonToList(final JsonNode rootNode) {
130161 }).toList ();
131162 }
132163
164+ private Optional <Nft > jsonNodeToOptionalNft (final JsonNode jsonNode ) throws HederaException {
165+ if (jsonNode == null || !jsonNode .fieldNames ().hasNext ()) {
166+ return Optional .empty ();
167+ }
168+ try {
169+ return Optional .of (jsonNodeToNft (jsonNode ));
170+ } catch (final Exception e ) {
171+ throw new HederaException ("Error parsing NFT from JSON '" + jsonNode + "'" , e );
172+ }
173+ }
174+
133175 private Nft jsonNodeToNft (final JsonNode jsonNode ) throws IOException {
176+ try {
134177 final TokenId parsedTokenId = TokenId .fromString (jsonNode .get ("token_id" ).asText ());
135178 final AccountId account = AccountId .fromString (jsonNode .get ("account_id" ).asText ());
136179 final long serial = jsonNode .get ("serial_number" ).asLong ();
137180 final byte [] metadata = jsonNode .get ("metadata" ).binaryValue ();
138181 return new Nft (parsedTokenId , serial , account , metadata );
139- }
140-
141- @ Override
142- public Optional <Nft > queryNftsByTokenIdAndSerial (@ NonNull final TokenId tokenId , @ NonNull final long serialNumber ) throws HederaException {
143- Objects .requireNonNull (tokenId , "tokenId must not be null" );
144- if (serialNumber <= 0 ) {
145- throw new IllegalArgumentException ("serialNumber must be positive" );
146- }
147- final String body = restClient .get ()
148- .uri (mirrorNodeEndpoint + "/api/v1/tokens/" + tokenId + "/nfts/" + serialNumber )
149- .accept (MediaType .APPLICATION_JSON )
150- .retrieve ()
151- .onStatus (HttpStatusCode ::is4xxClientError , (request , response ) -> {
152- if (!HttpStatus .NOT_FOUND .equals (response .getStatusCode ())) {
153- throw new RuntimeException ("Client error: " + response .getStatusText ());
154- }
155- }).body (String .class );
156- if (body == null || body .equals ("{\" _status\" :{\" messages\" :[{\" message\" :\" Not found\" }]}}" )) {
157- return Optional .empty ();
158- }
159- try {
160- JsonNode jsonNode = objectMapper .readTree (body );
161- return Optional .ofNullable (jsonNodeToNft (jsonNode ));
162- } catch (IOException e ) {
163- throw new HederaException ("Error parsing body as JSON: " + body , e );
182+ } catch (final Exception e ) {
183+ throw new IllegalArgumentException ("Error parsing NFT from JSON '" + jsonNode + "'" , e );
164184 }
165185 }
166186
167- @ Override
168- public Optional <Nft > queryNftsByAccountAndTokenIdAndSerial (@ NonNull final AccountId accountId , @ NonNull final TokenId tokenId , final long serialNumber ) throws HederaException {
169- Objects .requireNonNull (accountId , "accountId must not be null" );
170- return queryNftsByTokenIdAndSerial (tokenId , serialNumber )
171- .filter (nft -> Objects .equals (nft .owner (), accountId ));
172- }
173-
174- @ Override
175- public Optional <TransactionInfo > queryTransaction (@ NonNull final String transactionId ) throws HederaException {
176- Objects .requireNonNull (transactionId , "transactionId must not be null" );
177- final ResponseEntity <String > entity = restClient .get ()
178- .uri (mirrorNodeEndpoint + "/api/v1/transactions/" + transactionId )
179- .accept (MediaType .APPLICATION_JSON )
180- .retrieve ()
181- .onStatus (HttpStatusCode ::is4xxClientError , (request , response ) -> {
182- if (!HttpStatus .NOT_FOUND .equals (response .getStatusCode ())) {
183- throw new RuntimeException ("Client error: " + response .getStatusText ());
184- }
185- }).toEntity (String .class );
186- if (HttpStatus .NOT_FOUND .equals (entity .getStatusCode ())) {
187- return Optional .empty ();
188- }
189- final String body = entity .getBody ();
190- //TODO: JSON PARSING
191- return Optional .of (new TransactionInfo (transactionId ));
192- }
193187}
0 commit comments