Merge branch 'master' into bug4811

This commit is contained in:
Axel Uhl committed 2021-02-01 09:48:26 +01:00
commit be6eaaa29a
14 files changed
+125 -65

No files matched your search

@@ -0,0 +1,30 @@
package com.sap.sse.security.subscription;
import com.sap.sse.common.Duration;
/**
* Service interface for provider API requests
*/
public interface SubscriptionApiBaseService {
/**
* The OSGi registry property name under which to look up a specific implementation of this API.
*/
String PROVIDER_NAME_OSGI_REGISTRY_KEY = "provider-name";
/**
* The name used for the {@link #PROVIDER_NAME_OSGI_REGISTRY_KEY} key during registration of this service with the
* OSGi service registry.
*/
String getProviderName();
/**
* A subscription provider may have API rate limits. This is the minimum time between starting two subsequent
* requests to the same subscription provider API.
*/
Duration getTimeBetweenApiRequestStart();
/**
* The delay after which to re-schedule a request that failed for having exceeded the service's rate limit
*/
Duration getLimitReachedResumeDelay();
}
@@ -4,10 +4,11 @@ package com.sap.sse.security.subscription;
* Subscription provider API request interface. A request needs to be queued and scheduled by
* {@code SubscriptionApiRequestProcessor}
*/
@FunctionalInterface
public interface SubscriptionApiRequest {
/**
* Handle sending API request to subscription provider service
*/
void run();
SubscriptionApiBaseService getSubscriptionApiBaseService();
}
@@ -1,5 +1,6 @@
package com.sap.sse.security.subscription;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
@@ -16,42 +17,33 @@ import com.sap.sse.common.TimePoint;
*/
public class SubscriptionApiRequestProcessorImpl implements SubscriptionApiRequestProcessor {
private static final Logger logger = Logger.getLogger(SubscriptionApiRequestProcessorImpl.class.getName());
/**
* Chargebee has API rate limits Threshold value for test site: ~750 API calls in 5 minutes. Threshold value for
* live site: ~150 API calls per site per minute. So to prevent the limit would be reached, a request has a frame of
* ~400ms, and a next request should be made after 400ms from previous request.
*/
private static final Duration TIME_BETWEEN_API_REQUEST_START = Duration.ONE_MILLISECOND.times(400);
/**
* The delay after which to re-schedule a request that failed for having exceeded the service's rate limit
*/
private static final Duration LIMIT_REACHED_RESUME_DELAY = Duration.ONE_MILLISECOND.times(65000);
private final ScheduledExecutorService executor;
/**
* The latest time point for which a request was scheduled by this processor using the {@link #executor}.
* {@code null} means that no request has been scheduled yet. Reading and updating this field needs to
* be thread safe and must be done while holding this object's monitor ({@code synchronized}). See the
* {@link #getDelayToNextRequestStartTimePoint()} and {@link #getDelayWhenRateLimitWasExceeded()}
* methods which implement this for regular requests and for requests that have to be re-scheduled
* after the rate limit has been exceeded (despite our attempts done here to respect the rate limits).
* The latest time point for which a request was scheduled by this processor using the {@link #executor}, keyed by
* the provider name (see {@link SubscriptionApiService#getProviderName()}). {@code null} means that no request has
* been scheduled yet. Reading and updating this field needs to be thread safe and must be done while holding this
* object's monitor ({@code synchronized}). See the {@link #getDelayToNextRequestStartTimePoint(String)} and
* {@link #getDelayWhenRateLimitWasExceeded(String)} methods which implement this for regular requests and for requests
* that have to be re-scheduled after the rate limit has been exceeded (despite our attempts done here to respect
* the rate limits).
*/
private TimePoint startOfLatestRequest;
private ConcurrentHashMap<String, TimePoint> startOfLatestRequestByProviderName;
public SubscriptionApiRequestProcessorImpl(ScheduledExecutorService executor) {
this.executor = executor;
this.startOfLatestRequestByProviderName = new ConcurrentHashMap<>();
}
@Override
public void addRequest(SubscriptionApiRequest request) {
scheduleRequest(request, getDelayToNextRequestStartTimePoint());
scheduleRequest(request, getDelayToNextRequestStartTimePoint(request.getSubscriptionApiBaseService()));
}
@Override
public void rescheduleRequestAfterRateLimitExceeded(SubscriptionApiRequest request) {
scheduleRequest(request, getDelayWhenRateLimitWasExceeded());
scheduleRequest(request, getDelayWhenRateLimitWasExceeded(request.getSubscriptionApiBaseService()));
}
/**
@@ -71,25 +63,29 @@ public class SubscriptionApiRequestProcessorImpl implements SubscriptionApiReque
}, delay.asMillis(), TimeUnit.MILLISECONDS);
}
private Duration getDelayWhenRateLimitWasExceeded() {
private Duration getDelayWhenRateLimitWasExceeded(SubscriptionApiBaseService subscriptionApiBaseService) {
final TimePoint now = TimePoint.now();
synchronized (this) {
startOfLatestRequest = now.plus(LIMIT_REACHED_RESUME_DELAY);
startOfLatestRequestByProviderName.put(subscriptionApiBaseService.getProviderName(),
now.plus(subscriptionApiBaseService.getLimitReachedResumeDelay()));
}
return LIMIT_REACHED_RESUME_DELAY;
return subscriptionApiBaseService.getLimitReachedResumeDelay();
}
private Duration getDelayToNextRequestStartTimePoint() {
private Duration getDelayToNextRequestStartTimePoint(SubscriptionApiBaseService subscriptionApiBaseService) {
final TimePoint now = TimePoint.now();
TimePoint startOfLatestRequest;
synchronized (this) {
if (startOfLatestRequest == null) {
if (!startOfLatestRequestByProviderName.containsKey(subscriptionApiBaseService.getProviderName())) {
startOfLatestRequest = now;
} else {
startOfLatestRequest = startOfLatestRequest.plus(TIME_BETWEEN_API_REQUEST_START);
startOfLatestRequest = startOfLatestRequestByProviderName
.get(subscriptionApiBaseService.getProviderName()).plus(subscriptionApiBaseService.getTimeBetweenApiRequestStart());
if (startOfLatestRequest.before(now)) {
startOfLatestRequest = now;
}
}
startOfLatestRequestByProviderName.put(subscriptionApiBaseService.getProviderName(), startOfLatestRequest);
}
return now.until(startOfLatestRequest);
}
@@ -6,7 +6,7 @@ import com.sap.sse.security.shared.subscription.Subscription;
/**
* Service interface for provider API requests
*/
public interface SubscriptionApiService {
public interface SubscriptionApiService extends SubscriptionApiBaseService {
public static interface OnSubscriptionsResultListener{
void onSubscriptionsResult(User user, Iterable<Subscription> subscriptions);
@@ -16,17 +16,6 @@ public interface SubscriptionApiService {
void onCancelResult(SubscriptionCancelResult cancelResult);
}
/**
* The OSGi registry property name under which to look up a specific implementation of this API.
*/
String PROVIDER_NAME_OSGI_REGISTRY_KEY = "provider-name";
/**
* The name used for the {@link #PROVIDER_NAME_OSGI_REGISTRY_KEY} key during registration of this service with the
* OSGi service registry.
*/
String getProviderName();
/**
* Fetch user subscriptions from payment service provider. The logic of fetching subscriptions should be done in
* background.
@@ -1,6 +1,7 @@
package com.sap.sse.security.subscription.chargebee;
import com.chargebee.ApiResponse;
import com.sap.sse.security.subscription.SubscriptionApiBaseService;
import com.sap.sse.security.subscription.SubscriptionApiRequest;
import com.sap.sse.security.subscription.SubscriptionApiRequestProcessor;
@@ -16,9 +17,11 @@ import com.sap.sse.security.subscription.SubscriptionApiRequestProcessor;
*/
public abstract class ChargebeeApiRequest implements SubscriptionApiRequest {
private final SubscriptionApiRequestProcessor requestProcessor;
private final SubscriptionApiBaseService chargebeeApiServiceParams;
protected ChargebeeApiRequest(SubscriptionApiRequestProcessor requestProcessor) {
protected ChargebeeApiRequest(SubscriptionApiRequestProcessor requestProcessor, SubscriptionApiBaseService chargebeeApiServiceParams) {
this.requestProcessor = requestProcessor;
this.chargebeeApiServiceParams = chargebeeApiServiceParams;
}
@Override
@@ -37,9 +40,14 @@ public abstract class ChargebeeApiRequest implements SubscriptionApiRequest {
}
}
}
protected abstract ChargebeeInternalApiRequestWrapper createRequest();
@Override
public SubscriptionApiBaseService getSubscriptionApiBaseService() {
return chargebeeApiServiceParams;
}
protected abstract void processResult(ChargebeeInternalApiRequestWrapper request);
protected abstract void handleError(Exception e);
@@ -1,6 +1,7 @@
package com.sap.sse.security.subscription.chargebee;
import com.chargebee.Environment;
import com.sap.sse.common.Duration;
import com.sap.sse.security.shared.impl.User;
import com.sap.sse.security.shared.subscription.chargebee.ChargebeeSubscriptionProvider;
import com.sap.sse.security.subscription.SubscriptionApiRequestProcessor;
@@ -11,6 +12,20 @@ public class ChargebeeApiService implements SubscriptionApiService {
private final boolean active;
private final SubscriptionApiRequestProcessor requestProcessor;
/**
* Chargebee has API rate limits Threshold value for test site: ~750 API calls in 5 minutes. Threshold value for
* live site: ~150 API calls per site per minute. So to prevent the limit would be reached, a request has a frame of
* ~400ms, and a next request should be made after 400ms from previous request.
*/
private static final Duration TIME_BETWEEN_API_REQUEST_START = Duration.ONE_MILLISECOND.times(400);
/**
* The delay after which to re-schedule a request that failed for having exceeded the service's rate limit
*/
private static final Duration LIMIT_REACHED_RESUME_DELAY = Duration.ONE_MILLISECOND.times(65000);
public ChargebeeApiService(ChargebeeConfiguration configuration, SubscriptionApiRequestProcessor requestProcessor) {
if (configuration != null) {
@@ -32,15 +47,25 @@ public class ChargebeeApiService implements SubscriptionApiService {
return ChargebeeSubscriptionProvider.PROVIDER_NAME;
}
@Override
public Duration getTimeBetweenApiRequestStart() {
return TIME_BETWEEN_API_REQUEST_START;
}
@Override
public Duration getLimitReachedResumeDelay() {
return LIMIT_REACHED_RESUME_DELAY;
}
@Override
public void getUserSubscriptions(User user, OnSubscriptionsResultListener listener) {
new ChargebeeFetchUserSubscriptionsTask(user, requestProcessor,
subscriptions -> listener.onSubscriptionsResult(user, subscriptions)).run();
subscriptions -> listener.onSubscriptionsResult(user, subscriptions), this).run();
}
@Override
public void cancelSubscription(String subscriptionId, OnCancelSubscriptionResultListener listener) {
new ChargebeeCancelSubscriptionTask(subscriptionId, requestProcessor, result -> listener.onCancelResult(result))
new ChargebeeCancelSubscriptionTask(subscriptionId, requestProcessor, result -> listener.onCancelResult(result), this)
.run();
}
@@ -8,6 +8,7 @@ import java.util.logging.Logger;
import com.chargebee.Result;
import com.chargebee.models.Subscription;
import com.chargebee.models.Subscription.CancelRequest;
import com.sap.sse.security.subscription.SubscriptionApiBaseService;
import com.sap.sse.security.subscription.SubscriptionApiRequestProcessor;
/**
@@ -24,8 +25,8 @@ public class ChargebeeCancelSubscriptionRequest extends ChargebeeApiRequest {
private final OnResultListener listener;
public ChargebeeCancelSubscriptionRequest(String subscriptionId, OnResultListener listener,
SubscriptionApiRequestProcessor requestProcessor) {
super(requestProcessor);
SubscriptionApiRequestProcessor requestProcessor, SubscriptionApiBaseService chargebeeApiServiceParams) {
super(requestProcessor, chargebeeApiServiceParams);
this.subscriptionId = subscriptionId;
this.listener = listener;
}
@@ -4,6 +4,7 @@ import java.util.logging.Logger;
import com.sap.sse.security.shared.subscription.Subscription;
import com.sap.sse.security.shared.subscription.chargebee.ChargebeeSubscription;
import com.sap.sse.security.subscription.SubscriptionApiBaseService;
import com.sap.sse.security.subscription.SubscriptionApiRequestProcessor;
import com.sap.sse.security.subscription.SubscriptionCancelResult;
@@ -25,17 +26,19 @@ public class ChargebeeCancelSubscriptionTask
private final String subscriptionId;
private final SubscriptionApiRequestProcessor requestProcessor;
private final OnResultListener listener;
private final SubscriptionApiBaseService chargebeeApiServiceParams;
public ChargebeeCancelSubscriptionTask(String subscriptionId, SubscriptionApiRequestProcessor requestProcessor,
OnResultListener listener) {
OnResultListener listener, SubscriptionApiBaseService chargebeeApiServiceParams) {
this.subscriptionId = subscriptionId;
this.requestProcessor = requestProcessor;
this.listener = listener;
this.chargebeeApiServiceParams = chargebeeApiServiceParams;
}
public void run() {
logger.info(() -> "Schedule cancel Chargebee subscription, id: " + subscriptionId);
requestProcessor.addRequest(new ChargebeeSubscriptionRequest(subscriptionId, this, requestProcessor));
requestProcessor.addRequest(new ChargebeeSubscriptionRequest(subscriptionId, this, requestProcessor, chargebeeApiServiceParams));
}
@Override
@@ -51,7 +54,7 @@ public class ChargebeeCancelSubscriptionTask
onDone(new SubscriptionCancelResult(/* success */ true, sub));
} else {
requestProcessor.addRequest(
new ChargebeeCancelSubscriptionRequest(subscriptionId, this, requestProcessor));
new ChargebeeCancelSubscriptionRequest(subscriptionId, this, requestProcessor, chargebeeApiServiceParams));
}
} else {
onDone(new SubscriptionCancelResult(/* success */false, /* subscription */null, /* deleted */true));
@@ -6,6 +6,7 @@ import com.chargebee.models.Invoice;
import com.chargebee.models.Subscription;
import com.chargebee.models.Transaction;
import com.sap.sse.security.shared.impl.User;
import com.sap.sse.security.subscription.SubscriptionApiBaseService;
import com.sap.sse.security.subscription.SubscriptionApiRequestProcessor;
/**
@@ -30,21 +31,22 @@ public class ChargebeeFetchSubscriptionInformationTask
private boolean hasTransaction;
private Invoice invoice;
private Transaction transaction;
private final SubscriptionApiBaseService chargebeeApiServiceParams;
public ChargebeeFetchSubscriptionInformationTask(User user, Subscription subscription, OnResultListener listener,
SubscriptionApiRequestProcessor requestProcessor) {
SubscriptionApiRequestProcessor requestProcessor, SubscriptionApiBaseService chargebeeApiServiceParams) {
this.user = user;
this.subscription = subscription;
this.listener = listener;
this.requestProcessor = requestProcessor;
this.chargebeeApiServiceParams = chargebeeApiServiceParams;
}
public void run() {
logger.info(() -> "Fetch Chargebee subscription information, user: " + user.getName() + ", subscription "
+ subscription.id());
requestProcessor.addRequest(new ChargebeeInvoiceRequest(user, subscription.id(), this, requestProcessor));
requestProcessor.addRequest(new ChargebeeTransactionRequest(user, subscription.id(), this, requestProcessor));
requestProcessor.addRequest(new ChargebeeInvoiceRequest(user, subscription.id(), this, requestProcessor, chargebeeApiServiceParams));
requestProcessor.addRequest(new ChargebeeTransactionRequest(user, subscription.id(), this, requestProcessor, chargebeeApiServiceParams));
}
@Override
@@ -7,6 +7,7 @@ import java.util.logging.Logger;
import com.sap.sse.security.shared.impl.User;
import com.sap.sse.security.shared.subscription.Subscription;
import com.sap.sse.security.subscription.SubscriptionApiBaseService;
import com.sap.sse.security.subscription.SubscriptionApiRequestProcessor;
public class ChargebeeFetchUserSubscriptionsTask implements ChargebeeSubscriptionListRequest.OnResultListener {
@@ -22,12 +23,14 @@ public class ChargebeeFetchUserSubscriptionsTask implements ChargebeeSubscriptio
private final OnResultListener listener;
private List<Subscription> userSubscriptions;
private final SubscriptionApiBaseService chargebeeApiServiceParams;
public ChargebeeFetchUserSubscriptionsTask(User user, SubscriptionApiRequestProcessor requestProcessor,
OnResultListener listener) {
OnResultListener listener, SubscriptionApiBaseService chargebeeApiServiceParams) {
this.user = user;
this.requestProcessor = requestProcessor;
this.listener = listener;
this.chargebeeApiServiceParams = chargebeeApiServiceParams;
}
public void run() {
@@ -41,12 +44,10 @@ public class ChargebeeFetchUserSubscriptionsTask implements ChargebeeSubscriptio
for (ChargebeeApiSubscriptionData sub : subscriptions) {
subscriptionList.add(sub.toSubscription());
}
// Sort subscription list by created date, so newest item goes first in the list
Collections.sort(subscriptionList, (s1, s2) -> {
return s1.getSubscriptionCreatedAt().compareTo(s2.getSubscriptionCreatedAt()) * -1;
});
if (userSubscriptions == null) {
userSubscriptions = new ArrayList<Subscription>();
}
@@ -62,7 +63,7 @@ public class ChargebeeFetchUserSubscriptionsTask implements ChargebeeSubscriptio
private void fetchSubscriptionList(String offset) {
logger.info(() -> "Schedule fetch Chargebee subscriptions, user: " + user.getName() + ", offset: "
+ (offset == null ? "" : offset));
requestProcessor.addRequest(new ChargebeeSubscriptionListRequest(user, offset, this, requestProcessor));
requestProcessor.addRequest(new ChargebeeSubscriptionListRequest(user, offset, this, requestProcessor, chargebeeApiServiceParams));
}
private void onDone() {
@@ -8,6 +8,7 @@ import com.chargebee.filters.enums.SortOrder;
import com.chargebee.models.Invoice;
import com.chargebee.models.Invoice.InvoiceListRequest;
import com.sap.sse.security.shared.impl.User;
import com.sap.sse.security.subscription.SubscriptionApiBaseService;
import com.sap.sse.security.subscription.SubscriptionApiRequestProcessor;
/**
@@ -28,8 +29,8 @@ public class ChargebeeInvoiceRequest extends ChargebeeApiRequest {
private final OnResultListener listener;
public ChargebeeInvoiceRequest(User user, String subscriptionId, OnResultListener listener,
SubscriptionApiRequestProcessor requestProcessor) {
super(requestProcessor);
SubscriptionApiRequestProcessor requestProcessor, SubscriptionApiBaseService chargebeeApiServiceParams) {
super(requestProcessor, chargebeeApiServiceParams);
this.user = user;
this.subscriptionId = subscriptionId;
this.listener = listener;
@@ -10,6 +10,7 @@ import com.chargebee.filters.enums.SortOrder;
import com.chargebee.models.Subscription;
import com.chargebee.models.Subscription.SubscriptionListRequest;
import com.sap.sse.security.shared.impl.User;
import com.sap.sse.security.subscription.SubscriptionApiBaseService;
import com.sap.sse.security.subscription.SubscriptionApiRequestProcessor;
/**
@@ -35,8 +36,8 @@ public class ChargebeeSubscriptionListRequest extends ChargebeeApiRequest
private int resultSize;
public ChargebeeSubscriptionListRequest(User user, String offset, OnResultListener listener,
SubscriptionApiRequestProcessor requestProcessor) {
super(requestProcessor);
SubscriptionApiRequestProcessor requestProcessor, SubscriptionApiBaseService chargebeeApiServiceParams) {
super(requestProcessor, chargebeeApiServiceParams);
this.user = user;
this.offset = offset;
this.listener = listener;
@@ -86,7 +87,7 @@ public class ChargebeeSubscriptionListRequest extends ChargebeeApiRequest
for (ListResult.Entry entry : result) {
Subscription subscription = entry.subscription();
if (!subscription.deleted()) {
new ChargebeeFetchSubscriptionInformationTask(user, subscription, this, getRequestProcessor()).run();
new ChargebeeFetchSubscriptionInformationTask(user, subscription, this, getRequestProcessor(), getSubscriptionApiBaseService()).run();
} else {
subscriptions.add(new ChargebeeApiSubscriptionData(subscription, null, null));
}
@@ -6,6 +6,7 @@ import java.util.logging.Logger;
import com.chargebee.ListResult;
import com.chargebee.models.Subscription;
import com.chargebee.models.Subscription.SubscriptionListRequest;
import com.sap.sse.security.subscription.SubscriptionApiBaseService;
import com.sap.sse.security.subscription.SubscriptionApiRequestProcessor;
/**
@@ -22,8 +23,8 @@ public class ChargebeeSubscriptionRequest extends ChargebeeApiRequest {
private final OnResultListener listener;
public ChargebeeSubscriptionRequest(String subscriptionId, OnResultListener listener,
SubscriptionApiRequestProcessor requestProcessor) {
super(requestProcessor);
SubscriptionApiRequestProcessor requestProcessor, SubscriptionApiBaseService chargebeeApiServiceParams) {
super(requestProcessor, chargebeeApiServiceParams);
this.subscriptionId = subscriptionId;
this.listener = listener;
}
@@ -8,6 +8,7 @@ import com.chargebee.filters.enums.SortOrder;
import com.chargebee.models.Transaction;
import com.chargebee.models.Transaction.TransactionListRequest;
import com.sap.sse.security.shared.impl.User;
import com.sap.sse.security.subscription.SubscriptionApiBaseService;
import com.sap.sse.security.subscription.SubscriptionApiRequestProcessor;
/**
@@ -25,8 +26,8 @@ public class ChargebeeTransactionRequest extends ChargebeeApiRequest {
private final OnResultListener listener;
public ChargebeeTransactionRequest(User user, String subscriptionId, OnResultListener listener,
SubscriptionApiRequestProcessor requestProcessor) {
super(requestProcessor);
SubscriptionApiRequestProcessor requestProcessor, SubscriptionApiBaseService chargebeeApiServiceParams) {
super(requestProcessor, chargebeeApiServiceParams);
this.user = user;
this.subscriptionId = subscriptionId;
this.listener = listener;