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
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@ public String dataSyncToServer(@RequestBody String requestOBJ,
@PostMapping(value = { "/diagnostic-documents-to-server" })
public String diagnosticDocumentsToServer(@RequestHeader(value = "Authorization") String authorization,
@RequestHeader(value = "ServerAuthorization") String serverAuthorization,
@RequestParam Long villageId) {
@RequestParam(required = false) Long villageId) {
OutputResponse response = new OutputResponse();
try {
String s = diagnosticDocumentPushServiceImpl.pushPendingDocuments(serverAuthorization, villageId);
String s = diagnosticDocumentPushServiceImpl.pushPendingDocuments(serverAuthorization);
if (s != null)
response.setResponse(s);
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,21 @@
String sha256Hash = (String) item.get("sha256Hash");
if (sha256Hash != null && !sha256Hash.equalsIgnoreCase(sha256Hex(plaintext))) {
ack.put("status", "FAILED");
ack.put("error", "sha256 mismatch on receipt");

Check failure on line 78 in src/main/java/com/iemr/mmu/service/dataSyncLayerCentral/DiagnosticDocumentIngestService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "error" 3 times.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaERjjUlg6KKAFeMtdQ4&open=AaERjjUlg6KKAFeMtdQ4&pullRequest=198
return ack;
}

Long beneficiaryId = asLong(item.get("beneficiaryId"));
Long villageId = asLong(item.get("villageId"));
Long vanId = asLong(item.get("vanID"));
String orderType = (String) item.get("orderType");
String storedFileName = (String) item.get("storedFileName");
if (villageId == null || beneficiaryId == null || orderType == null || documentType == null
if (vanId == null || beneficiaryId == null || orderType == null || documentType == null
|| storedFileName == null) {
ack.put("status", "FAILED");
ack.put("error", "Missing villageId, beneficiaryId, orderType, documentType or storedFileName");
ack.put("error", "Missing vanID, beneficiaryId, orderType, documentType or storedFileName");
return ack;
}
String s3Key = villageId + "/" + beneficiaryId + "/" + orderType + "/" + documentType + "/"
String s3Key = vanId + "/" + beneficiaryId + "/" + orderType + "/" + documentType + "/"
+ storedFileName;
String contentType = (String) item.get("contentType");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -66,11 +67,10 @@
@Autowired
private CryptoUtil cryptoUtil;

public String pushPendingDocuments(String Authorization, Long villageId) throws Exception {
public String pushPendingDocuments(String Authorization) throws Exception {

Check warning on line 70 in src/main/java/com/iemr/mmu/service/dataSyncLayerCentral/DiagnosticDocumentPushServiceImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaERjjN6g6KKAFeMtdQ1&open=AaERjjN6g6KKAFeMtdQ1&pullRequest=198

Check warning on line 70 in src/main/java/com/iemr/mmu/service/dataSyncLayerCentral/DiagnosticDocumentPushServiceImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaERjjN6g6KKAFeMtdQ2&open=AaERjjN6g6KKAFeMtdQ2&pullRequest=198

Check failure on line 70 in src/main/java/com/iemr/mmu/service/dataSyncLayerCentral/DiagnosticDocumentPushServiceImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 48 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=PSMRI_MMU-API&issues=AaERjjN6g6KKAFeMtdQ3&open=AaERjjN6g6KKAFeMtdQ3&pullRequest=198
List<Map<String, Object>> pendingRows = diagnosticDocumentRepository.findPendingDocuments();
boolean anyRowsFound = !pendingRows.isEmpty();
int totalAttempted = 0;
int totalSucceeded = 0;
List<String> failureReasons = new ArrayList<>();

for (int offset = 0; offset < pendingRows.size(); offset += batchSize) {
List<Map<String, Object>> rows = pendingRows.subList(offset, Math.min(offset + batchSize, pendingRows.size()));
Expand All @@ -88,13 +88,12 @@
} catch (Exception e) {
logger.warn("Skipping diagnostic document push, could not read file off disk: id={}, error={}",
rowId, e.getMessage());
diagnosticDocumentRepository.markPushFailed(rowId,
"Could not read file off shared filesystem: " + e.getMessage());
markFailed(rowId, "Could not read file off shared filesystem: " + e.getMessage(), failureReasons);
continue;
}
if (base64Plaintext == null) {
logger.warn("Skipping diagnostic document push, decrypt failed: id={}", rowId);
diagnosticDocumentRepository.markPushFailed(rowId, "Decrypt failed");
markFailed(rowId, "Decrypt failed", failureReasons);
continue;
}

Expand All @@ -118,7 +117,6 @@
item.put("vanID", row.get("vanID"));
item.put("parkingPlaceID", row.get("parkingPlaceID"));
item.put("vanSerialNo", row.get("vanSerialNo"));
item.put("villageId", villageId);
item.put("fileContentBase64", base64Plaintext);
payloadItems.add(item);
rowsById.put(rowId, row);
Expand All @@ -128,8 +126,6 @@
continue;
}

totalAttempted += payloadItems.size();

String requestOBJ = new Gson().toJson(payloadItems);
List<Map<String, Object>> acks;
try {
Expand All @@ -140,7 +136,7 @@

if (response == null || !response.hasBody()) {
logger.warn("No response body from central server for diagnostic document push, marking batch failed");
markBatchFailed(rowsById, "No response body from central server");
markBatchFailed(rowsById, "No response body from central server", failureReasons);
continue;
}

Expand All @@ -149,28 +145,28 @@
JsonElement parsedBody = JsonParser.parseString(response.getBody());
if (!parsedBody.isJsonObject()) {
logger.warn("Unexpected response shape from central server for diagnostic document push, marking batch failed");
markBatchFailed(rowsById, "Unexpected response shape from central server");
markBatchFailed(rowsById, "Unexpected response shape from central server", failureReasons);
continue;
}
JsonObject envelope = parsedBody.getAsJsonObject();
if (!envelope.has("statusCode") || envelope.get("statusCode").getAsInt() != 200
|| !envelope.has("data")) {
logger.warn("Central server reported failure for diagnostic document push batch, marking batch failed: {}",
response.getBody());
markBatchFailed(rowsById, "Central server reported failure: " + response.getBody());
markBatchFailed(rowsById, "Central server reported failure: " + response.getBody(), failureReasons);
continue;
}

Type ackListType = new TypeToken<List<Map<String, Object>>>() {
}.getType();
acks = new Gson().fromJson(envelope.get("data"), ackListType);
if (acks == null) {
markBatchFailed(rowsById, "Central server returned no acknowledgements");
markBatchFailed(rowsById, "Central server returned no acknowledgements", failureReasons);
continue;
}
} catch (Exception e) {
logger.error("Error calling central server for diagnostic document push, marking batch failed", e);
markBatchFailed(rowsById, "Error calling central server: " + e.getMessage());
markBatchFailed(rowsById, "Error calling central server: " + e.getMessage(), failureReasons);
continue;
}

Expand All @@ -188,8 +184,7 @@
String reason = (String) ack.get("error");
logger.warn("Central server rejected diagnostic document push: id={}, externalOrderId={}, documentType={}, error={}",
rowId, ack.get("externalOrderId"), ack.get("documentType"), reason);
diagnosticDocumentRepository.markPushFailed(rowId,
reason != null ? reason : "Central server rejected the document");
markFailed(rowId, reason != null ? reason : "Central server rejected the document", failureReasons);
}
}
if (!unmatchedRowsById.isEmpty()) {
Expand All @@ -198,30 +193,55 @@
logger.warn(
"Diagnostic document push: {} row(s) in this batch got no matching ack back, marking failed",
unmatchedRowsById.size());
markBatchFailed(unmatchedRowsById, "No acknowledgement received from central server");
markBatchFailed(unmatchedRowsById, "No acknowledgement received from central server", failureReasons);
}

totalSucceeded += batchSuccessCount;
logger.info("Diagnostic document push batch complete: attempted={}, succeeded={}", payloadItems.size(),
batchSuccessCount);
}

if (!anyRowsFound) {
return "No pending diagnostic documents to sync";
if (pendingRows.isEmpty()) {
return "No data to sync";
}
if (totalAttempted == 0) {
return "No documents could be decrypted for push";

// Same table-level summary shape as /van-to-server (UploadDataToServerImpl) so the
// data sync screen can read both the same way.
int totalRecords = pendingRows.size();
int failedRecords = failureReasons.size();
String status;
if (failedRecords == 0) {
status = "success";
} else if (totalSucceeded == 0) {
status = "failed";
} else {
status = "partial";
}
if (totalSucceeded == 0) {
// Documents WERE decrypted and sent, but none were accepted (e.g. the central
// server rejected every batch) - the specific reason for each row is recorded in
// its own docSyncFailureReason, this is just the overall-outcome summary.
return "Documents were sent but none were accepted by the central server";

Map<String, Object> finalResponse = new LinkedHashMap<>();
finalResponse.put("response",
"success".equals(status) ? "Data sync completed successfully" : "Data sync completed with failures");
finalResponse.put("schemaName", "db_iemr");
finalResponse.put("tableName", "tb_diagnostic_document");
finalResponse.put("status", status);
finalResponse.put("totalRecords", totalRecords);
finalResponse.put("successfulRecords", totalSucceeded);
finalResponse.put("failedRecords", failedRecords);
if (!failureReasons.isEmpty()) {
finalResponse.put("failureReasons", failureReasons);
}

logger.info("Diagnostic document push complete overall: attempted={}, succeeded={}", totalAttempted,
totalSucceeded);
return "Data successfully synced";
logger.info("Diagnostic document push complete: {} (Success: {}, Failed: {}, Total: {})", status,
totalSucceeded, failedRecords, totalRecords);
return new Gson().toJson(finalResponse);
}

/***
* @purpose Marks one row failed locally and records its reason for the run summary.
*/
private void markFailed(Long rowId, String reason, List<String> failureReasons) {
diagnosticDocumentRepository.markPushFailed(rowId, reason);
failureReasons.add("documentId " + rowId + ": " + reason);
}

/***
Expand All @@ -230,9 +250,9 @@
* batch as failed, matching how /van-to-server marks a whole batch failed on a
* connection error, rather than leaving them stuck at docsProcessed='N' forever.
*/
private void markBatchFailed(Map<Long, Map<String, Object>> rowsById, String reason) {
private void markBatchFailed(Map<Long, Map<String, Object>> rowsById, String reason, List<String> failureReasons) {
for (Long rowId : rowsById.keySet()) {
diagnosticDocumentRepository.markPushFailed(rowId, reason);
markFailed(rowId, reason, failureReasons);
}
}

Expand Down
Loading