Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
74eb626
Notification Changes
srivanimuddineni Feb 2, 2026
54812a1
Refactor Subscriber class with Lombok annotations
srivanimuddineni Feb 2, 2026
3c79368
Test case fix
srivanimuddineni Feb 2, 2026
8ae9b01
removed unnecessary comments
srivanimuddineni Feb 2, 2026
2f6baa4
Resolved review comments
srivanimuddineni Feb 2, 2026
5315962
Reverted docker file to original
srivanimuddineni Feb 2, 2026
f8e4490
pmd issues fix
srivanimuddineni Feb 2, 2026
4de50cb
Notification Changes for AMP-167 & 168(sync) and AMP-169 - Incorporte…
srivanimuddineni Feb 3, 2026
3213590
Notification Changes for AMP-167 & 168(sync) and AMP-169 - Incorporte…
srivanimuddineni Feb 3, 2026
68d5b2b
Notification Changes for AMP-167 & 168(sync) and AMP-169 - Incorporte…
srivanimuddineni Feb 3, 2026
9d6c599
Notification Changes for AMP-167 & 168(sync) and AMP-169 - Resolved …
srivanimuddineni Feb 3, 2026
c00f7c1
Notification Changes for AMP-167 & 168(sync) & AMP-169 Impl of RestT…
srivanimuddineni Feb 4, 2026
8a140ab
Notification Changes for AMP-167 & 168(sync) & AMP-169 Impl of RestT…
srivanimuddineni Feb 4, 2026
5770001
feature: amp-167 notification changes
coling01 Feb 4, 2026
a1584f5
feature: amp-167 notification changes
coling01 Feb 4, 2026
7502171
feature: amp-167 notification changes
coling01 Feb 4, 2026
1f70708
Merge pull request #32 from hmcts/feature/colin-notification-changes
srivanimuddineni Feb 4, 2026
cdb5b41
Notification Changes for AMP-167 & 168(sync) & AMP-169 moved subscrip…
srivanimuddineni Feb 4, 2026
bfedef3
Notification Changes for AMP-167 & 168(sync) & AMP-169 moved subscrip…
srivanimuddineni Feb 4, 2026
b84fe48
Notification Changes for AMP-167 & 168(sync) & AMP-169 removed readyOnly
srivanimuddineni Feb 4, 2026
0d2397e
Notification Changes for AMP-167 & 168(sync) & AMP-169 Added Manager …
srivanimuddineni Feb 4, 2026
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
10 changes: 0 additions & 10 deletions Dockerfile

This file was deleted.

3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ plugins {
id 'java'
id 'org.springframework.boot' version '4.0.1'
id 'io.spring.dependency-management' version '1.1.7'
id 'org.openapi.generator' version '7.19.0'
id 'jacoco'
id 'maven-publish'
id 'com.github.ben-manes.versions' version '0.53.0'
Expand All @@ -17,7 +18,7 @@ apply {
from("$rootDir/gradle/dependencies/java-core.gradle")
from("$rootDir/gradle/dependencies/spring-core.gradle")
from("$rootDir/gradle/dependencies/spring-db.gradle")
from("$rootDir/gradle/dependencies/spring-cloud.gradle")
from("$rootDir/gradle/dependencies/openapi-material.gradle")

from("$rootDir/gradle/github/repositories.gradle")
from("$rootDir/gradle/github/java.gradle")
Expand Down
25 changes: 0 additions & 25 deletions docs/debugging.md

This file was deleted.

37 changes: 37 additions & 0 deletions gradle/dependencies/openapi-material.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Material API OpenAPI client generation
tasks.named('compileJava') {
dependsOn(tasks.openApiGenerate)
}

def materialClientOutputDir = "${layout.buildDirectory.get().asFile.absolutePath}/generated"

openApiGenerate {
inputSpec = "$rootDir/src/main/resources/openapi/material-api-spec.yml"
outputDir = materialClientOutputDir
generatorName = 'java'
library = 'resttemplate'
apiPackage = 'uk.gov.hmcts.cp.material.openapi.api'
modelPackage = 'uk.gov.hmcts.cp.material.openapi.model'
invokerPackage = 'uk.gov.hmcts.cp.material.openapi'
typeMappings = ['OffsetDateTime': 'Instant']
importMappings = ['java.time.OffsetDateTime': 'java.time.Instant']
configOptions = [
useSpringBoot4 : 'true',
useJakartaEe : 'true',
useTags : 'true',
openApiNullable : 'false'
]
}

sourceSets {
main {
java {
srcDir "${materialClientOutputDir}/src/main/java"
}
}
}

dependencies {
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
implementation 'javax.annotation:javax.annotation-api:1.3.2'
}
3 changes: 0 additions & 3 deletions gradle/dependencies/spring-cloud.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ dependencyManagement {
}

dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
testImplementation 'org.wiremock.integrations:wiremock-spring-boot:3.2.0'
testImplementation 'org.wiremock:wiremock-standalone:3.3.1'


}
1 change: 1 addition & 0 deletions gradle/dependencies/spring-core.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-aspectj"
implementation "org.springframework.boot:spring-boot-starter-actuator"
implementation "org.springframework.boot:spring-boot-starter-validation"
implementation "org.springframework.retry:spring-retry"

testImplementation "org.springframework.boot:spring-boot-starter-webmvc-test"
testImplementation "org.springframework.boot:spring-boot-starter-test"
Expand Down
2 changes: 0 additions & 2 deletions src/main/java/uk/gov/hmcts/cp/subscription/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@SpringBootApplication
public class Application {

Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,33 @@

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.RestTemplate;
import uk.gov.hmcts.cp.subscription.services.ClockService;
import uk.gov.hmcts.cp.subscription.services.exceptions.MaterialMetadataNotReadyException;
import uk.gov.hmcts.cp.subscription.services.exceptions.CallbackUrlDeliveryException;

import java.time.Clock;
import java.util.Map;

@Configuration
public class AppConfig {

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}

@Bean
public ClockService clockService() {
return new ClockService(Clock.systemDefaultZone());
}

@Bean
public RetryTemplate retryTemplate() {
return RetryConfig.retryConfig().toRetryTemplate( Map.of(
MaterialMetadataNotReadyException.class, true,
CallbackUrlDeliveryException.class, true
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package uk.gov.hmcts.cp.subscription.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import uk.gov.hmcts.cp.material.openapi.ApiClient;
import uk.gov.hmcts.cp.material.openapi.api.MaterialApi;

/**
* Configures the OpenAPI-generated Material API client with base URL from material-client.url.
*/
@Configuration
public class MaterialApiConfig {

@Bean
public ApiClient materialApiClient(
@Value("${material-client.url}") final String baseUrl,
final RestTemplate restTemplate) {
final ApiClient client = new ApiClient(restTemplate);
client.setBasePath(baseUrl);
return client;
}

@Bean
public MaterialApi materialApi(final ApiClient materialApiClient) {
return new MaterialApi(materialApiClient);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package uk.gov.hmcts.cp.subscription.config;

import lombok.Builder;
import lombok.Value;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;

import java.util.Map;

/**
* Common retry configuration for Material API and Callback URL delivery.
* Supports exponential backoff and configurable retry policies.
*/
@Value
@Builder
public class RetryConfig {

int maxAttempts;
long initialDelayMs;
double multiplier;
long maxDelayMs;

/**
* Creates a RetryTemplate with exponential backoff that retries on the given exception types.
*/
public RetryTemplate toRetryTemplate(final Map<Class<? extends Throwable>, Boolean> retryableExceptions) {
final SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxAttempts, retryableExceptions);
final ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(initialDelayMs);
backOffPolicy.setMultiplier(multiplier);
backOffPolicy.setMaxInterval(maxDelayMs);

final RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(retryPolicy);
template.setBackOffPolicy(backOffPolicy);
return template;
}

/** Generic default: 3 attempts, 50ms initial, multiplier 2, max 5000ms. */
public static RetryConfig retryConfig() {
return RetryConfig.builder()
.maxAttempts(3)
.initialDelayMs(50)
.multiplier(2)
.maxDelayMs(5000)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package uk.gov.hmcts.cp.subscription.controllers;

import feign.FeignException;
import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.NoHandlerFoundException;

@RestControllerAdvice
@Slf4j
Expand All @@ -34,8 +37,8 @@ public ResponseEntity<String> handleClientException(final HttpClientErrorExcepti
}

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<String> handleHttpMessageNotReadable(final HttpMessageNotReadableException exception) {
log.error("Invalid request body: {}", exception.getMessage());
public ResponseEntity<String> handleHttpMessageNotReadableException(final HttpMessageNotReadableException exception) {
log.error("Invalid request body - HttpMessageNotReadableException: {}", exception.getMessage(), exception);
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body("");
Expand All @@ -45,25 +48,47 @@ public ResponseEntity<String> handleHttpMessageNotReadable(final HttpMessageNotR
public ResponseEntity<String> handleUnknownException(final Exception exception) {
log.error("Exception {}", exception.getMessage());
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.status(HttpStatus.BAD_REQUEST)
.body(exception.getMessage());
}

@ExceptionHandler(FeignException.class)
public ResponseEntity<String> handleFeignException(final FeignException ex) {
log.error("FeignException from downstream service", ex);
HttpStatus status = HttpStatus.resolve(ex.status());
if (status == null) {
status = HttpStatus.BAD_GATEWAY;
}
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ResponseEntity<String> handleHttpMediaTypeNotSupportedException(
final HttpMediaTypeNotSupportedException exception) {
log.error("Unsupported media type - HttpMediaTypeNotSupportedException: {}", exception.getMessage(), exception);
return ResponseEntity
.status(status)
.body(ex.contentUTF8());
.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
.body(exception.getMessage());
}

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Void> handleConstraintViolation(final ConstraintViolationException ex) {
log.error("Exception {}", ex.getMessage());
return ResponseEntity.badRequest().build();
}

@ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<String> handleNoHandlerFoundException(final NoHandlerFoundException exception) {
log.error("No handler found for request: {} {} - This might indicate a deserialization error before method matching",
exception.getHttpMethod(), exception.getRequestURL());
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body("Endpoint not found: " + exception.getRequestURL());
}

@ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<String> handleResponseStatusException(final ResponseStatusException exception) {
log.error("ResponseStatusException: {}", exception.getReason(), exception);
return ResponseEntity
.status(exception.getStatusCode())
.body(exception.getReason() != null ? exception.getReason() : exception.getMessage());
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<String> handleValidationException(final MethodArgumentNotValidException exception) {
log.error("Validation failed: {}", exception.getMessage());
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(exception.getMessage());
}
}
Loading
Loading