Deadline processing updated

This commit is contained in:
echerniak 2021-09-16 19:51:53 +03:00
parent d41f2a4699
commit c5c97c13d5
No known key found for this signature in database
GPG Key ID: 7D79B3A9CB749B36
9 changed files with 461 additions and 118 deletions

48
pom.xml
View File

@ -78,6 +78,54 @@
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.5.2</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>15.0.2</version>
<exclusions>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-security-adapter</artifactId>
<version>15.0.2</version>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>

View File

@ -1,20 +0,0 @@
package com.rbkmoney.anapi.v2.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
return new ThreadPoolTaskExecutor();
}
}

View File

@ -0,0 +1,135 @@
package com.rbkmoney.anapi.v2.config;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.util.StringUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
@Configuration
@EnableWebSecurity
@ComponentScan(
basePackageClasses = KeycloakSecurityComponents.class,
excludeFilters = @ComponentScan.Filter(
type = FilterType.REGEX,
pattern = "org.keycloak.adapters.springsecurity.management.HttpSessionManager"))
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
@ConditionalOnProperty(value = "auth.enabled", havingValue = "true")
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Value("${keycloak.realm}")
private String keycloakRealmName;
@Value("${keycloak.resource}")
private String keycloakResourceName;
@Value("${keycloak.realm-public-key}")
private String keycloakRealmPublicKey;
@Value("${keycloak.realm-public-key.file-path:}")
private String keycloakRealmPublicKeyFile;
@Value("${keycloak.auth-server-url}")
private String keycloakAuthServerUrl;
@Value("${keycloak.ssl-required}")
private String keycloakSSLRequired;
@Value("${keycloak.not-before}")
private int keycloakTokenNotBefore;
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers(HttpMethod.GET, "/**/health").permitAll()
.anyRequest().authenticated();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
@Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return facade -> {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(adapterConfig());
deployment.setNotBefore(keycloakTokenNotBefore);
return deployment;
};
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.applyPermitDefaultValues();
configuration.addAllowedMethod(HttpMethod.PUT);
configuration.addAllowedMethod(HttpMethod.DELETE);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
private AdapterConfig adapterConfig() {
if (StringUtils.hasLength(keycloakRealmPublicKeyFile)) {
keycloakRealmPublicKey = readKeyFromFile(keycloakRealmPublicKeyFile);
}
AdapterConfig adapterConfig = new AdapterConfig();
adapterConfig.setRealm(keycloakRealmName);
adapterConfig.setRealmKey(keycloakRealmPublicKey);
adapterConfig.setResource(keycloakResourceName);
adapterConfig.setAuthServerUrl(keycloakAuthServerUrl);
adapterConfig.setUseResourceRoleMappings(true);
adapterConfig.setBearerOnly(true);
adapterConfig.setSslRequired(keycloakSSLRequired);
return adapterConfig;
}
private String readKeyFromFile(String filePath) {
try {
List<String> strings = Files.readAllLines(Paths.get(filePath));
strings.remove(strings.size() - 1);
strings.remove(0);
return strings.stream().map(String::trim).collect(Collectors.joining());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}

View File

@ -0,0 +1,105 @@
package com.rbkmoney.anapi.v2.config;
import com.rbkmoney.woody.api.flow.WFlow;
import com.rbkmoney.woody.api.trace.context.metadata.user.UserIdentityEmailExtensionKit;
import com.rbkmoney.woody.api.trace.context.metadata.user.UserIdentityIdExtensionKit;
import com.rbkmoney.woody.api.trace.context.metadata.user.UserIdentityRealmExtensionKit;
import com.rbkmoney.woody.api.trace.context.metadata.user.UserIdentityUsernameExtensionKit;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.keycloak.representations.AccessToken;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import static com.rbkmoney.anapi.v2.util.DeadlineUtils.*;
import static com.rbkmoney.woody.api.trace.ContextUtils.setCustomMetadataValue;
import static com.rbkmoney.woody.api.trace.ContextUtils.setDeadline;
@Configuration
@SuppressWarnings({"ParameterName", "LocalVariableName"})
public class WebConfig {
@Bean
public FilterRegistrationBean woodyFilter() {
WFlow woodyFlow = new WFlow();
Filter filter = new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
woodyFlow.createServiceFork(
() -> {
try {
if (request.getUserPrincipal() != null) {
addWoodyContext(request.getUserPrincipal());
}
setWoodyDeadline(request);
filterChain.doFilter(request, response);
} catch (IOException | ServletException e) {
sneakyThrow(e);
}
}
)
.run();
}
private <E extends Throwable, T> T sneakyThrow(Throwable t) throws E {
throw (E) t;
}
};
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.setOrder(-50);
filterRegistrationBean.setName("woodyFilter");
filterRegistrationBean.addUrlPatterns("*");
return filterRegistrationBean;
}
private void addWoodyContext(Principal principal) {
KeycloakSecurityContext keycloakSecurityContext =
((KeycloakAuthenticationToken) principal).getAccount().getKeycloakSecurityContext();
AccessToken accessToken = keycloakSecurityContext.getToken();
setCustomMetadataValue(UserIdentityIdExtensionKit.KEY, accessToken.getSubject());
setCustomMetadataValue(UserIdentityUsernameExtensionKit.KEY, accessToken.getPreferredUsername());
setCustomMetadataValue(UserIdentityEmailExtensionKit.KEY, accessToken.getEmail());
setCustomMetadataValue(UserIdentityRealmExtensionKit.KEY, keycloakSecurityContext.getRealm());
}
private void setWoodyDeadline(HttpServletRequest request) {
String xRequestDeadline = request.getHeader("X-Request-Deadline");
String xRequestId = request.getHeader("X-Request-ID");
if (xRequestDeadline != null) {
setDeadline(getInstant(xRequestDeadline, xRequestId));
}
}
private Instant getInstant(String xRequestDeadline, String xRequestId) {
Instant instant;
if (containsRelativeValues(xRequestDeadline, xRequestId)) {
instant = Instant.now()
.plus(extractMilliseconds(xRequestDeadline, xRequestId), ChronoUnit.MILLIS)
.plus(extractSeconds(xRequestDeadline, xRequestId), ChronoUnit.MILLIS)
.plus(extractMinutes(xRequestDeadline, xRequestId), ChronoUnit.MILLIS);
} else {
instant = Instant.parse(xRequestDeadline);
}
return instant;
}
}

View File

@ -25,15 +25,16 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import static com.rbkmoney.anapi.v2.util.CommonUtil.getRequestDeadlineMillis;
import static com.rbkmoney.anapi.v2.util.DeadlineUtils.checkDeadline;
@Slf4j
@Controller
@RequiredArgsConstructor
@SuppressWarnings("ParameterName")
public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesApi, PayoutsApi, RefundsApi {
private final SearchService searchService;
@ -50,12 +51,12 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
@SneakyThrows
@Override
public ResponseEntity<InlineResponse20010> searchPayments(String xrequestID,
public ResponseEntity<InlineResponse20010> searchPayments(String xRequestID,
@NotNull @Size(min = 1, max = 40) @Valid String partyID,
@NotNull @Valid OffsetDateTime fromTime,
@NotNull @Valid OffsetDateTime toTime,
@NotNull @Min(1L) @Max(1000L) @Valid Integer limit,
String xrequestDeadline,
String xRequestDeadline,
@Size(min = 1, max = 40) @Valid String shopID,
@Valid List<String> shopIDs,
@Valid String paymentInstitutionRealm,
@ -80,6 +81,7 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
@Min(1L) @Valid Long paymentAmountTo,
@Valid List<String> excludedShops,
@Valid String continuationToken) {
checkDeadline(xRequestDeadline, xRequestID);
PaymentSearchQuery query = paymentSearchConverter.convert(partyID,
fromTime,
toTime,
@ -108,14 +110,7 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
paymentAmountTo,
excludedShops,
continuationToken);
InlineResponse20010 response;
if (xrequestDeadline != null) {
response = searchService
.findPayments(query)
.get(getRequestDeadlineMillis(xrequestDeadline), TimeUnit.MILLISECONDS);
} else {
response = searchService.findPayments(query).get();
}
InlineResponse20010 response = searchService.findPayments(query);
return ResponseEntity.ok(response);
}
@ -125,12 +120,12 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
produces = {"application/json; charset=utf-8"}
)
@Override
public ResponseEntity<InlineResponse2008> searchChargebacks(String xrequestID,
public ResponseEntity<InlineResponse2008> searchChargebacks(String xRequestID,
@NotNull @Size(min = 1, max = 40) @Valid String partyID,
@NotNull @Valid OffsetDateTime fromTime,
@NotNull @Valid OffsetDateTime toTime,
@NotNull @Min(1L) @Max(1000L) @Valid Integer limit,
String xrequestDeadline,
String xRequestDeadline,
@Size(min = 1, max = 40) @Valid String shopID,
@Valid List<String> shopIDs,
@Valid String paymentInstitutionRealm,
@ -143,6 +138,7 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
@Valid List<String> chargebackCategories,
@Valid String continuationToken) {
//TODO: clarify mapping for paymentInstitutionRealm, xrequestID, xrequestDeadline, offset
checkDeadline(xRequestDeadline, xRequestID);
ChargebackSearchQuery query = chargebackSearchConverter.convert(partyID,
fromTime,
toTime,
@ -158,14 +154,8 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
chargebackStages,
chargebackCategories,
continuationToken);
InlineResponse2008 response;
if (xrequestDeadline != null) {
response = searchService
.findChargebacks(query)
.get(getRequestDeadlineMillis(xrequestDeadline), TimeUnit.MILLISECONDS);
} else {
response = searchService.findChargebacks(query).get();
}
InlineResponse2008 response = searchService
.findChargebacks(query);
return ResponseEntity.ok(response);
}
@ -175,12 +165,12 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
produces = {"application/json; charset=utf-8"}
)
@Override
public ResponseEntity<InlineResponse2009> searchInvoices(String xrequestID,
public ResponseEntity<InlineResponse2009> searchInvoices(String xRequestID,
@NotNull @Size(min = 1, max = 40) @Valid String partyID,
@NotNull @Valid OffsetDateTime fromTime,
@NotNull @Valid OffsetDateTime toTime,
@NotNull @Min(1L) @Max(1000L) @Valid Integer limit,
String xrequestDeadline,
String xRequestDeadline,
@Size(min = 1, max = 40) @Valid String shopID,
@Valid List<String> shopIDs,
@Valid String paymentInstitutionRealm,
@ -193,6 +183,7 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
@Valid List<String> excludedShops,
@Valid String continuationToken) {
//TODO: clarify mapping for paymentInstitutionRealm, xrequestID, xrequestDeadline, excludedShops
checkDeadline(xRequestDeadline, xRequestID);
InvoiceSearchQuery query = invoiceSearchConverter.convert(partyID,
fromTime,
toTime,
@ -208,14 +199,7 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
invoiceAmountTo,
excludedShops,
continuationToken);
InlineResponse2009 response;
if (xrequestDeadline != null) {
response = searchService
.findInvoices(query)
.get(getRequestDeadlineMillis(xrequestDeadline), TimeUnit.MILLISECONDS);
} else {
response = searchService.findInvoices(query).get();
}
InlineResponse2009 response = searchService.findInvoices(query);
return ResponseEntity.ok(response);
}
@ -225,12 +209,12 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
produces = {"application/json; charset=utf-8"}
)
@Override
public ResponseEntity<InlineResponse20011> searchPayouts(String xrequestID,
public ResponseEntity<InlineResponse20011> searchPayouts(String xRequestID,
@NotNull @Size(min = 1, max = 40) @Valid String partyID,
@NotNull @Valid OffsetDateTime fromTime,
@NotNull @Valid OffsetDateTime toTime,
@NotNull @Min(1L) @Max(1000L) @Valid Integer limit,
String xrequestDeadline,
String xRequestDeadline,
@Size(min = 1, max = 40) @Valid String shopID,
@Valid List<String> shopIDs,
@Valid String paymentInstitutionRealm,
@ -241,6 +225,7 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
@Valid String continuationToken) {
//TODO: clarify mapping for paymentInstitutionRealm, xrequestID, xrequestDeadline, excludedShops,
//offset + setStatuses
checkDeadline(xRequestDeadline, xRequestID);
PayoutSearchQuery query = payoutSearchConverter.convert(partyID,
fromTime,
toTime,
@ -253,14 +238,7 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
payoutToolType,
excludedShops,
continuationToken);
InlineResponse20011 response;
if (xrequestDeadline != null) {
response = searchService
.findPayouts(query)
.get(getRequestDeadlineMillis(xrequestDeadline), TimeUnit.MILLISECONDS);
} else {
response = searchService.findPayouts(query).get();
}
InlineResponse20011 response = searchService.findPayouts(query);
return ResponseEntity.ok(response);
}
@ -270,12 +248,12 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
produces = {"application/json; charset=utf-8"}
)
@Override
public ResponseEntity<InlineResponse20012> searchRefunds(String xrequestID,
public ResponseEntity<InlineResponse20012> searchRefunds(String xRequestID,
@NotNull @Size(min = 1, max = 40) @Valid String partyID,
@NotNull @Valid OffsetDateTime fromTime,
@NotNull @Valid OffsetDateTime toTime,
@NotNull @Min(1L) @Max(1000L) @Valid Integer limit,
String xrequestDeadline,
String xRequestDeadline,
@Size(min = 1, max = 40) @Valid String shopID,
@Valid List<String> shopIDs,
@Valid String paymentInstitutionRealm,
@ -289,6 +267,7 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
@Valid List<String> excludedShops,
@Valid String continuationToken) {
//TODO: clarify mapping for paymentInstitutionRealm, xrequestID, xrequestDeadline, excludedShops, offset
checkDeadline(xRequestDeadline, xRequestID);
RefundSearchQuery query = refundSearchConverter.convert(partyID,
fromTime,
toTime,
@ -306,14 +285,7 @@ public class SearchController implements PaymentsApi, ChargebacksApi, InvoicesAp
excludedShops,
continuationToken);
InlineResponse20012 response;
if (xrequestDeadline != null) {
response = searchService
.findRefunds(query)
.get(getRequestDeadlineMillis(xrequestDeadline), TimeUnit.MILLISECONDS);
} else {
response = searchService.findRefunds(query).get();
}
InlineResponse20012 response = searchService.findRefunds(query);
return ResponseEntity.ok(response);
}

View File

@ -0,0 +1,23 @@
package com.rbkmoney.anapi.v2.exception;
public class DeadlineException extends RuntimeException {
public DeadlineException() {
}
public DeadlineException(String message) {
super(message);
}
public DeadlineException(String message, Throwable cause) {
super(message, cause);
}
public DeadlineException(Throwable cause) {
super(cause);
}
public DeadlineException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -5,11 +5,8 @@ import com.rbkmoney.magista.*;
import com.rbkmoney.openapi.anapi_v2.model.*;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
@Service
@ -23,60 +20,55 @@ public class SearchService {
private final StatPayoutToPayoutConverter payoutResponseConverter;
private final StatRefundToRefundSearchResultConverter refundResponseConverter;
@Async
@SneakyThrows
public Future<InlineResponse20010> findPayments(PaymentSearchQuery query) {
public InlineResponse20010 findPayments(PaymentSearchQuery query) {
StatPaymentResponse magistaResponse = magistaClient.searchPayments(query);
return new AsyncResult<>(new InlineResponse20010()
return new InlineResponse20010()
.result(magistaResponse.getPayments().stream()
.map(paymentResponseConverter::convert)
.collect(Collectors.toList()))
.continuationToken(magistaResponse.getContinuationToken()));
.continuationToken(magistaResponse.getContinuationToken());
}
@Async
@SneakyThrows
public Future<InlineResponse2008> findChargebacks(ChargebackSearchQuery query) {
public InlineResponse2008 findChargebacks(ChargebackSearchQuery query) {
StatChargebackResponse magistaResponse = magistaClient.searchChargebacks(query);
return new AsyncResult<>(new InlineResponse2008()
return new InlineResponse2008()
.result(magistaResponse.getChargebacks().stream()
.map(chargebackResponseConverter::convert)
.collect(Collectors.toList()))
.continuationToken(magistaResponse.getContinuationToken()));
.continuationToken(magistaResponse.getContinuationToken());
}
@Async
@SneakyThrows
public Future<InlineResponse2009> findInvoices(InvoiceSearchQuery query) {
public InlineResponse2009 findInvoices(InvoiceSearchQuery query) {
StatInvoiceResponse magistaResponse = magistaClient.searchInvoices(query);
return new AsyncResult<>(new InlineResponse2009()
return new InlineResponse2009()
.result(magistaResponse.getInvoices().stream()
.map(invoiceResponseConverter::convert)
.collect(Collectors.toList()))
.continuationToken(magistaResponse.getContinuationToken()));
.continuationToken(magistaResponse.getContinuationToken());
}
@Async
@SneakyThrows
public Future<InlineResponse20011> findPayouts(PayoutSearchQuery query) {
public InlineResponse20011 findPayouts(PayoutSearchQuery query) {
StatPayoutResponse magistaResponse = magistaClient.searchPayouts(query);
return new AsyncResult<>(new InlineResponse20011()
return new InlineResponse20011()
.result(magistaResponse.getPayouts().stream()
.map(payoutResponseConverter::convert)
.collect(Collectors.toList()))
.continuationToken(magistaResponse.getContinuationToken()));
.continuationToken(magistaResponse.getContinuationToken());
}
@Async
@SneakyThrows
public Future<InlineResponse20012> findRefunds(RefundSearchQuery query) {
public InlineResponse20012 findRefunds(RefundSearchQuery query) {
StatRefundResponse magistaResponse = magistaClient.searchRefunds(query);
return new AsyncResult<>(new InlineResponse20012()
return new InlineResponse20012()
.result(magistaResponse.getRefunds().stream()
.map(refundResponseConverter::convert)
.collect(Collectors.toList()))
.continuationToken(magistaResponse.getContinuationToken()));
.continuationToken(magistaResponse.getContinuationToken());
}
}

View File

@ -3,19 +3,12 @@ package com.rbkmoney.anapi.v2.util;
import lombok.experimental.UtilityClass;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import java.time.Duration;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
@UtilityClass
public class CommonUtil {
private static final Pattern deadlinePattern = Pattern.compile("\\d+(?:\\.\\d+)?(?:ms|s|m)");
public static List<String> merge(@Nullable String id, @Nullable List<String> ids) {
if (id != null) {
if (ids == null) {
@ -26,20 +19,4 @@ public class CommonUtil {
return ids;
}
public static long getRequestDeadlineMillis(@NotNull String requestDeadLine) {
if (deadlinePattern.matcher(requestDeadLine).matches()) {
//150000ms, 540s, 3.5m, etc
if (requestDeadLine.endsWith("ms")) {
return Long.parseLong(requestDeadLine.substring(0, requestDeadLine.length() - 3));
}
String duration = "PT" + requestDeadLine.toUpperCase();
return Duration.parse(duration).toMillis();
}
//ISO 8601
OffsetDateTime odt = OffsetDateTime.parse(requestDeadLine);
return odt.toInstant().toEpochMilli() - Instant.now().toEpochMilli();
}
}

View File

@ -0,0 +1,111 @@
package com.rbkmoney.anapi.v2.util;
import com.rbkmoney.anapi.v2.exception.DeadlineException;
import lombok.experimental.UtilityClass;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@UtilityClass
@SuppressWarnings("ParameterName")
public class DeadlineUtils {
public static void checkDeadline(String xRequestDeadline, String xRequestId) {
if (xRequestDeadline == null) {
return;
}
if (containsRelativeValues(xRequestDeadline, xRequestId)) {
return;
}
try {
Instant instant = Instant.parse(xRequestDeadline);
if (Instant.now().isAfter(instant)) {
throw new DeadlineException(String.format("Deadline has expired, xRequestId=%s ", xRequestId));
}
} catch (Exception ex) {
throw new DeadlineException(
String.format("Deadline has invalid 'Instant' format, xRequestId=%s ", xRequestId));
}
}
public static boolean containsRelativeValues(String xRequestDeadline, String xRequestId) {
return (extractMinutes(xRequestDeadline, xRequestId) + extractSeconds(xRequestDeadline, xRequestId) +
extractMilliseconds(xRequestDeadline, xRequestId)) > 0;
}
public static Long extractMinutes(String xRequestDeadline, String xRequestId) {
String format = "minutes";
checkNegativeValues(xRequestDeadline, xRequestId, "([-][0-9]+([.][0-9]+)?(?!ms)[m])", format);
Double minutes = extractValue(xRequestDeadline, "([0-9]+([.][0-9]+)?(?!ms)[m])", xRequestId, format);
return Optional.ofNullable(minutes).map(min -> min * 60000.0).map(Double::longValue).orElse(0L);
}
public static Long extractSeconds(String xRequestDeadline, String xRequestId) {
String format = "seconds";
checkNegativeValues(xRequestDeadline, xRequestId, "([-][0-9]+([.][0-9]+)?[s])", format);
Double seconds = extractValue(xRequestDeadline, "([0-9]+([.][0-9]+)?[s])", xRequestId, format);
return Optional.ofNullable(seconds).map(s -> s * 1000.0).map(Double::longValue).orElse(0L);
}
public static Long extractMilliseconds(String xRequestDeadline, String xRequestId) {
String format = "milliseconds";
checkNegativeValues(xRequestDeadline, xRequestId, "([-][0-9]+([.][0-9]+)?[m][s])", format);
Double milliseconds = extractValue(xRequestDeadline, "([0-9]+([.][0-9]+)?[m][s])", xRequestId, format);
if (milliseconds != null && Math.ceil(milliseconds % 1) > 0) {
throw new DeadlineException(
String.format("Deadline 'milliseconds' parameter can have only integer value, xRequestId=%s ",
xRequestId));
}
return Optional.ofNullable(milliseconds).map(Double::longValue).orElse(0L);
}
private static void checkNegativeValues(String xRequestDeadline, String xRequestId, String regex, String format) {
if (!match(regex, xRequestDeadline).isEmpty()) {
throw new DeadlineException(
String.format("Deadline '%s' parameter has negative value, xRequestId=%s ", format, xRequestId));
}
}
private static Double extractValue(String xRequestDeadline, String formatRegex, String xRequestId, String format) {
String numberRegex = "([0-9]+([.][0-9]+)?)";
List<String> doubles = new ArrayList<>();
for (String string : match(formatRegex, xRequestDeadline)) {
doubles.addAll(match(numberRegex, string));
}
if (doubles.size() > 1) {
throw new DeadlineException(
String.format("Deadline '%s' parameter has a few relative value, xRequestId=%s ", format,
xRequestId));
}
if (doubles.isEmpty()) {
return null;
}
return Double.valueOf(doubles.get(0));
}
private static List<String> match(String regex, String data) {
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(data);
List<String> strings = new ArrayList<>();
while (matcher.find()) {
strings.add(matcher.group());
}
return strings;
}
}