From b4f74c6cf53cfa5312cc8c7d2e0cedb03815fa43 Mon Sep 17 00:00:00 2001 From: Richard Marmorstein Date: Fri, 27 Aug 2021 12:58:12 -0400 Subject: [PATCH 1/3] Infra --- .../stripe/model/SearchPagingIterable.java | 20 +++++ .../stripe/model/SearchPagingIterator.java | 80 +++++++++++++++++++ .../com/stripe/model/StripeSearchResult.java | 60 ++++++++++++++ .../model/StripeSearchResultInterface.java | 5 ++ src/main/java/com/stripe/net/ApiResource.java | 27 +++++++ 5 files changed, 192 insertions(+) create mode 100644 src/main/java/com/stripe/model/SearchPagingIterable.java create mode 100644 src/main/java/com/stripe/model/SearchPagingIterator.java create mode 100644 src/main/java/com/stripe/model/StripeSearchResult.java create mode 100644 src/main/java/com/stripe/model/StripeSearchResultInterface.java diff --git a/src/main/java/com/stripe/model/SearchPagingIterable.java b/src/main/java/com/stripe/model/SearchPagingIterable.java new file mode 100644 index 00000000000..6c8d0cd5334 --- /dev/null +++ b/src/main/java/com/stripe/model/SearchPagingIterable.java @@ -0,0 +1,20 @@ +package com.stripe.model; + +import java.util.Iterator; + +/** + * Provides an {@code Iterable} target that automatically iterates across all API + * pages and which is suitable for use with a {@code foreach} loop. + */ +public class SearchPagingIterable implements Iterable { + private StripeSearchResultInterface page; + + SearchPagingIterable(final StripeSearchResultInterface page) { + this.page = page; + } + + @Override + public Iterator iterator() { + return new SearchPagingIterator<>(page); + } +} diff --git a/src/main/java/com/stripe/model/SearchPagingIterator.java b/src/main/java/com/stripe/model/SearchPagingIterator.java new file mode 100644 index 00000000000..27ad40c3cee --- /dev/null +++ b/src/main/java/com/stripe/model/SearchPagingIterator.java @@ -0,0 +1,80 @@ +package com.stripe.model; + +import com.stripe.Stripe; +import com.stripe.net.ApiResource; +import com.stripe.net.RequestOptions; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; + +public class SearchPagingIterator extends ApiResource implements Iterator { + private final String url; + + @SuppressWarnings("rawtypes") + private final Class collectionType; + + private StripeSearchResultInterface currentSearchResult; + private Iterator currentDataIterator; + + private String nextPage; + + SearchPagingIterator(final StripeSearchResultInterface stripeSearchResult) { + this.url = Stripe.getApiBase() + stripeSearchResult.getUrl(); + this.nextPage = stripeSearchResult.getNextPage(); + + this.collectionType = stripeSearchResult.getClass(); + + this.currentSearchResult = stripeSearchResult; + this.currentDataIterator = stripeSearchResult.getData().iterator(); + } + + @Override + public boolean hasNext() { + return currentDataIterator.hasNext() || currentSearchResult.getHasMore(); + } + + @Override + public T next() { + // if we've run out of data on the current page, try to fetch another + // one + if (!currentDataIterator.hasNext() && currentSearchResult.getHasMore()) { + try { + Map params = new HashMap<>(); + + // copy all the parameters from the initial request + Map initialParams = currentSearchResult.getRequestParams(); + if (initialParams != null) { + params.putAll(initialParams); + } + + // then put our new page start in + params.put("next_page", this.nextPage); + + this.currentSearchResult = list(params, currentSearchResult.getRequestOptions()); + + this.currentDataIterator = currentSearchResult.getData().iterator(); + } catch (final Exception e) { + throw new RuntimeException("Unable to lazy-load stripe objects", e); + } + } + + if (currentDataIterator.hasNext()) { + final T next = currentDataIterator.next(); + return next; + } + + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + private StripeSearchResultInterface list( + final Map params, final RequestOptions options) throws Exception { + return ApiResource.requestSearchResult(url, params, collectionType, options); + } +} diff --git a/src/main/java/com/stripe/model/StripeSearchResult.java b/src/main/java/com/stripe/model/StripeSearchResult.java new file mode 100644 index 00000000000..e465fcc1459 --- /dev/null +++ b/src/main/java/com/stripe/model/StripeSearchResult.java @@ -0,0 +1,60 @@ +package com.stripe.model; + +import com.stripe.net.RequestOptions; +import java.util.List; +import java.util.Map; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** Provides a representation of a single page worth of data from a Stripe API search method. */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +public abstract class StripeSearchResult extends StripeObject + implements StripeSearchResultInterface { + String object; + + @Getter(onMethod_ = {@Override}) + List data; + + @Getter(onMethod_ = {@Override}) + Boolean hasMore; + + @Getter(onMethod_ = {@Override}) + String url; + + @Getter(onMethod_ = {@Override}) + String nextPage; + + @Getter(onMethod_ = {@Override}) + @Setter(onMethod = @__({@Override})) + private transient RequestOptions requestOptions; + + @Getter(onMethod_ = {@Override}) + @Setter(onMethod = @__({@Override})) + private Map requestParams; + + public Iterable autoPagingIterable() { + return new SearchPagingIterable<>(this); + } + + public Iterable autoPagingIterable(Map params) { + this.setRequestParams(params); + return new SearchPagingIterable<>(this); + } + + /** + * Constructs an iterable that can be used to iterate across all objects across all pages. As page + * boundaries are encountered, the next page will be fetched automatically for continued + * iteration. + * + * @param params request parameters (will override the parameters from the initial list request) + * @param options request options (will override the options from the initial list request) + */ + public Iterable autoPagingIterable(Map params, RequestOptions options) { + this.setRequestOptions(options); + this.setRequestParams(params); + return new SearchPagingIterable<>(this); + } +} diff --git a/src/main/java/com/stripe/model/StripeSearchResultInterface.java b/src/main/java/com/stripe/model/StripeSearchResultInterface.java new file mode 100644 index 00000000000..ae121bdc6f5 --- /dev/null +++ b/src/main/java/com/stripe/model/StripeSearchResultInterface.java @@ -0,0 +1,5 @@ +package com.stripe.model; + +public interface StripeSearchResultInterface extends StripeCollectionInterface { + String getNextPage(); +} diff --git a/src/main/java/com/stripe/net/ApiResource.java b/src/main/java/com/stripe/net/ApiResource.java index c23e7a5beed..804b113c02d 100644 --- a/src/main/java/com/stripe/net/ApiResource.java +++ b/src/main/java/com/stripe/net/ApiResource.java @@ -21,6 +21,7 @@ import com.stripe.model.StripeObjectInterface; import com.stripe.model.StripeRawJsonObject; import com.stripe.model.StripeRawJsonObjectDeserializer; +import com.stripe.model.StripeSearchResultInterface; import com.stripe.util.StringUtils; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -223,6 +224,32 @@ public static > T requestCollection( return collection; } + public static > T requestSearchResult( + String url, ApiRequestParams params, Class clazz, RequestOptions options) + throws StripeException { + checkNullTypedParams(url, params); + return requestSearchResult(url, params.toMap(), clazz, options); + } + + /** + * Similar to #request, but specific for use with searchResult types that come from the API + * + *

Collections need a little extra work because we need to plumb request options and params + * through so that we can iterate to the next page if necessary. + */ + public static > T requestSearchResult( + String url, Map params, Class clazz, RequestOptions options) + throws StripeException { + T searchResult = request(RequestMethod.GET, url, params, clazz, options); + + if (searchResult != null) { + searchResult.setRequestOptions(options); + searchResult.setRequestParams(params); + } + + return searchResult; + } + /** * Invalidate null typed parameters. * From 7789dfa85a2487196eeff3c42b4843baf4af97d0 Mon Sep 17 00:00:00 2001 From: Richard Marmorstein Date: Tue, 31 Aug 2021 10:40:15 -0400 Subject: [PATCH 2/3] Fix iterator --- src/main/java/com/stripe/model/SearchPagingIterator.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/stripe/model/SearchPagingIterator.java b/src/main/java/com/stripe/model/SearchPagingIterator.java index 27ad40c3cee..9f04a28525e 100644 --- a/src/main/java/com/stripe/model/SearchPagingIterator.java +++ b/src/main/java/com/stripe/model/SearchPagingIterator.java @@ -51,7 +51,8 @@ public T next() { // then put our new page start in params.put("next_page", this.nextPage); - this.currentSearchResult = list(params, currentSearchResult.getRequestOptions()); + this.currentSearchResult = search(params, currentSearchResult.getRequestOptions()); + this.nextPage = this.currentSearchResult.getNextPage(); this.currentDataIterator = currentSearchResult.getData().iterator(); } catch (final Exception e) { @@ -73,7 +74,7 @@ public void remove() { } @SuppressWarnings("unchecked") - private StripeSearchResultInterface list( + private StripeSearchResultInterface search( final Map params, final RequestOptions options) throws Exception { return ApiResource.requestSearchResult(url, params, collectionType, options); } From a155d4ce88df606874bfd6c4dd1433aa091c4e9c Mon Sep 17 00:00:00 2001 From: Richard Marmorstein Date: Tue, 31 Aug 2021 10:51:25 -0400 Subject: [PATCH 3/3] Comments --- src/main/java/com/stripe/model/SearchPagingIterable.java | 2 ++ src/main/java/com/stripe/model/SearchPagingIterator.java | 1 + src/main/java/com/stripe/model/StripeSearchResult.java | 5 ++++- .../java/com/stripe/model/StripeSearchResultInterface.java | 4 ++++ src/main/java/com/stripe/net/ApiResource.java | 7 +++++-- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/stripe/model/SearchPagingIterable.java b/src/main/java/com/stripe/model/SearchPagingIterable.java index 6c8d0cd5334..f99803d35ff 100644 --- a/src/main/java/com/stripe/model/SearchPagingIterable.java +++ b/src/main/java/com/stripe/model/SearchPagingIterable.java @@ -5,6 +5,8 @@ /** * Provides an {@code Iterable} target that automatically iterates across all API * pages and which is suitable for use with a {@code foreach} loop. + * + *

Please note SearchPagingIterable is in beta and is subject to change or removal at any time. */ public class SearchPagingIterable implements Iterable { private StripeSearchResultInterface page; diff --git a/src/main/java/com/stripe/model/SearchPagingIterator.java b/src/main/java/com/stripe/model/SearchPagingIterator.java index 9f04a28525e..1e45b76ad5d 100644 --- a/src/main/java/com/stripe/model/SearchPagingIterator.java +++ b/src/main/java/com/stripe/model/SearchPagingIterator.java @@ -8,6 +8,7 @@ import java.util.Map; import java.util.NoSuchElementException; +/** Please note SearchPagingIterator is in beta and is subject to change or removal at any time. */ public class SearchPagingIterator extends ApiResource implements Iterator { private final String url; diff --git a/src/main/java/com/stripe/model/StripeSearchResult.java b/src/main/java/com/stripe/model/StripeSearchResult.java index e465fcc1459..8c0c563f61a 100644 --- a/src/main/java/com/stripe/model/StripeSearchResult.java +++ b/src/main/java/com/stripe/model/StripeSearchResult.java @@ -7,7 +7,10 @@ import lombok.Getter; import lombok.Setter; -/** Provides a representation of a single page worth of data from a Stripe API search method. */ +/** + * Provides a representation of a single page worth of data from a Stripe API search method. Please + * note, StripeSearchResult is beta functionality and is subject to change or removal at any time. + */ @Getter @Setter @EqualsAndHashCode(callSuper = false) diff --git a/src/main/java/com/stripe/model/StripeSearchResultInterface.java b/src/main/java/com/stripe/model/StripeSearchResultInterface.java index ae121bdc6f5..01caf0915cd 100644 --- a/src/main/java/com/stripe/model/StripeSearchResultInterface.java +++ b/src/main/java/com/stripe/model/StripeSearchResultInterface.java @@ -1,5 +1,9 @@ package com.stripe.model; +/** + * Please note, StripeSearchResultInterface is beta functionality and is subject to change or + * removal at any time. + */ public interface StripeSearchResultInterface extends StripeCollectionInterface { String getNextPage(); } diff --git a/src/main/java/com/stripe/net/ApiResource.java b/src/main/java/com/stripe/net/ApiResource.java index 804b113c02d..0b241340f70 100644 --- a/src/main/java/com/stripe/net/ApiResource.java +++ b/src/main/java/com/stripe/net/ApiResource.java @@ -234,8 +234,11 @@ public static > T requestSearchResult( /** * Similar to #request, but specific for use with searchResult types that come from the API * - *

Collections need a little extra work because we need to plumb request options and params - * through so that we can iterate to the next page if necessary. + *

SearchResults, like collections need a little extra work because we need to plumb request + * options and params through so that we can iterate to the next page if necessary. + * + *

Please note, requestSearchResult is beta functionality and is subject to charge or removal + * at any time. */ public static > T requestSearchResult( String url, Map params, Class clazz, RequestOptions options)