Managing Historical Validation Checks Like @Past and @Present with the Spring Framework and Bean Validation

  • Home
  • Blog
  • Managing Historical Validation Checks Like @Past and @Present with the Spring Framework and Bean Validation
Managing Historical Validation Checks Like @Past and @Present with the Spring Framework and Bean Validation

Managing Historical Validation Checks Like @Past and @Present with the Spring Framework and Bean Validation

When running test scenarios in software, we sometimes need to change the date and time.

For example, we might want to test whether a dashboard widget labeled “Pending Payments” which is intended for a payment due in three months can perform the correct calculations and display the result accurately when that date arrives.

Or, in a scenario where future-dated transactions are not permitted, we may need to enter future-dated data to verify how the system will generate results on that date.

  • It may be necessary to demonstrate this, especially for user acceptance testing.
  • In our RESTful APIs, we perform basic validations on data received in requests using Bean Validation. We can use the @PastOrPresent or @Past annotations to ensure that no date field is set to a future date. As I mentioned earlier, in a scenario where you try to enter a future date, the Bean Validation rules cause the validator to return an error message stating that the entered date cannot be in the future.
  • In this article, I will explain exactly how to make the system behave as if the date were in the future or on the entered date.
  • To achieve this, I used two features in Spring: `javax.validation.Configuration.clockProvider` and `@RefreshScope`.
  • To customize the ClockProvider object in the javax.validation.Validator implementation, you must configure it as follows. The ClockProvider provides the java.time.Clock object that is referenced during historical checks in validation. Spring Boot uses hibernate-validator by default, and the DefaultClockProvider object provided by hibernate-validator is used in the application.

     

  • @RefreshScope is provided by spring-cloud-context and enables configuration changes to be applied at runtime in distributed applications. Beans marked with @RefreshScope are reinitialized when an ApplicationEvent of type RefreshEvent is published, allowing them to resume service with the new configuration.

@EnableWebMvc

@Configuration

public class FrozenClockConfig implements WebMvcConfigurer {

 

    @RefreshScope

    @Bean

    @Override

    public org.springframework.validation.Validator getValidator() {

        return new LocalValidatorFactoryBean() {

            @Override

            protected void postProcessConfiguration(javax.validation.Configuration<?> configuration) {

                configuration.clockProvider(FrozenClock.getClockProvider());

            }

        };

    }

}

  • I created a class like the one below to manage the time data within the app. FrozenClock retrieves the latest state of the app's date and time and returns a new clock provider object based on that.

public final class FrozenClock {

 

    private static Date systemDate = new Date();

    private static boolean frozen = false;

 

    private FrozenClock() {

    }

 

    public static void freeze(final Date newDate) {

        systemDate = newDate;

        frozen = true;

    }

 

    public static void unfreeze() {

        frozen = false;

    }

 

    public static ClockProvider getClockProvider() {

        return () -> {

            if (frozen) {

                return Clock.fixed(Instant.ofEpochSecond(systemDate.getTime()), Clock.systemDefaultZone().getZone());

            } else {

                return Clock.systemDefaultZone();

            }

        };

    }

}

I now need an API that allows me to manage the system date externally. I’m doing this with the following RESTful controller.

@RestController

@RequestMapping("/clock")

@RequiredArgsConstructor

public class ClockManagementController {

 

    private final ApplicationEventPublisher eventPublisher;

 

    @PutMapping(path = "/freeze")

    public ResponseEntity freeze(@RequestBody @Valid FrozenClockInfo frozenClockInfo) {

        FrozenClock.freeze(frozenClockInfo.getNewDate());

        refreshSystemClock();

        return new ResponseEntity<>(HttpStatus.OK);

    }

 

    @PutMapping(path = "/unfreeze")

    public ResponseEntity unfreeze() {

        FrozenClock.unfreeze();

        refreshSystemClock();

 

        return new ResponseEntity<>(HttpStatus.OK);

    }

 

    private void refreshSystemClock() {

        eventPublisher.publishEvent(new RefreshEvent(this, "RefreshEvent", "Refresh synchronized clock  related beans"));

    }

}

This controller contains two methods named `freeze` and `unfreeze`. Using the `freeze` method, I can change and lock the system date using a date value sent from an external source. As shown, every time the freeze and unfreeze methods are called, the refreshSystemClock method triggers a RefreshEvent within the application via the ApplicationEventPublisher. Spring uses this event to ensure that beans marked with the RefreshScope annotation are reinitialized.

  • Freeze

curl -X PUT “http://localhost:6001/clock/freeze” -H  “accept: */*” -H  “Content-Type: application/json” -d “{  \”newDate\“: \”2020-12-31T18:10:29.574Z\“}”

 

  • I can reset the system date to its default values using the Unfreeze method.

curl -X PUT “http://localhost:6001/clock/unfreeze” -H  “accept: */*”

 

  • I also have an API called DemoController that handles date checks.

@Slf4j

@RestController

@RequestMapping("/demo")

public class DemoController {

 

    @PostMapping

    public @ResponseBody

    ResponseEntity create(@Valid @RequestBody DemoDTO demoDTO) {

        log.debug("demoDTO = {}", demoDTO);

        return new ResponseEntity<>(HttpStatus.CREATED);

    }

  • }

  • create metodu DemoDTO adında bir nesne kabul etmekte.

@Getter

@Setter

@ToString

public class DemoDTO {

    @NotEmpty

    private String id;

 

    @PastOrPresent

    @NotNull

    private Date operationDate;

}

  • As you can see, the `operationDate` field has a `@PastOrPresent` constraint. This prevents us from submitting a value with a future date. I want to test the system by performing a transaction with a future date. However, under these conditions, I cannot pass the validation checks. I believe I can resolve this issue with the adjustments mentioned above.

     

  • System Date: 2020–10–31. I’m making my first request attempt based on this date.

     

  • In response to this request, I receive an HTTP-400-Bad Request error, and the cause of the error is the @PastOrPresent validation.

  • I'm going to set the system date forward.

  • And the result is a successful HTTP-201 response. After running my test, I can reset the system date to its default values.

You can access the sample application I created for this article at the following address.

https://github.com/dilaverdemirel/frozen-clock-demo