Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.baeldung.handlerinterceptor;

import java.security.SecureRandom;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import org.springframework.lang.NonNull;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class DeliveryChargeInterceptor implements HandlerInterceptor {

static final String USE_V2_ATTRIBUTE = "useV2";

private final FeatureFlagService featureFlagService;
private final SecureRandom random = new SecureRandom();
private final Logger logger = LoggerFactory.getLogger(DeliveryChargeInterceptor.class);

public DeliveryChargeInterceptor(FeatureFlagService featureFlagService) {
this.featureFlagService = featureFlagService;
}

@Override
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
if (handler instanceof HandlerMethod) {
int rollout = featureFlagService.rolloutPercentage();
boolean useV2 = rollout > 0 && random.nextInt(100) < rollout;
request.setAttribute(USE_V2_ATTRIBUTE, useV2);
logger.info("Delivery charge feature: rollout={}%, useV2={}", rollout, useV2);
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.baeldung.handlerinterceptor;

public interface DeliveryChargeService {

double calculateV1(String postCode);

double calculateV2(String postCode);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.baeldung.handlerinterceptor;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import jakarta.servlet.http.HttpServletRequest;

@RestController
public class DeliveryChargesController {

private final DeliveryChargeService deliveryChargeService;

public DeliveryChargesController(DeliveryChargeService deliveryChargeService) {
this.deliveryChargeService = deliveryChargeService;
}

@PostMapping("/delivery-charges/calculate")
public double calculate(@RequestParam String postcode, HttpServletRequest request) {
Boolean useV2 = (Boolean) request.getAttribute(DeliveryChargeInterceptor.USE_V2_ATTRIBUTE);
if (Boolean.TRUE.equals(useV2)) {
return deliveryChargeService.calculateV2(postcode);
}
return deliveryChargeService.calculateV1(postcode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.baeldung.handlerinterceptor;

public interface FeatureFlagService {
int rolloutPercentage();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.baeldung.handlerinterceptor;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = "com.baeldung.handlerinterceptor")
public class HandlerInterceptorApp {
public static void main(String[] args) {
SpringApplication.run(HandlerInterceptorApp.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.baeldung.handlerinterceptor;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

private final FeatureFlagService featureFlagService;

public WebMvcConfig(FeatureFlagService featureFlagService) {
this.featureFlagService = featureFlagService;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DeliveryChargeInterceptor(featureFlagService))
.addPathPatterns("/delivery-charges/**");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.baeldung.handlerinterceptor;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(controllers = DeliveryChargesController.class)
class DeliveryChargeInterceptorUnitTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private DeliveryChargeService deliveryChargeService;

@MockBean
private FeatureFlagService featureFlagService;

@Test
void givenZeroRollout_whenCalculateDeliveryCharge_thenV1IsUsed() throws Exception {
when(featureFlagService.rolloutPercentage()).thenReturn(0);
when(deliveryChargeService.calculateV1("SW1A")).thenReturn(5.0);

mockMvc.perform(post("/delivery-charges/calculate").param("postcode", "SW1A"))
.andExpect(status().isOk())
.andExpect(content().string("5.0"));
}

@Test
void givenFullRollout_whenCalculateDeliveryCharge_thenV2IsUsed() throws Exception {
when(featureFlagService.rolloutPercentage()).thenReturn(100);
when(deliveryChargeService.calculateV2("SW1A")).thenReturn(3.5);

mockMvc.perform(post("/delivery-charges/calculate").param("postcode", "SW1A"))
.andExpect(status().isOk())
.andExpect(content().string("3.5"));
}

@Test
void givenPartialRollout_whenCalculateDeliveryCharge_thenBothVersionsAreUsed() throws Exception {
when(featureFlagService.rolloutPercentage()).thenReturn(50);
when(deliveryChargeService.calculateV1("SW1A")).thenReturn(5.0);
when(deliveryChargeService.calculateV2("SW1A")).thenReturn(3.5);

int v1Count = 0;
int v2Count = 0;

for (int i = 0; i < 20; i++) {
String response = mockMvc.perform(post("/delivery-charges/calculate").param("postcode", "SW1A"))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
if ("5.0".equals(response)) {
v1Count++;
} else if ("3.5".equals(response)) {
v2Count++;
}
}

assertThat(v1Count).isGreaterThan(0);
assertThat(v2Count).isGreaterThan(0);
}
}