22
33import static io .github .jpmorganchase .fusion .filter .DatasetFilter .filterDatasets ;
44
5+ import com .google .gson .Gson ;
6+ import com .google .gson .JsonArray ;
7+ import com .google .gson .JsonObject ;
8+ import com .google .gson .JsonParser ;
59import io .github .jpmorganchase .fusion .api .APIManager ;
610import io .github .jpmorganchase .fusion .api .FusionAPIManager ;
711import io .github .jpmorganchase .fusion .api .exception .APICallException ;
1115import io .github .jpmorganchase .fusion .builders .APIConfiguredBuilders ;
1216import io .github .jpmorganchase .fusion .builders .Builders ;
1317import io .github .jpmorganchase .fusion .http .Client ;
18+ import io .github .jpmorganchase .fusion .http .HttpResponse ;
1419import io .github .jpmorganchase .fusion .http .JdkClient ;
1520import io .github .jpmorganchase .fusion .model .*;
1621import io .github .jpmorganchase .fusion .oauth .credential .BearerTokenCredentials ;
3641import java .util .Map ;
3742import java .util .Objects ;
3843import lombok .Builder ;
44+ import lombok .extern .slf4j .Slf4j ;
3945
4046/**
4147 * Class representing the Fusion API, providing methods that correspond to available API endpoints
4248 */
49+ @ Slf4j
4350public class Fusion {
4451
52+ private static final int DEFAULT_PAGE_SIZE = -1 ;
4553 private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter .ofPattern ("yyyy-MM-dd" );
4654
4755 private final APIManager api ;
@@ -195,6 +203,90 @@ private Map<String, Map<String, Object>> callForMap(String url) {
195203 return responseParser .parseResourcesUntyped (json );
196204 }
197205
206+ /**
207+ * Makes paginated API calls and aggregates all results transparently.
208+ * This method handles the pagination logic internally, making multiple API calls
209+ * as needed and combining the results into a single response string.
210+ *
211+ * @param url the API endpoint URL
212+ * @return aggregated JSON response containing all pages of data
213+ */
214+ private String callAPIWithPagination (String url ) {
215+ log .debug ("Starting paginated request to URL: {}" , url );
216+
217+ Map <String , String > headers = new HashMap <>();
218+ headers .put ("x-jpmc-paginate" , "true" );
219+ if (DEFAULT_PAGE_SIZE > 0 ) {
220+ log .debug ("Using page size: {}" , DEFAULT_PAGE_SIZE );
221+ headers .put ("x-jpmc-page-size" , String .valueOf (DEFAULT_PAGE_SIZE ));
222+ }
223+
224+ Gson gson = new Gson ();
225+ JsonArray aggregatedResources = new JsonArray ();
226+ String nextToken = null ;
227+ int pageCount = 0 ;
228+
229+ do {
230+ pageCount ++;
231+ if (nextToken != null ) {
232+ headers .put ("x-jpmc-next-token" , nextToken );
233+ log .debug ("Fetching page {} with next token" , pageCount );
234+ } else {
235+ log .debug ("Fetching page {}" , pageCount );
236+ }
237+
238+ HttpResponse <String > response = this .api .callAPIWithResponse (url , headers );
239+ String pageJson = response .getBody ();
240+
241+ JsonObject pageObject = JsonParser .parseString (pageJson ).getAsJsonObject ();
242+ if (pageObject .has ("resources" ) && pageObject .get ("resources" ).isJsonArray ()) {
243+ JsonArray pageResources = pageObject .getAsJsonArray ("resources" );
244+ int pageResourceCount = pageResources .size ();
245+ pageResources .forEach (aggregatedResources ::add );
246+ log .debug ("Retrieved {} resources from page {}" , pageResourceCount , pageCount );
247+ }
248+
249+ nextToken = getHeaderValue (response .getHeaders (), "x-jpmc-next-token" );
250+
251+ if (nextToken != null && !nextToken .isEmpty ()) {
252+ log .debug ("Next token received, more pages available" );
253+ }
254+
255+ } while (nextToken != null && !nextToken .isEmpty ());
256+
257+ log .debug (
258+ "Pagination complete. Total pages fetched: {}, Total resources: {}" ,
259+ pageCount ,
260+ aggregatedResources .size ());
261+
262+ JsonObject result = new JsonObject ();
263+ result .add ("resources" , aggregatedResources );
264+ return gson .toJson (result );
265+ }
266+
267+ /**
268+ * Gets a header value from the response headers map (case-insensitive).
269+ *
270+ * @param headers the response headers map
271+ * @param headerName the header name to look for
272+ * @return the header value, or null if not found
273+ */
274+ private String getHeaderValue (Map <String , java .util .List <String >> headers , String headerName ) {
275+ if (headers == null || headerName == null ) {
276+ return null ;
277+ }
278+
279+ for (Map .Entry <String , java .util .List <String >> entry : headers .entrySet ()) {
280+ if (entry .getKey () != null && entry .getKey ().equalsIgnoreCase (headerName )) {
281+ java .util .List <String > values = entry .getValue ();
282+ if (values != null && !values .isEmpty ()) {
283+ return values .get (0 );
284+ }
285+ }
286+ }
287+ return null ;
288+ }
289+
198290 /**
199291 * Get a list of the catalogs available to the API account.
200292 *
@@ -203,7 +295,8 @@ private Map<String, Map<String, Object>> callForMap(String url) {
203295 * @throws OAuthException if a token could not be retrieved for authentication
204296 */
205297 public Map <String , Catalog > listCatalogs () {
206- String json = this .api .callAPI (rootURL .concat ("catalogs" ));
298+ String url = rootURL .concat ("catalogs" );
299+ String json = callAPIWithPagination (url );
207300 return responseParser .parseCatalogResponse (json );
208301 }
209302
@@ -223,7 +316,7 @@ public Map<String, Map<String, Object>> catalogResources(String catalogName) {
223316 /**
224317 * Get a filtered list of the data products in the specified catalog
225318 * <p>
226- * Note that as of current version this search capability is not yet implemented
319+ * Note that as of the current version, this search capability is not yet implemented
227320 *
228321 * @param catalogName identifier of the catalog to be queried
229322 * @param contains a search keyword.
@@ -235,7 +328,7 @@ public Map<String, Map<String, Object>> catalogResources(String catalogName) {
235328 public Map <String , DataProduct > listProducts (String catalogName , String contains , boolean idContains ) {
236329 // TODO: unimplemented logic implied by the method parameters
237330 String url = String .format ("%1scatalogs/%2s/products" , this .rootURL , catalogName );
238- String json = this . api . callAPI (url );
331+ String json = callAPIWithPagination (url );
239332 return responseParser .parseDataProductResponse (json );
240333 }
241334
@@ -266,7 +359,7 @@ public Map<String, DataProduct> listProducts() {
266359 /**
267360 * Get a filtered list of the datasets in the specified catalog
268361 * <p>
269- * Note that as of current version this search capability is not yet implemented
362+ * Note that as of the current version, this search capability is not yet implemented
270363 *
271364 * @param catalogName identifier of the catalog to be queried
272365 * @param contains a search keyword.
@@ -277,7 +370,7 @@ public Map<String, DataProduct> listProducts() {
277370 */
278371 public Map <String , Dataset > listDatasets (String catalogName , String contains , boolean idContains ) {
279372 String url = String .format ("%1scatalogs/%2s/datasets" , this .rootURL , catalogName );
280- String json = this . api . callAPI (url );
373+ String json = callAPIWithPagination (url );
281374 return filterDatasets (responseParser .parseDatasetResponse (json , catalogName ), contains , idContains );
282375 }
283376
@@ -361,7 +454,7 @@ public Map<String, Map<String, Object>> datasetResources(String dataset) {
361454 */
362455 public Map <String , DatasetSeries > listDatasetMembers (String catalogName , String dataset ) {
363456 String url = String .format ("%1scatalogs/%2s/datasets/%3s/datasetseries" , this .rootURL , catalogName , dataset );
364- String json = this . api . callAPI (url );
457+ String json = callAPIWithPagination (url );
365458 return responseParser .parseDatasetSeriesResponse (json );
366459 }
367460
@@ -421,7 +514,7 @@ public Map<String, Map<String, Object>> datasetMemberResources(String dataset, S
421514 */
422515 public Map <String , Attribute > listAttributes (String catalogName , String dataset ) {
423516 String url = String .format ("%1scatalogs/%2s/datasets/%3s/attributes" , this .rootURL , catalogName , dataset );
424- String json = this . api . callAPI (url );
517+ String json = callAPIWithPagination (url );
425518 return responseParser .parseAttributeResponse (json , catalogName , dataset );
426519 }
427520
@@ -463,11 +556,10 @@ public Map<String, Map<String, Object>> attributeResources(String catalogName, S
463556 * @throws OAuthException if a token could not be retrieved for authentication
464557 */
465558 public Map <String , Distribution > listDistributions (String catalogName , String dataset , String seriesMember ) {
466-
467559 String url = String .format (
468560 "%1scatalogs/%2s/datasets/%3s/datasetseries/%4s/distributions" ,
469561 this .rootURL , catalogName , dataset , seriesMember );
470- String json = this . api . callAPI (url );
562+ String json = callAPIWithPagination (url );
471563 return responseParser .parseDistributionResponse (json );
472564 }
473565
0 commit comments