Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,14 @@ The next sections describes how to set up project configuration.
- `circuitbreaker.open_state_duration` - duration (in millis) of circuit breaker sitting in open state.
- `circuitbreaker.closed_state_calls_number` - size of circuit breaker sliding window.
- `circuitbreaker.half_open_state_calls_number` - number of calls in half open state.

### Storage
- `storage.default-ttl-seconds` - set the default ttl for the data

#### Redis
- `storage.redis.{application-name}.port` - redis port.
- `storage.redis.{application-name}.host` - redis host.
- `storage.redis.{application-name}.password` - redis password, leave empty if no password required.
- `storage.redis.{application-name}.cluster.nodes` - list of node uris, set when using clustered redis.
- `storage.redis.{application-name}.cluster.enable_topology_refresh` - toggle for topology refresh support, set when using clustered redis.
- `storage.redis.{application-name}.cluster.topology_periodic_refresh_period` - refresh period of clustered redis topology, used when `storage.redis.{application-name}.cluster.enable_topology_refresh` is set to true.
23 changes: 23 additions & 0 deletions src/main/java/org/prebid/cache/config/StorageConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.prebid.cache.config;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Configuration
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't it work without @Configuration?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, for that to work without @Configuration we need to change component scan method.

@Validated
@ConfigurationProperties(prefix = "storage")
public class StorageConfig {

@NotNull
@Min(0)
Long defaultTtlSeconds;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

@Component
@RequiredArgsConstructor
public class GetModuleStorageHandler {
public class GetStorageHandler {

private static final String API_KEY_HEADER = "x-pbc-api-key";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.prebid.cache.builders.PrebidServerResponseBuilder;
import org.prebid.cache.config.StorageConfig;
import org.prebid.cache.exceptions.BadRequestException;
import org.prebid.cache.model.ModulePayload;
import org.prebid.cache.model.StoragePayload;
import org.prebid.cache.model.Payload;
import org.prebid.cache.model.PayloadWrapper;
import org.prebid.cache.repository.redis.module.storage.ModuleCompositeRepository;
Expand All @@ -20,31 +21,33 @@
import reactor.core.publisher.SynchronousSink;
import reactor.core.scheduler.Schedulers;

import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Component
@RequiredArgsConstructor
public class PostModuleStorageHandler {
public class PostStorageHandler {

private static final String API_KEY_HEADER = "x-pbc-api-key";

private final Validator validator;
private final ModuleCompositeRepository moduleRepository;
private final PrebidServerResponseBuilder responseBuilder;
private final ApiConfig apiConfig;
private final StorageConfig storageConfig;

public Mono<ServerResponse> save(final ServerRequest request) {
if (!isApiKeyValid(request)) {
return ServerResponse.status(HttpStatus.UNAUTHORIZED).build();
}

return request.body(BodyExtractors.toMono(ModulePayload.class))
return request.body(BodyExtractors.toMono(StoragePayload.class))
.switchIfEmpty(Mono.error(new BadRequestException("Empty body")))
.handle(this::validateModulePayload)
.flatMap(modulePayload -> moduleRepository.save(
modulePayload.getApplication(),
mapToPayloadWrapper(modulePayload)))
.flatMap(storagePayload -> moduleRepository.save(
storagePayload.getApplication(),
mapToPayloadWrapper(storagePayload)))
.subscribeOn(Schedulers.parallel())
.flatMap(ignored -> ServerResponse.noContent().build())
.onErrorResume(error -> responseBuilder.error(Mono.just(error), request));
Expand All @@ -54,7 +57,7 @@ private boolean isApiKeyValid(final ServerRequest request) {
return StringUtils.equals(request.headers().firstHeader(API_KEY_HEADER), apiConfig.getApiKey());
}

private void validateModulePayload(final ModulePayload payload, final SynchronousSink<ModulePayload> sink) {
private void validateModulePayload(final StoragePayload payload, final SynchronousSink<StoragePayload> sink) {
final var result = validator.validate(payload);
if (result.isEmpty()) {
sink.next(payload);
Expand All @@ -66,11 +69,15 @@ private void validateModulePayload(final ModulePayload payload, final Synchronou
}
}

private static PayloadWrapper mapToPayloadWrapper(final ModulePayload payload) {
private PayloadWrapper mapToPayloadWrapper(final StoragePayload payload) {
final long ttlSeconds = Optional.ofNullable(payload.getTtlseconds())
.map(Integer::longValue)
.orElse(storageConfig.getDefaultTtlSeconds());

return PayloadWrapper.builder()
.id(payload.getKey())
.prefix(StringUtils.EMPTY)
.expiry(payload.getTtlseconds().longValue())
.expiry(ttlSeconds)
.payload(Payload.of(payload.getType().toString(), payload.getKey(), payload.getValue()))
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

@Value
@Builder
public class ModulePayload {
public class StoragePayload {

@NotEmpty
public String key;
Expand All @@ -23,7 +23,6 @@ public class ModulePayload {
@NotEmpty
public String application;

@NotNull
@Min(0)
public Integer ttlseconds;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

@Data
@Component
@ConfigurationProperties(prefix = "module.storage")
@ConfigurationProperties(prefix = "storage")
public class ModuleCompositeRedisConfigurationProperties {

private Map<String, RedisConfigurationProperties> redis;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/prebid/cache/routers/ApiConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class ApiConfig {
private String cachePath;

@NotEmpty
private String moduleStoragePath;
private String storagePath;

@NotEmpty
private String apiKey;
Expand Down
14 changes: 7 additions & 7 deletions src/main/java/org/prebid/cache/routers/ApiRouter.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import org.prebid.cache.handlers.ErrorHandler;
import org.prebid.cache.handlers.cache.GetCacheHandler;
import org.prebid.cache.handlers.cache.PostCacheHandler;
import org.prebid.cache.handlers.storage.GetModuleStorageHandler;
import org.prebid.cache.handlers.storage.PostModuleStorageHandler;
import org.prebid.cache.handlers.storage.GetStorageHandler;
import org.prebid.cache.handlers.storage.PostStorageHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
Expand All @@ -28,8 +28,8 @@ public class ApiRouter {
@Bean
RouterFunction<?> doRoute(final GetCacheHandler getCacheHandler,
final PostCacheHandler postCacheHandler,
final GetModuleStorageHandler getModuleStorageHandler,
final PostModuleStorageHandler postModuleStorageHandler,
final GetStorageHandler getStorageHandler,
final PostStorageHandler postStorageHandler,
final ErrorHandler errorHandler,
final ApiConfig apiConfig) {

Expand All @@ -45,9 +45,9 @@ RouterFunction<?> doRoute(final GetCacheHandler getCacheHandler,
GET(apiConfig.getCachePath()).and(accept(MediaType.APPLICATION_XML)),
getCacheHandler::fetch)
.andRoute(
POST(apiConfig.getModuleStoragePath()).and(accept(MediaType.APPLICATION_JSON)),
postModuleStorageHandler::save)
.andRoute(GET(apiConfig.getModuleStoragePath()), getModuleStorageHandler::fetch)
POST(apiConfig.getStoragePath()).and(accept(MediaType.APPLICATION_JSON)),
postStorageHandler::save)
.andRoute(GET(apiConfig.getStoragePath()), getStorageHandler::fetch)
.andOther(route(RequestPredicates.all(), errorHandler::invalidRequest));
}
}
8 changes: 4 additions & 4 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ info.app.description: @project.description@
info.app.version: @project.version@
spring.main.banner-mode: "off"
api.cache-path: /cache
api.module-storage-path: /module-storage
api.storage-path: /storage
api.api-key: API_KEY
server.port: 8080
server.compression.enabled: true
Expand All @@ -27,9 +27,9 @@ cache:
clients_cache_size: 1000
host_param_protocol: https

module:
storage:
redis: {}
storage:
redis: {}
default-ttl-seconds: 300

# logging
logging.level.root: info
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.prebid.cache.builders.PrebidServerResponseBuilder;
import org.prebid.cache.handlers.storage.GetModuleStorageHandler;
import org.prebid.cache.handlers.storage.GetStorageHandler;
import org.prebid.cache.model.Payload;
import org.prebid.cache.model.PayloadWrapper;
import org.prebid.cache.repository.redis.module.storage.ModuleCompositeRepository;
Expand All @@ -27,13 +27,13 @@

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {
GetModuleStorageHandler.class,
GetStorageHandler.class,
PrebidServerResponseBuilder.class,
ApiConfig.class
})
@EnableConfigurationProperties
@SpringBootTest
public class GetModuleStorageHandlerTests {
public class GetStorageHandlerTests {

@Autowired
ApiConfig apiConfig;
Expand All @@ -44,13 +44,13 @@ public class GetModuleStorageHandlerTests {
@MockBean
ModuleCompositeRepository moduleCompositeRepository;

GetModuleStorageHandler handler;
GetStorageHandler handler;

WireMockServer serverMock;

@BeforeEach
public void setup() {
handler = new GetModuleStorageHandler(moduleCompositeRepository, responseBuilder, apiConfig);
handler = new GetStorageHandler(moduleCompositeRepository, responseBuilder, apiConfig);
serverMock = new WireMockServer(8080);
serverMock.start();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.prebid.cache.builders.PrebidServerResponseBuilder;
import org.prebid.cache.handlers.storage.PostModuleStorageHandler;
import org.prebid.cache.model.ModulePayload;
import org.prebid.cache.config.StorageConfig;
import org.prebid.cache.handlers.storage.PostStorageHandler;
import org.prebid.cache.model.StoragePayload;
import org.prebid.cache.model.Payload;
import org.prebid.cache.model.PayloadWrapper;
import org.prebid.cache.repository.redis.module.storage.ModuleCompositeRepository;
import org.prebid.cache.routers.ApiConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
Expand All @@ -33,33 +33,40 @@

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {
PostModuleStorageHandler.class,
PrebidServerResponseBuilder.class,
ApiConfig.class
})
@EnableConfigurationProperties
@SpringBootTest
class PostModuleStorageHandlerTests {
class PostStorageHandlerTests {

@Autowired
ApiConfig apiConfig;

@Autowired
PrebidServerResponseBuilder responseBuilder;

@MockBean
StorageConfig storageConfig;

@MockBean
ModuleCompositeRepository moduleCompositeRepository;

@MockBean
Validator validator;

PostModuleStorageHandler handler;
PostStorageHandler handler;

WireMockServer serverMock;

@BeforeEach
public void setup() {
handler = new PostModuleStorageHandler(validator, moduleCompositeRepository, responseBuilder, apiConfig);
handler = new PostStorageHandler(
validator,
moduleCompositeRepository,
responseBuilder,
apiConfig,
storageConfig);
serverMock = new WireMockServer(8080);
serverMock.start();
}
Expand All @@ -73,7 +80,7 @@ public void teardown() {
void testVerifySave() {
given(validator.validate(any())).willReturn(Collections.emptySet());

final var payload = ModulePayload.builder()
final var payload = StoragePayload.builder()
.key("key")
.type(PayloadType.TEXT)
.application("application")
Expand Down Expand Up @@ -104,11 +111,46 @@ void testVerifySave() {
.verify();
}

@Test
void testVerifyDefaultTtl() {
given(validator.validate(any())).willReturn(Collections.emptySet());
given(storageConfig.getDefaultTtlSeconds()).willReturn(999L);

final var payload = StoragePayload.builder()
.key("key")
.type(PayloadType.TEXT)
.application("application")
.value("value")
.build();

final var payloadWrapper = PayloadWrapper.builder()
.id("key")
.prefix("")
.payload(Payload.of("text", "key", "value"))
.expiry(999L)
.build();

given(moduleCompositeRepository.save("application", payloadWrapper))
.willReturn(Mono.just(payloadWrapper));

final var serverRequest = MockServerRequest.builder()
.method(HttpMethod.POST)
.header("x-pbc-api-key", apiConfig.getApiKey())
.body(Mono.just(payload));

final var responseMono = handler.save(serverRequest);

StepVerifier.create(responseMono)
.consumeNextWith(serverResponse -> assertEquals(204, serverResponse.statusCode().value()))
.expectComplete()
.verify();
}

@Test
void testVerifyApiKeyAuthorization() {
given(validator.validate(any())).willReturn(Collections.emptySet());

final var payload = ModulePayload.builder()
final var payload = StoragePayload.builder()
.key("key")
.type(PayloadType.TEXT)
.application("application")
Expand Down
Loading