mirror of
https://github.com/valitydev/anapi-v2.git
synced 2024-11-06 08:35:19 +00:00
Deadline processing updated
This commit is contained in:
parent
d41f2a4699
commit
c5c97c13d5
48
pom.xml
48
pom.xml
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
135
src/main/java/com/rbkmoney/anapi/v2/config/SecurityConfig.java
Normal file
135
src/main/java/com/rbkmoney/anapi/v2/config/SecurityConfig.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
105
src/main/java/com/rbkmoney/anapi/v2/config/WebConfig.java
Normal file
105
src/main/java/com/rbkmoney/anapi/v2/config/WebConfig.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
111
src/main/java/com/rbkmoney/anapi/v2/util/DeadlineUtils.java
Normal file
111
src/main/java/com/rbkmoney/anapi/v2/util/DeadlineUtils.java
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user