bug6017: introduced a lock map for subscription handling

This commit is contained in:
Axel Uhl
2024-06-14 15:14:12 +02:00
parent 035bf742dd
commit a9f73d81e5
4 changed files with 67 additions and 43 deletions
@@ -1,11 +1,15 @@
package com.sap.sailing.server.gateway.subscription;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.Response;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
import com.sap.sse.security.shared.UserManagementException;
import com.sap.sse.security.shared.impl.User;
import com.sap.sse.security.shared.subscription.Subscription;
@@ -17,6 +21,13 @@ public abstract class SubscriptionWebHookHandler {
private static final Logger logger = Logger.getLogger(SubscriptionWebHookHandler.class.getName());
protected SubscriptionWebHookServlet context;
/**
* When working with a user's subscriptions, such as first reading, then changing and updating a user's subscription
* based on what was read, a user-specific write lock must be obtained to ensure that no writes can cut in between.
* See also {@link #lockSubscriptionsForUser} and {@link #unlockSubscriptionsForUser}.
*/
private final static ConcurrentMap<User, NamedReentrantReadWriteLock> subscriptionLocksForUsers = new ConcurrentHashMap<>();
/**
* Handle webhook
@@ -37,6 +48,14 @@ public abstract class SubscriptionWebHookHandler {
protected User getUser(String customerId) {
return context.getSecurityService().getUserByName(customerId);
}
protected void lockSubscriptionsForUser(final User user) {
LockUtil.lockForWrite(subscriptionLocksForUsers.computeIfAbsent(user, u->new NamedReentrantReadWriteLock("Subscriptions lock for user "+user.getName(), /* fair */ false)));
}
protected void unlockSubscriptionsForUser(final User user) {
LockUtil.unlockAfterWrite(subscriptionLocksForUsers.computeIfAbsent(user, u->new NamedReentrantReadWriteLock("Subscriptions lock for user "+user.getName(), /* fair */ false)));
}
protected void updateUserSubscription(User user, Subscription subscription) throws UserManagementException {
logger.info(() -> "Update subscription, user " + user.getName() + ", new subscription "
@@ -29,7 +29,7 @@ public class SubscriptionWebHookHandlerFactory {
}
/**
* Return webhook hanlder for request path, return null in case handler could not be found for the request
* Return webhook handler for request path, return null in case handler could not be found for the request
*/
public SubscriptionWebHookHandler getHandlerForPath(String path, SubscriptionWebHookServlet context) {
final Class<? extends SubscriptionWebHookHandler> handlerCls = handlers.get(path);
@@ -53,7 +53,7 @@ public class SubscriptionWebHookHandlerFactory {
}
private void registerHandler(Class<? extends SubscriptionWebHookHandler> handlerCls) {
SubscriptionWebHookHandler inst = createHandlerInstance(handlerCls);
final SubscriptionWebHookHandler inst = createHandlerInstance(handlerCls);
if (inst != null) {
handlers.put(inst.getHandlerPath(), handlerCls);
}
@@ -20,8 +20,8 @@ public class SubscriptionWebHookServlet extends SailingServerHttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String path = getRequestHandlerPath(request);
SubscriptionWebHookHandler handler = SubscriptionWebHookHandlerFactory.getInstance().getHandlerForPath(path,
final String path = getRequestHandlerPath(request);
final SubscriptionWebHookHandler handler = SubscriptionWebHookHandlerFactory.getInstance().getHandlerForPath(path,
this);
if (handler != null) {
handler.handle(request, response);
@@ -31,8 +31,8 @@ public class SubscriptionWebHookServlet extends SailingServerHttpServlet {
}
private String getRequestHandlerPath(HttpServletRequest request) {
String pathInfo = request.getPathInfo();
String[] pathParts = pathInfo.split("/");
final String pathInfo = request.getPathInfo();
final String[] pathParts = pathInfo.split("/");
return pathParts[pathParts.length - 1];
}
}
@@ -100,50 +100,55 @@ public class ChargebeeWebHookHandler extends SubscriptionWebHookHandler {
|| occuredAt.before(subscription.getManualUpdatedAt());
}
private void processEvent(SubscriptionWebHookEvent event, User user) throws UserManagementException {
private void processEvent(final SubscriptionWebHookEvent event, final User user) throws UserManagementException {
final SubscriptionWebHookEventType eventType = event.getEventType();
if (eventType != null) {
logger.info(() -> "Start process webhook event \"" + eventType.getName() + "\" for user " + user.getName());
final Subscription userSubscription = getCurrentUserSubscriptionFromEvent(user, event);
switch (eventType) {
case CUSTOMER_DELETED:
updateUserSubscription(user, buildEmptySubscription(userSubscription, event));
break;
case SUBSCRIPTION_DELETED:
if (userSubscription != null && userSubscription.getSubscriptionId() != null
&& userSubscription.getSubscriptionId().equals(event.getSubscriptionId())) {
lockSubscriptionsForUser(user);
try {
final Subscription userSubscription = getCurrentUserSubscriptionFromEvent(user, event);
switch (eventType) {
case CUSTOMER_DELETED:
updateUserSubscription(user, buildEmptySubscription(userSubscription, event));
}
break;
case SUBSCRIPTION_CREATED:
case SUBSCRIPTION_CHANGED:
case SUBSCRIPTION_ACTIVATED:
case PAYMENT_SUCCEEDED:
case PAYMENT_FAILED:
case SUBSCRIPTION_PAUSED:
case SUBSCRIPTION_RESUMED:
case SUBSCRIPTION_CANCELLED:
updateUserSubscription(user, buildSubscription(userSubscription, event));
break;
case PAYMENT_REFUNDED:
if (userSubscription.getInvoiceId() != null
&& userSubscription.getInvoiceId().equals(event.getInvoiceId())) {
break;
case SUBSCRIPTION_DELETED:
if (userSubscription != null && userSubscription.getSubscriptionId() != null
&& userSubscription.getSubscriptionId().equals(event.getSubscriptionId())) {
updateUserSubscription(user, buildEmptySubscription(userSubscription, event));
}
break;
case SUBSCRIPTION_CREATED:
case SUBSCRIPTION_CHANGED:
case SUBSCRIPTION_ACTIVATED:
case PAYMENT_SUCCEEDED:
case PAYMENT_FAILED:
case SUBSCRIPTION_PAUSED:
case SUBSCRIPTION_RESUMED:
case SUBSCRIPTION_CANCELLED:
updateUserSubscription(user, buildSubscription(userSubscription, event));
}
break;
case INVOICE_GENERATED:
updateSubscriptionInvoice(user, userSubscription, event);
break;
case INVOICE_UPDATED:
if (userSubscription != null && userSubscription.getInvoiceId() != null
&& userSubscription.getInvoiceId().equals(event.getInvoiceId())) {
break;
case PAYMENT_REFUNDED:
if (userSubscription.getInvoiceId() != null
&& userSubscription.getInvoiceId().equals(event.getInvoiceId())) {
updateUserSubscription(user, buildSubscription(userSubscription, event));
}
break;
case INVOICE_GENERATED:
updateSubscriptionInvoice(user, userSubscription, event);
break;
case INVOICE_UPDATED:
if (userSubscription != null && userSubscription.getInvoiceId() != null
&& userSubscription.getInvoiceId().equals(event.getInvoiceId())) {
updateSubscriptionInvoice(user, userSubscription, event);
}
break;
default:
logger.warning(
() -> "Webhook event type was unknown and will not be processed ");
break;
}
break;
default:
logger.warning(
() -> "Webhook event type was unknown and will not be processed ");
break;
} finally {
unlockSubscriptionsForUser(user);
}
logger.info(() -> "Webhook event \"" + eventType.getName() + "\" has been processed for user "
+ user.getName());