Skip to content

Commit afc16f3

Browse files
committed
Merge remote-tracking branch 'origin/release/v11.2.3' into release/v11.2.3
2 parents 3cad800 + ed1afc0 commit afc16f3

38 files changed

Lines changed: 1562 additions & 354 deletions

File tree

backend/src/main/java/com/park/utmstack/config/RestTemplateConfiguration.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.apache.http.conn.ssl.TrustStrategy;
66
import org.apache.http.impl.client.CloseableHttpClient;
77
import org.apache.http.impl.client.HttpClients;
8+
import org.jetbrains.annotations.NotNull;
89
import org.slf4j.Logger;
910
import org.slf4j.LoggerFactory;
1011
import org.springframework.context.annotation.Bean;
@@ -44,7 +45,7 @@ public RestTemplate rawRestTemplate() {
4445
RestTemplate rest = new RestTemplate();
4546
rest.setErrorHandler(new DefaultResponseErrorHandler() {
4647
@Override
47-
public boolean hasError(ClientHttpResponse response) {
48+
public boolean hasError(@NotNull ClientHttpResponse response) {
4849
return false;
4950
}
5051
});

backend/src/main/java/com/park/utmstack/domain/application_modules/validators/impl/ModuleConfigurationValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public boolean isValid(GroupConfigurationDTO dto, ConstraintValidatorContext con
3232
return module.validateConfiguration(utmModule, dto.getKeys());
3333
} catch (Exception e) {
3434
context.disableDefaultConstraintViolation();
35-
context.buildConstraintViolationWithTemplate("Invalid configuration for selected module.")
35+
context.buildConstraintViolationWithTemplate(e.getMessage())
3636
.addPropertyNode("keys")
3737
.addConstraintViolation();
3838
return false;
Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.park.utmstack.service.application_modules.connectors;
22

3+
import com.fasterxml.jackson.databind.JsonNode;
34
import com.park.utmstack.config.Constants;
5+
import com.park.utmstack.service.dto.application_modules.ModuleConfigValidationErrorMapper;
6+
import com.park.utmstack.service.dto.application_modules.ModuleConfigValidationErrorResponse;
47
import com.park.utmstack.service.dto.application_modules.UtmModuleGroupConfWrapperDTO;
58
import com.park.utmstack.service.web_clients.rest_template.RestTemplateService;
69
import com.park.utmstack.util.exceptions.ApiException;
@@ -24,44 +27,61 @@ public class ModuleConfigurationValidationService {
2427

2528
public boolean validateModuleConfiguration(String module, UtmModuleGroupConfWrapperDTO configurations) {
2629
final String ctx = CLASSNAME + ".ModuleConfigurationValidationService";
30+
2731
HttpHeaders headers = new HttpHeaders();
2832
headers.add("Content-Type", "application/json");
2933
headers.add("Accept", "*/*");
3034
headers.set(Constants.EVENT_PROCESSOR_INTERNAL_KEY_HEADER, System.getenv(Constants.ENV_INTERNAL_KEY));
3135

32-
String baseUrl = "http://" + System.getenv(Constants.ENV_EVENT_PROCESSOR_HOST) + ":" + System.getenv(Constants.ENV_EVENT_PROCESSOR_PORT);
36+
String baseUrl = "http://" + System.getenv(Constants.ENV_EVENT_PROCESSOR_HOST) + ":" + System.getenv(Constants.ENV_EVENT_PROCESSOR_PORT);
3337
String endPoint = baseUrl + "/api/v1/modules-config/validate?nameShort=" + module;
34-
try{
35-
ResponseEntity<String> response = restTemplateService.post(
36-
endPoint,
37-
configurations,
38-
String.class,
39-
headers
40-
);
41-
42-
if (!response.getStatusCode().is2xxSuccessful()) {
43-
List<String> errors = response.getHeaders().get("X-UtmStack-error");
44-
String errorMessage = (errors != null && !errors.isEmpty())
45-
? String.join(", ", errors)
46-
: "Unknown error occurred during module configuration validation.";
47-
48-
log.error("{}: Module configuration validation failed for module: {} with status: {}. Cause: {}",
49-
ctx, module, response.getStatusCode(), errorMessage);
50-
throw new ApiException(
51-
String.format("Module configuration validation failed for module: %s. Cause: %s", module, errorMessage),
52-
response.getStatusCode()
53-
);
38+
39+
ResponseEntity<JsonNode> response = restTemplateService.postRaw(
40+
endPoint,
41+
configurations,
42+
JsonNode.class,
43+
headers
44+
);
45+
46+
JsonNode body = response.getBody();
47+
48+
if (response.getStatusCode().is2xxSuccessful() && body != null && body.has("status")) {
49+
return true;
50+
}
51+
52+
if (body != null && body.has("error")) {
53+
String errorText = body.get("error").asText();
54+
55+
if (errorText.contains("{\"meta\"")) {
56+
ModuleConfigValidationErrorResponse structured = ModuleConfigValidationErrorMapper.parse(errorText);
57+
58+
if (structured != null) {
59+
String traceId = structured.getMeta().getTraceId();
60+
String message = structured.getErrors().get(0).getMessage();
61+
62+
log.error("{}: External provider validation failed for module {}. TraceId: {}. Message: {}",
63+
ctx, module, traceId, message);
64+
65+
throw new ApiException(
66+
"External provider validation failed: " + message + " (traceId=" + traceId + ")",
67+
HttpStatus.UNAUTHORIZED
68+
);
69+
}
5470
}
5571

56-
return true;
72+
log.error("{}: Module configuration validation failed for module {}. Cause: {}",
73+
ctx, module, errorText);
5774

58-
} catch (ApiException e) {
59-
throw e;
60-
} catch (Exception e) {
61-
log.error("{}: An error occurred while validating module configuration for module: {}. Cause: {}",
62-
ctx, module, e.getMessage(), e);
63-
throw new ApiException("An error occurred while validating module configuration", HttpStatus.INTERNAL_SERVER_ERROR);
75+
throw new ApiException(errorText, HttpStatus.BAD_REQUEST);
6476
}
77+
78+
log.error("{}: Unexpected response validating module {}.", ctx, module);
79+
throw new ApiException(
80+
String.format("%s: Unexpected response validating module %s.", ctx, module),
81+
HttpStatus.INTERNAL_SERVER_ERROR
82+
);
6583
}
84+
85+
6686
}
6787

backend/src/main/java/com/park/utmstack/service/compliance/ComplianceFileResponse.java

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,56 @@
11
package com.park.utmstack.service.compliance;
22

33
import com.park.utmstack.service.MailService;
4+
import com.park.utmstack.service.dto.web_pdf.PdfServiceResponse;
45
import com.park.utmstack.service.util.PdfService;
6+
import lombok.RequiredArgsConstructor;
57
import org.slf4j.Logger;
68
import org.slf4j.LoggerFactory;
79
import org.springframework.stereotype.Service;
8-
import org.springframework.transaction.annotation.Transactional;
910

1011
import java.time.Clock;
1112
import java.time.Instant;
1213

13-
/**
14-
* Service Implementation for managing Compliance PDF Delivery.
15-
*/
1614
@Service
17-
@Transactional
15+
@RequiredArgsConstructor
1816
public class ComplianceMailService {
19-
private final Logger log = LoggerFactory.getLogger(ComplianceMailService.class);
17+
18+
private static final Logger log = LoggerFactory.getLogger(ComplianceMailService.class);
2019
private static final String CLASSNAME = "ComplianceMailService";
20+
2121
private final MailService mailService;
2222
private final PdfService pdfService;
2323

24-
public ComplianceMailService(MailService mailService,
25-
PdfService pdfService) {
26-
this.mailService = mailService;
27-
this.pdfService = pdfService;
28-
}
29-
30-
/**
31-
* Method to generate dashboard in PDF format and send via email
32-
*/
33-
public void sendComplianceByMail(String url, String userEmail) throws Exception {
24+
public void sendComplianceByMail(String url, String userEmail) {
3425
final String ctx = CLASSNAME + ".sendComplianceByMail";
3526

3627
String accessKey = System.getenv("INTERNAL_KEY");
37-
byte[] pdfInBytes = pdfService.getPdf(url, accessKey,PdfService.PdfAccessTypes.PDF_TYPE_INTERNAL.get());
38-
if (pdfInBytes != null && pdfInBytes.length > 0) {
39-
mailService.sendComplianceReportEmail(userEmail, "UTMStack Compliance Report Delivery"
40-
, "This is a scheduled email delivery of a Compliance Report, please do not answer this email. ",
41-
"Compliance_Report_" + Instant.now(Clock.systemUTC()) + ".pdf", pdfInBytes);
42-
} else {
43-
log.error(ctx + ": We couldn't send the email, reason: No data returned from PDF service");
28+
29+
if (accessKey == null || accessKey.isBlank()) {
30+
log.error("{}: INTERNAL_KEY environment variable is missing", ctx);
31+
return;
4432
}
33+
34+
PdfServiceResponse response =
35+
pdfService.downloadPdf(url, accessKey, PdfService.PdfAccessTypes.PDF_TYPE_INTERNAL.get());
36+
37+
if (response.getPdfBytes() == null || response.getPdfBytes().length == 0) {
38+
log.error("{}: PDF service returned empty content for URL {}", ctx, url);
39+
return;
40+
}
41+
42+
String filename = "Compliance_Report_" + Instant.now(Clock.systemUTC()) + ".pdf";
43+
44+
mailService.sendComplianceReportEmail(
45+
userEmail,
46+
"UTMStack Compliance Report Delivery",
47+
"This is a scheduled email delivery of a Compliance Report, please do not answer this email.",
48+
filename,
49+
response.getPdfBytes()
50+
);
51+
52+
log.info("{}: Email successfully sent to {}", ctx, userEmail);
53+
4554
}
4655
}
56+

backend/src/main/java/com/park/utmstack/service/compliance/UtmComplianceReportScheduleService.java

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111
import com.park.utmstack.service.UserService;
1212
import com.park.utmstack.service.application_events.ApplicationEventService;
1313
import com.park.utmstack.service.dto.compliance.UtmComplianceReportScheduleCriteria;
14+
import com.park.utmstack.util.exceptions.ApiException;
1415
import com.park.utmstack.web.rest.errors.BadRequestAlertException;
1516
import org.slf4j.Logger;
1617
import org.slf4j.LoggerFactory;
1718
import org.springframework.data.domain.Page;
1819
import org.springframework.data.domain.Pageable;
1920
import org.springframework.data.jpa.domain.Specification;
21+
import org.springframework.http.HttpStatus;
2022
import org.springframework.scheduling.annotation.Scheduled;
2123
import org.springframework.scheduling.support.CronExpression;
2224
import org.springframework.stereotype.Service;
@@ -128,7 +130,7 @@ public Optional<UtmComplianceReportSchedule> findByComplianceReportValues(UtmCom
128130
log.debug("Request to get UtmComplianceReportSchedule : {}", reportSchedule);
129131
User user = userService.getCurrentUserLogin();
130132
return utmComplianceReportScheduleRepository.findFirstByUserIdAndComplianceIdAndScheduleString(user.getId(),
131-
reportSchedule.getComplianceId(),reportSchedule.getScheduleString());
133+
reportSchedule.getComplianceId(), reportSchedule.getScheduleString());
132134
}
133135

134136
/**
@@ -144,31 +146,43 @@ public void delete(Long id) {
144146

145147
/**
146148
* Scheduled method to execute the compliance report pdf generation and email delivery
147-
* */
149+
*
150+
*/
148151
@Scheduled(fixedDelay = 5000, initialDelay = 30000)
149152
public void scheduleComplianceReport() {
150-
final String ctx = CLASSNAME + ".scheduleComplianceReport";
151153

152154
List<UtmComplianceReportSchedule> schedulesList = findAll();
155+
schedulesList.forEach(this::processSchedule);
156+
157+
}
158+
159+
private void processSchedule(UtmComplianceReportSchedule schedule) {
160+
161+
Optional<User> userOpt = userService.getUserWithAuthorities(schedule.getUserId());
162+
163+
if (userOpt.isEmpty()) {
164+
log.error("Schedule {} skipped: user {} not found", schedule.getId(), schedule.getUserId());
165+
return;
166+
}
167+
168+
User user = userOpt.get();
169+
170+
Instant now = Instant.now(Clock.systemUTC());
171+
Instant next = getNext(schedule.getScheduleString(), schedule.getLastExecutionTime(), now);
172+
173+
if (!isTimeToExecute(next, now)) {
174+
return;
175+
}
176+
177+
complianceMailService.sendComplianceByMail(schedule.getUrlWithParams(), user.getEmail());
178+
markExecuted(schedule, next);
153179

154-
schedulesList.forEach(current -> {
155-
Optional<User> user = userService.getUserWithAuthorities(current.getUserId());
156-
try {
157-
Instant currentDate = Instant.now(Clock.systemUTC());
158-
Instant next = getNext(current.getScheduleString(), current.getLastExecutionTime(), currentDate);
159-
if (isTimeToExecute(next, currentDate)) {
160-
// Set the next execution time (Base time seed)
161-
current.setLastExecutionTime(next);
162-
utmComplianceReportScheduleRepository.save(current);
163-
complianceMailService.sendComplianceByMail(current.getUrlWithParams(), user.get().getEmail());
164-
}
165-
166-
} catch (Exception e) {
167-
String msg = ctx + ": " + e.getLocalizedMessage();
168-
log.error(msg);
169-
applicationEventService.createEvent(msg, ApplicationEventType.ERROR);
170-
}
171-
});
180+
}
181+
182+
@Transactional
183+
public void markExecuted(UtmComplianceReportSchedule schedule, Instant next) {
184+
schedule.setLastExecutionTime(next);
185+
utmComplianceReportScheduleRepository.save(schedule);
172186
}
173187

174188
/***
@@ -180,7 +194,8 @@ private boolean isTimeToExecute(Instant next, Instant currentDate) {
180194

181195
/**
182196
* Method to know the next valid Instant to execute the task, even if the system was shut down for a while
183-
* */
197+
*
198+
*/
184199
private Instant getNext(String cronExpresion, Instant lastExecution, Instant currentDate) {
185200
CronExpression parse = CronExpression.parse(cronExpresion);
186201
Instant possibleNext = Objects.requireNonNull(parse.next(lastExecution.atZone(ZoneOffset.UTC))).toInstant();
@@ -194,7 +209,7 @@ private Instant getNext(String cronExpresion, Instant lastExecution, Instant cur
194209
// near next execution to avoid extra executions, because the general scheduler that calls these methods,
195210
// is every 5 seconds
196211
Long diffBetweenCurrentAndPossibleNext = currentSecs - possibleNext.getEpochSecond();
197-
Integer rate = Long.valueOf(diffBetweenCurrentAndPossibleNext/diffBetweenLastAndNext).intValue();
212+
Integer rate = Long.valueOf(diffBetweenCurrentAndPossibleNext / diffBetweenLastAndNext).intValue();
198213
Instant resultNext = lastExecution.atZone(ZoneOffset.UTC).toInstant().plusSeconds(diffBetweenLastAndNext * rate);
199214
return resultNext.atZone(ZoneOffset.UTC).toInstant();
200215
}
@@ -205,7 +220,7 @@ private Specification<UtmComplianceReportSchedule> createSpecification(UtmCompli
205220

206221
User user = userService.getCurrentUserLogin();
207222
Specification<UtmComplianceReportSchedule> specification = Specification.where((root, query, criteriaBuilder) ->
208-
criteriaBuilder.equal(root.get("userId"), user.getId()));
223+
criteriaBuilder.equal(root.get("userId"), user.getId()));
209224
if (criteria != null) {
210225
if (criteria.getName() != null) {
211226
specification = specification.and(buildSpecification(criteria.getName(),
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.park.utmstack.service.dto.application_modules;
2+
3+
import lombok.Data;
4+
5+
@Data
6+
public class CSError {
7+
private int code;
8+
private String message;
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.park.utmstack.service.dto.application_modules;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Data;
5+
6+
@Data
7+
public class Meta {
8+
@JsonProperty("trace_id") private String traceId;
9+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.park.utmstack.service.dto.application_modules;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
5+
public class ModuleConfigValidationErrorMapper {
6+
7+
private static final ObjectMapper mapper = new ObjectMapper();
8+
9+
public static ModuleConfigValidationErrorResponse parse(String errorText) {
10+
try {
11+
ObjectMapper mapper = new ObjectMapper();
12+
13+
int start = errorText.indexOf("{\"meta\"");
14+
if (start == -1) return null;
15+
16+
String innerJson = errorText.substring(start);
17+
18+
return mapper.readValue(innerJson, ModuleConfigValidationErrorResponse.class);
19+
20+
} catch (Exception e) {
21+
return null;
22+
}
23+
}
24+
25+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.park.utmstack.service.dto.application_modules;
2+
3+
import lombok.Data;
4+
5+
import java.util.List;
6+
7+
@Data
8+
public class ModuleConfigValidationErrorResponse {
9+
private Meta meta;
10+
private List<CSError> errors;
11+
}
12+
13+

0 commit comments

Comments
 (0)