private static void deleteWithRetry(final BlockingSphereClient client, final Versioned<Product> unPublishedProduct, final int ttl) { if (ttl > 0) { try { client.executeBlocking(ProductDeleteCommand.of(unPublishedProduct)); } catch(final ConcurrentModificationException e) { final Versioned<Product> versioned = Versioned.of(unPublishedProduct.getId(), e.getCurrentVersion()); deleteWithRetry(client, versioned, ttl - 1); } } else { throw new RuntimeException("cannot delete product due to too much concurrent updates, product: " + unPublishedProduct); } }
/** * Gets the version of the object at the time of the failed command. * * {@include.example io.sphere.sdk.errors.SphereExceptionIntegrationTest#concurrentModification()} * * @return version or null */ @Nullable public Long getCurrentVersion() { final List<? extends SphereError> errors = getErrors(); return ObjectUtils.defaultIfNull(errors, Collections.emptyList()).stream() .map(errror -> (SphereError) errror) .filter(error -> ConcurrentModificationError.CODE.equals(error.getCode())) .map(error -> error.as(ConcurrentModificationError.class).getCurrentVersion()) .findFirst() .orElse(null); } }
public static ExceptionFactory of() { final ExceptionFactory exceptionFactory = new ExceptionFactory() .when(r -> isServiceNotAvailable(r), r -> new ServiceUnavailableException(extractBody(r))) .whenStatus(401, r -> { final String body = extractBody(r); return body.contains("invalid_token") ? new InvalidTokenException() : new UnauthorizedException(body); }) .whenStatus(500, r -> new InternalServerErrorException(extractBody(r))) .whenStatus(502, r -> new BadGatewayException(extractBody(r))) .whenStatus(503, r -> new ServiceUnavailableException(extractBody(r))) .whenStatus(504, r -> new GatewayTimeoutException(extractBody(r))) .whenStatus(409, r -> new ConcurrentModificationException()) .whenStatus(400, r -> { final ErrorResponse errorResponse = JsonUtils.readObject(ErrorResponse.typeReference(), r.getResponseBody().get()); return new ErrorResponseException(errorResponse); } ) .whenStatus(404, r -> new NotFoundException()) //default .when(response -> true, r -> new SphereException("Can't parse backend response.")); return exceptionFactory; }
public static ExceptionFactory of() { final ExceptionFactory exceptionFactory = new ExceptionFactory() .when(r -> isServiceNotAvailable(r), r -> new ServiceUnavailableException(extractBody(r))) .whenStatus(401, r -> { final String body = extractBody(r); return body.contains("invalid_token") ? new InvalidTokenException() : new UnauthorizedException(body,401); }) .whenStatus(403, r -> new ForbiddenException(extractBody(r))) .whenStatus(500, r -> new InternalServerErrorException(extractBody(r))) .whenStatus(502, r -> new BadGatewayException(extractBody(r))) .whenStatus(503, r -> new ServiceUnavailableException(extractBody(r))) .whenStatus(504, r -> new GatewayTimeoutException(extractBody(r))) .whenStatus(409, r -> { final ErrorResponse errorResponse = SphereJsonUtils.readObject(r.getResponseBody(), ErrorResponse.typeReference()); return new ConcurrentModificationException(errorResponse); }) .whenStatus(413, r -> new RequestEntityTooLargeException()) .whenStatus(400, r -> { final String body = extractBody(r); if (body.contains("invalid_scope")) { return new InvalidTokenException(); } final ErrorResponse errorResponse = SphereJsonUtils.readObject(r.getResponseBody(), ErrorResponse.typeReference()); return new ErrorResponseException(errorResponse); }) .whenStatus(404, r -> new NotFoundException()) //default .when(response -> true, r -> new SphereException("Can't parse backend response.")); return exceptionFactory; }
public static ExceptionFactory of() { final ExceptionFactory exceptionFactory = new ExceptionFactory() .when(r -> isServiceNotAvailable(r), r -> new ServiceUnavailableException(extractBody(r))) .whenStatus(401, r -> { final String body = extractBody(r); return body.contains("invalid_token") ? new InvalidTokenException() : new UnauthorizedException(body); }) .whenStatus(500, r -> new InternalServerErrorException(extractBody(r))) .whenStatus(502, r -> new BadGatewayException(extractBody(r))) .whenStatus(503, r -> new ServiceUnavailableException(extractBody(r))) .whenStatus(504, r -> new GatewayTimeoutException(extractBody(r))) .whenStatus(409, r -> new ConcurrentModificationException()) .whenStatus(413, r -> new RequestEntityTooLargeException()) .whenStatus(400, r -> { final ErrorResponse errorResponse = SphereJsonUtils.readObject(r.getResponseBody(), ErrorResponse.typeReference()); return new ErrorResponseException(errorResponse); } ) .whenStatus(404, r -> new NotFoundException()) //default .when(response -> true, r -> new SphereException("Can't parse backend response.")); return exceptionFactory; }
private static Product unpublishWithRetry(final BlockingSphereClient client, final Versioned<Product> product, final int ttl) { if (ttl > 0) { try { return client.executeBlocking(ProductUpdateCommand.of(product, Unpublish.of())); } catch(final ConcurrentModificationException e) { final Versioned<Product> versioned = Versioned.of(product.getId(), e.getCurrentVersion()); return unpublishWithRetry(client, versioned, ttl - 1); } } else { throw new RuntimeException("cannot unpublish product due to too much concurrent updates, product: " + product); } }
public static ExceptionFactory of() { final ExceptionFactory exceptionFactory = new ExceptionFactory() .when(r -> isServiceNotAvailable(r), r -> new ServiceUnavailableException(extractBody(r))) .whenStatus(401, r -> { final String body = extractBody(r); return body.contains("invalid_token") ? new InvalidTokenException() : new UnauthorizedException(body,401); }) .whenStatus(403, r -> new ForbiddenException(extractBody(r))) .whenStatus(500, r -> new InternalServerErrorException(extractBody(r))) .whenStatus(502, r -> new BadGatewayException(extractBody(r))) .whenStatus(503, r -> new ServiceUnavailableException(extractBody(r))) .whenStatus(504, r -> new GatewayTimeoutException(extractBody(r))) .whenStatus(409, r -> { final ErrorResponse errorResponse = SphereJsonUtils.readObject(r.getResponseBody(), ErrorResponse.typeReference()); return new ConcurrentModificationException(errorResponse); }) .whenStatus(413, r -> new RequestEntityTooLargeException()) .whenStatus(400, r -> { final String body = extractBody(r); if (body.contains("invalid_scope")) { return new InvalidTokenException(); } final ErrorResponse errorResponse = SphereJsonUtils.readObject(r.getResponseBody(), ErrorResponse.typeReference()); return new ErrorResponseException(errorResponse); }) .whenStatus(404, r -> new NotFoundException()) //default .when(response -> true, r -> new SphereException("Can't parse backend response.")); return exceptionFactory; }
@Test public void concurrentModification() throws Exception { withCategory(client(), cat -> { final CategoryUpdateCommand cmd = CategoryUpdateCommand.of(cat, ChangeName.of(LocalizedString.ofEnglish("new"))); final Category successfulUpdatedCategory = client().executeBlocking(cmd); final Throwable throwable = catchThrowable(() -> client().executeBlocking(cmd));//same command twice assertThat(throwable).isInstanceOf(ConcurrentModificationException.class); final ConcurrentModificationException exception = (ConcurrentModificationException) throwable; assertThat(exception.getCurrentVersion()) .isGreaterThanOrEqualTo(successfulUpdatedCategory.getVersion()); }); }
private static Predicate<RetryContext> isDeleteAndNewVersionIsKnown() { return retryContext -> retryContext.getLatestError() instanceof ConcurrentModificationException && ((ConcurrentModificationException) retryContext.getLatestError()).getCurrentVersion() != null && retryContext.getLatestParameter() instanceof SphereRequest && ((SphereRequest) retryContext.getLatestParameter()).httpRequestIntent().getHttpMethod() == HttpMethod.DELETE; }
@Test public void demoForBruteForceSolveTheVersionConflictWithExceptionCurrentVersion() { withTaxedProduct(client(), product -> { withCart(client(), cart -> { //when a customer clicks enter to cart final int variantId = 1; final int quantity = 2; final CartUpdateCommand cartUpdateCommand = CartUpdateCommand.of(cart, AddLineItem.of(product, variantId, quantity)); client().executeBlocking(cartUpdateCommand);//successful attempt in another thread/machine/container/tab //the operation will increase the version number of the cart //warning: ignoring version conflicts can be very poor behaviour on some use cases //we show this here if you want to execute the command anyway Cart updated2; try { updated2 = client().executeBlocking(cartUpdateCommand); } catch (final ConcurrentModificationException e) { final CartUpdateCommand commandWithCurrentCartVersion = cartUpdateCommand.withVersion(e.getCurrentVersion()); updated2 = client().executeBlocking(commandWithCurrentCartVersion); } return updated2; }); }); }