Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for auto-paginatable SearchResult type #1252

Merged
merged 3 commits into from
Aug 31, 2021
Merged
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
22 changes: 22 additions & 0 deletions src/main/java/com/stripe/model/SearchPagingIterable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.stripe.model;

import java.util.Iterator;

/**
* Provides an <code>{@code Iterable<T>}</code> target that automatically iterates across all API
* pages and which is suitable for use with a <code>{@code foreach}</code> loop.
*
* <p>Please note SearchPagingIterable is in beta and is subject to change or removal at any time.
*/
public class SearchPagingIterable<T> implements Iterable<T> {
private StripeSearchResultInterface<T> page;

SearchPagingIterable(final StripeSearchResultInterface<T> page) {
this.page = page;
}

@Override
public Iterator<T> iterator() {
return new SearchPagingIterator<>(page);
}
}
82 changes: 82 additions & 0 deletions src/main/java/com/stripe/model/SearchPagingIterator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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;

/** Please note SearchPagingIterator is in beta and is subject to change or removal at any time. */
public class SearchPagingIterator<T> extends ApiResource implements Iterator<T> {
private final String url;

@SuppressWarnings("rawtypes")
private final Class<? extends StripeSearchResultInterface> collectionType;

private StripeSearchResultInterface<T> currentSearchResult;
private Iterator<T> currentDataIterator;

private String nextPage;

SearchPagingIterator(final StripeSearchResultInterface<T> 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<String, Object> params = new HashMap<>();

// copy all the parameters from the initial request
Map<String, Object> initialParams = currentSearchResult.getRequestParams();
if (initialParams != null) {
params.putAll(initialParams);
}

// then put our new page start in
params.put("next_page", this.nextPage);

this.currentSearchResult = search(params, currentSearchResult.getRequestOptions());
this.nextPage = this.currentSearchResult.getNextPage();

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<T> search(
final Map<String, Object> params, final RequestOptions options) throws Exception {
return ApiResource.requestSearchResult(url, params, collectionType, options);
}
}
63 changes: 63 additions & 0 deletions src/main/java/com/stripe/model/StripeSearchResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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. Please
* note, StripeSearchResult is beta functionality and is subject to change or removal at any time.
*/
@Getter
@Setter
@EqualsAndHashCode(callSuper = false)
public abstract class StripeSearchResult<T> extends StripeObject
implements StripeSearchResultInterface<T> {
String object;

@Getter(onMethod_ = {@Override})
List<T> 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<String, Object> requestParams;

public Iterable<T> autoPagingIterable() {
return new SearchPagingIterable<>(this);
}

public Iterable<T> autoPagingIterable(Map<String, Object> 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<T> autoPagingIterable(Map<String, Object> params, RequestOptions options) {
this.setRequestOptions(options);
this.setRequestParams(params);
return new SearchPagingIterable<>(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +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<T> extends StripeCollectionInterface<T> {
String getNextPage();
}
30 changes: 30 additions & 0 deletions src/main/java/com/stripe/net/ApiResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -223,6 +224,35 @@ public static <T extends StripeCollectionInterface<?>> T requestCollection(
return collection;
}

public static <T extends StripeSearchResultInterface<?>> T requestSearchResult(
String url, ApiRequestParams params, Class<T> 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
*
* <p>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.
*
* <p>Please note, requestSearchResult is beta functionality and is subject to charge or removal
* at any time.
*/
public static <T extends StripeSearchResultInterface<?>> T requestSearchResult(
String url, Map<String, Object> params, Class<T> 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.
*
Expand Down