Skip to content

Commit 3d46d82

Browse files
committed
avniproject/avni-client#1458 | Capture errored entity details when transactional data's constraint violation occurs
1 parent 00f325d commit 3d46d82

12 files changed

+53
-18
lines changed

avni-server-api/src/main/java/org/avni/server/dao/OperatingIndividualScopeAwareRepository.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
import org.avni.server.dao.sync.TransactionDataCriteriaBuilderUtil;
66
import org.avni.server.domain.*;
77
import org.avni.server.framework.security.UserContextHolder;
8+
import org.avni.server.service.exception.ConstraintViolationExceptionAcrossOrganisations;
89
import org.avni.server.util.JsonObjectUtil;
10+
import org.hibernate.exception.ConstraintViolationException;
11+
import org.springframework.dao.DataIntegrityViolationException;
912
import org.springframework.data.domain.Page;
1013
import org.springframework.data.domain.Slice;
1114
import org.springframework.data.jpa.domain.Specification;
@@ -16,6 +19,7 @@
1619
import java.util.ArrayList;
1720
import java.util.Date;
1821
import java.util.List;
22+
import java.util.Objects;
1923

2024
@SuppressWarnings("rawtypes")
2125
@NoRepositoryBean
@@ -176,4 +180,15 @@ default <B extends CHSEntity, A extends CHSEntity> void addPredicate(CriteriaBui
176180
predicates.add(cb.equal(from.get("id"), cb.literal(0)));
177181
}
178182
}
183+
184+
default <S extends T> S saveEntity(S entity) {
185+
try {
186+
return save(entity);
187+
} catch (DataIntegrityViolationException dive) {
188+
if (Objects.isNull(entity.getId()) && dive.getCause() != null && dive.getCause().getClass().equals(ConstraintViolationException.class)) {
189+
throw new ConstraintViolationExceptionAcrossOrganisations(String.format("Entity=> ID: %d, UUID: %s, Type:%s, User:%s, Msg: %s", entity.getId(), entity.getUuid(), entity.getClass().getCanonicalName(), entity.getLastModifiedByName(), dive.getMessage()), (ConstraintViolationException) dive.getCause());
190+
}
191+
throw dive;
192+
}
193+
}
179194
}

avni-server-api/src/main/java/org/avni/server/service/ChecklistService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public ChecklistItem findChecklistItem(String checklistUUID, String checklistIte
3737

3838
@Transactional(Transactional.TxType.REQUIRED)
3939
public void saveItem(ChecklistItem checklistItem) {
40-
checklistItemRepository.save(checklistItem);
40+
checklistItemRepository.saveEntity(checklistItem);
4141
}
4242

4343
@Override

avni-server-api/src/main/java/org/avni/server/service/CommentService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ public CommentService(CommentRepository commentRepository, IndividualRepository
3333
public Comment saveComment(CommentContract commentContract) {
3434
Comment comment = new Comment();
3535
buildComment(commentContract, comment);
36-
return commentRepository.save(comment);
36+
return commentRepository.saveEntity(comment);
3737
}
3838

3939
public Comment editComment(CommentContract commentContract, Comment existingComment) {
4040
buildComment(commentContract, existingComment);
41-
return commentRepository.save(existingComment);
41+
return commentRepository.saveEntity(existingComment);
4242
}
4343

4444
private void buildComment(CommentContract commentContract, Comment comment) {
@@ -54,7 +54,7 @@ private void buildComment(CommentContract commentContract, Comment comment) {
5454

5555
public Comment deleteComment(Comment comment) {
5656
comment.setVoided(true);
57-
return commentRepository.save(comment);
57+
return commentRepository.saveEntity(comment);
5858
}
5959

6060
@Override

avni-server-api/src/main/java/org/avni/server/service/CommentThreadService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ public CommentThread createNewThread(CommentThreadContract threadContract) {
4444
comments.add(comment);
4545
});
4646
commentThread.setComments(comments);
47-
return commentThreadRepository.save(commentThread);
47+
return commentThreadRepository.saveEntity(commentThread);
4848
}
4949

5050
public CommentThread resolveThread(CommentThread commentThread) {
5151
commentThread.setStatus(CommentThread.CommentThreadStatus.Resolved);
5252
commentThread.setResolvedDateTime(new DateTime());
53-
return commentThreadRepository.save(commentThread);
53+
return commentThreadRepository.saveEntity(commentThread);
5454
}
5555

5656
@Override

avni-server-api/src/main/java/org/avni/server/service/EncounterService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ public Encounter save(Encounter encounter) {
186186
if (individual.getAddressLevel() != null) {
187187
encounter.setAddressId(individual.getAddressLevel().getId());
188188
}
189-
return encounterRepository.save(encounter);
189+
return encounterRepository.saveEntity(encounter);
190190
}
191191

192192
public Page<Encounter> search(EncounterSearchRequest encounterSearchRequest) {

avni-server-api/src/main/java/org/avni/server/service/GroupSubjectService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public OperatingIndividualScopeAwareRepository<GroupSubject> repository() {
4040
public GroupSubject save(GroupSubject groupSubject) throws ValidationException {
4141
this.addSyncAttributes(groupSubject);
4242
assignMemberToTheAssigneeOfGroup(groupSubject);
43-
return groupSubjectRepository.save(groupSubject);
43+
return groupSubjectRepository.saveEntity(groupSubject);
4444
}
4545

4646
private void assignMemberToTheAssigneeOfGroup(GroupSubject groupSubject) {

avni-server-api/src/main/java/org/avni/server/service/IndividualService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ public Individual voidSubject(Individual individual) {
348348
assertNoUnVoidedEncounters(individual);
349349
assertNoUnVoidedEnrolments(individual);
350350
individual.setVoided(true);
351-
return individualRepository.save(individual);
351+
return individualRepository.saveEntity(individual);
352352
}
353353

354354
private void assertNoUnVoidedEnrolments(Individual individual) {
@@ -396,7 +396,7 @@ public Object getObservationValueForUpload(FormElement formElement, String answe
396396
@Messageable(EntityType.Subject)
397397
public Individual save(Individual individual) {
398398
individual.addConceptSyncAttributeValues(individual.getSubjectType(), individual.getObservations());
399-
return individualRepository.save(individual);
399+
return individualRepository.saveEntity(individual);
400400
}
401401

402402
public String findPhoneNumber(long subjectId) {

avni-server-api/src/main/java/org/avni/server/service/ProgramEncounterService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ public ProgramEncounter save(ProgramEncounter programEncounter) {
230230
if (individual.getAddressLevel() != null) {
231231
programEncounter.setAddressId(individual.getAddressLevel().getId());
232232
}
233-
programEncounter = programEncounterRepository.save(programEncounter);
233+
programEncounter = programEncounterRepository.saveEntity(programEncounter);
234234
return programEncounter;
235235
}
236236

avni-server-api/src/main/java/org/avni/server/service/ProgramEnrolmentService.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public ProgramEnrolment programEnrolmentSave(ProgramEnrolmentRequest request) {
206206
programEnrolment.setIndividual(individual);
207207
saveIdentifierAssignments(programEnrolment, request);
208208
}
209-
programEnrolment = programEnrolmentRepository.save(programEnrolment);
209+
programEnrolment = programEnrolmentRepository.saveEntity(programEnrolment);
210210

211211
if (request.getVisitSchedules() != null && request.getVisitSchedules().size() > 0) {
212212
programEncounterService.saveVisitSchedules(request.getUuid(), request.getVisitSchedules(), null);
@@ -223,7 +223,7 @@ public ProgramEnrolment programEnrolmentSave(ProgramEnrolmentRequest request) {
223223
@Messageable(EntityType.ProgramEnrolment)
224224
public ProgramEnrolment save(ProgramEnrolment programEnrolment) {
225225
this.addSyncAttributes(programEnrolment);
226-
return programEnrolmentRepository.save(programEnrolment);
226+
return programEnrolmentRepository.saveEntity(programEnrolment);
227227
}
228228

229229
private void addSyncAttributes(ProgramEnrolment enrolment) {
@@ -250,7 +250,7 @@ private Checklist saveChecklist(ChecklistContract checklistContract, String prog
250250
Checklist existingChecklist = checklistRepository.findByProgramEnrolmentId(programEnrolment.getId());
251251
if (existingChecklist != null) {
252252
existingChecklist.setBaseDate(checklistContract.getBaseDate());
253-
return checklistRepository.save(existingChecklist);
253+
return checklistRepository.saveEntity(existingChecklist);
254254
}
255255
Checklist checklist = new Checklist();
256256
checklist.assignUUIDIfRequired();
@@ -259,15 +259,15 @@ private Checklist saveChecklist(ChecklistContract checklistContract, String prog
259259
checklist.setBaseDate(checklistContract.getBaseDate());
260260
checklist.setChecklistDetail(checklistDetail);
261261
checklist.setProgramEnrolment(programEnrolment);
262-
Checklist savedChecklist = checklistRepository.save(checklist);
262+
Checklist savedChecklist = checklistRepository.saveEntity(checklist);
263263
checklistContract.getItems().forEach(item -> {
264264
ChecklistItem checklistItem = new ChecklistItem();
265265
checklistItem.assignUUIDIfRequired();
266266
String checklistItemDetailUUID = item.getDetail().getUuid();
267267
ChecklistItemDetail checklistItemDetail = checklistItemDetailRepository.findByUuid(checklistItemDetailUUID);
268268
checklistItem.setChecklistItemDetail(checklistItemDetail);
269269
checklistItem.setChecklist(savedChecklist);
270-
checklistItemRepository.save(checklistItem);
270+
checklistItemRepository.saveEntity(checklistItem);
271271
});
272272
return savedChecklist;
273273
}
@@ -276,7 +276,7 @@ private Checklist saveChecklist(ChecklistContract checklistContract, String prog
276276
public ProgramEnrolment voidEnrolment(ProgramEnrolment programEnrolment) {
277277
assertNoUnVoidedProgramEncounters(programEnrolment);
278278
programEnrolment.setVoided(true);
279-
return programEnrolmentRepository.save(programEnrolment);
279+
return programEnrolmentRepository.saveEntity(programEnrolment);
280280
}
281281

282282
private void assertNoUnVoidedProgramEncounters(ProgramEnrolment programEnrolment) {

avni-server-api/src/main/java/org/avni/server/service/SubjectMigrationService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public void changeSubjectsAddressLevel(List<Individual> subjects, AddressLevel d
127127
subjects.forEach(individual -> {
128128
this.markSubjectMigrationIfRequired(individual.getUuid(), null, destAddressLevel, null, individual.getObservations(), true);
129129
individual.setAddressLevel(destAddressLevel);
130-
individualRepository.save(individual);
130+
individualRepository.saveEntity(individual);
131131
});
132132
}
133133
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.avni.server.service.exception;
2+
3+
import org.hibernate.exception.ConstraintViolationException;
4+
5+
public class ConstraintViolationExceptionAcrossOrganisations extends ConstraintViolationException {
6+
public ConstraintViolationExceptionAcrossOrganisations(String message, ConstraintViolationException cve) {
7+
super(message, cve.getSQLException(), cve.getSQL(), cve.getConstraintName());
8+
}
9+
}

avni-server-api/src/main/java/org/avni/server/web/ErrorInterceptors.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
import org.avni.server.domain.accessControl.AvniAccessException;
66
import org.avni.server.domain.accessControl.AvniNoUserSessionException;
77
import org.avni.server.framework.rest.RestControllerErrorResponse;
8+
import org.avni.server.service.exception.ConstraintViolationExceptionAcrossOrganisations;
89
import org.avni.server.util.BadRequestError;
910
import org.avni.server.util.BugsnagReporter;
1011
import org.avni.server.web.util.ErrorBodyBuilder;
12+
import org.hibernate.exception.ConstraintViolationException;
1113
import org.springframework.beans.factory.annotation.Autowired;
1214
import org.springframework.beans.factory.annotation.Value;
15+
import org.springframework.dao.DataIntegrityViolationException;
1316
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
1417
import org.springframework.http.HttpHeaders;
1518
import org.springframework.http.HttpStatus;
@@ -91,6 +94,14 @@ public ResponseEntity fileUploadSizeLimitExceededError(Exception e) {
9194
return ResponseEntity.badRequest().body(String.format("Maximum upload file size exceeded; ensure file size is less than %s.", maxFileSize));
9295
}
9396

97+
@ExceptionHandler(value = {DataIntegrityViolationException.class, ConstraintViolationException.class, ConstraintViolationExceptionAcrossOrganisations.class})
98+
public ResponseEntity entityUpsertErrorDueToDataConstraintViolation(Exception e) {
99+
bugsnagReporter.logAndReportToBugsnag(e);
100+
return ResponseEntity.status(HttpStatus.CONFLICT).body(
101+
String.format("Entity create or update failed due to constraint violation: %s",
102+
errorBodyBuilder.getErrorMessageBody(e)));
103+
}
104+
94105
@ExceptionHandler(value = {Exception.class})
95106
public ResponseEntity unknownException(Exception e) {
96107
if (e instanceof BadRequestError) {

0 commit comments

Comments
 (0)