From 4770d358922bb3ed426653a39041a52bf6f0e32f Mon Sep 17 00:00:00 2001 From: "julian.lxs" Date: Fri, 27 Dec 2024 16:21:53 +0100 Subject: [PATCH] add exceptions and handling --- .../ExternalPlatformConnectionException.java | 7 ++ .../UserServiceConnectionException.java | 7 ++ .../entity/UnfinishedGradingEntity.java | 4 + .../service/GradingService.java | 108 ++++++++++++------ .../graphql/service/mutation.graphqls | 3 +- 5 files changed, 93 insertions(+), 36 deletions(-) create mode 100644 src/main/java/de/unistuttgart/iste/meitrex/assignment_service/exception/ExternalPlatformConnectionException.java create mode 100644 src/main/java/de/unistuttgart/iste/meitrex/assignment_service/exception/UserServiceConnectionException.java diff --git a/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/exception/ExternalPlatformConnectionException.java b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/exception/ExternalPlatformConnectionException.java new file mode 100644 index 0000000..60aec90 --- /dev/null +++ b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/exception/ExternalPlatformConnectionException.java @@ -0,0 +1,7 @@ +package de.unistuttgart.iste.meitrex.assignment_service.exception; + +public class ExternalPlatformConnectionException extends Exception { + public ExternalPlatformConnectionException(String message) { + super(message); + } +} diff --git a/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/exception/UserServiceConnectionException.java b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/exception/UserServiceConnectionException.java new file mode 100644 index 0000000..61e2027 --- /dev/null +++ b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/exception/UserServiceConnectionException.java @@ -0,0 +1,7 @@ +package de.unistuttgart.iste.meitrex.assignment_service.exception; + +public class UserServiceConnectionException extends Exception { + public UserServiceConnectionException(String message) { + super(message); + } +} diff --git a/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/persistence/entity/UnfinishedGradingEntity.java b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/persistence/entity/UnfinishedGradingEntity.java index 9fd7095..07639d6 100644 --- a/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/persistence/entity/UnfinishedGradingEntity.java +++ b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/persistence/entity/UnfinishedGradingEntity.java @@ -41,6 +41,10 @@ public PrimaryKey getId() { return primaryKey; } + public void incrementNumberOfTries() { + numberOfTries++; + } + public static UnfinishedGradingEntity fromJson(JSONObject gradingJson, UUID assignmentId) { String studentId = gradingJson.getString("studentId"); return new UnfinishedGradingEntity(new PrimaryKey(studentId, assignmentId), gradingJson.toString(), 0); diff --git a/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/service/GradingService.java b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/service/GradingService.java index 104e831..5765753 100644 --- a/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/service/GradingService.java +++ b/src/main/java/de/unistuttgart/iste/meitrex/assignment_service/service/GradingService.java @@ -1,7 +1,7 @@ package de.unistuttgart.iste.meitrex.assignment_service.service; -import de.unistuttgart.iste.meitrex.assignment_service.exception.ManualMappingRequiredException; +import de.unistuttgart.iste.meitrex.assignment_service.exception.*; import de.unistuttgart.iste.meitrex.assignment_service.persistence.entity.*; import de.unistuttgart.iste.meitrex.assignment_service.persistence.mapper.AssignmentMapper; import de.unistuttgart.iste.meitrex.assignment_service.persistence.repository.GradingRepository; @@ -93,7 +93,22 @@ public void importGradingsForAssignment(final UUID assignmentId, final LoggedInU body = response.join(); } - List gradingEntityList = parseStringIntoGradingEntityList(body, assignment); + if (body == null) { + // something went wrong, can't do anything, try again next time + throw new RuntimeException( + new ExternalPlatformConnectionException("Querying gradings for externalAssignmentId %s went wrong.".formatted(externalId))); // wrapping exception + // return; TODO return or throw wrapped exception? + } + + final List> meitrexStudentInfoList; + try { + meitrexStudentInfoList = getMeitrexStudentInfoList(); + } catch (UserServiceConnectionException e){ + throw new RuntimeException(e); // wrapping exception + // return; TODO return or throw wrapped exception? + } + + List gradingEntityList = parseStringIntoGradingEntityList(body, assignment, meitrexStudentInfoList); for (GradingEntity gradingEntity : gradingEntityList) { gradingRepository.save(gradingEntity); @@ -109,17 +124,19 @@ public void importGradingsForAssignment(final UUID assignmentId, final LoggedInU * @param assignmentEntity the assignment id which the gradings belong to * @return List of parsed grading entities */ - private List parseStringIntoGradingEntityList(final String string, final AssignmentEntity assignmentEntity) { + private List parseStringIntoGradingEntityList(final String string, final AssignmentEntity assignmentEntity, final List> meitrexStudentInfoList) { JSONArray gradingArray = new JSONArray(string); final List gradingEntityList = new ArrayList<>(gradingArray.length()); GradingEntity gradingEntity; for (int i = 0; i < gradingArray.length(); i++) { JSONObject jsonObject = gradingArray.getJSONObject(i); try { - gradingEntity = parseIntoGradingEntity(jsonObject, assignmentEntity); + gradingEntity = parseIntoGradingEntity(jsonObject, assignmentEntity, meitrexStudentInfoList); gradingEntityList.add(gradingEntity); } catch (ManualMappingRequiredException e) { // fine, will be handled by manual mapping of admin + } catch (ExternalPlatformConnectionException e) { + // can't be handled further, will be tried again when manual mapping happened } } return gradingEntityList; @@ -132,29 +149,26 @@ private List parseStringIntoGradingEntityList(final String string * @param assignmentEntity the assignment id which the grading belongs to * @return parsed grading entity */ - private GradingEntity parseIntoGradingEntity(final JSONObject jsonObject, final AssignmentEntity assignmentEntity) throws ManualMappingRequiredException { + private GradingEntity parseIntoGradingEntity(final JSONObject jsonObject, final AssignmentEntity assignmentEntity, final List> meitrexStudentInfoList) throws ManualMappingRequiredException, ExternalPlatformConnectionException { final GradingEntity gradingEntity = new GradingEntity(); String externalStudentId = jsonObject.getString("studentId"); // TODO match this to Meitrex student id UUID studentId; try { - studentId = getStudentIdFromExternalStudentId(externalStudentId); + studentId = getStudentIdFromExternalStudentId(externalStudentId, meitrexStudentInfoList); } catch (ManualMappingRequiredException e) { // ManualMappingInstance is added to repository, so that an admin can map manually JSONObject externalStudentInfo = e.getExternalStudentInfo(); manualMappingInstanceRepository.save(ManualMappingInstanceEntity.fromJson(externalStudentInfo)); // Grading is added to unfinished grading repository, so that it can be tried again, when a manual mapping was done. - UnfinishedGradingEntity unfinishedGradingEntity; - Optional foundEntityOptional = unfinishedGradingRepository.findById(new UnfinishedGradingEntity.PrimaryKey(externalStudentId, assignmentEntity.getAssessmentId())); - if (foundEntityOptional.isPresent()) { - unfinishedGradingEntity = foundEntityOptional.get(); - unfinishedGradingEntity.setNumberOfTries(unfinishedGradingEntity.getNumberOfTries() + 1); - } else { - unfinishedGradingEntity = UnfinishedGradingEntity.fromJson(jsonObject, assignmentEntity.getAssessmentId()); - } - unfinishedGradingRepository.save(unfinishedGradingEntity); + addToUnfinishedGradingRepository(jsonObject, assignmentEntity, externalStudentId); + + throw(e); + } catch (ExternalPlatformConnectionException e) { + // Grading is added to unfinished grading repository, so that it can be tried again, when a manual mapping was done. + addToUnfinishedGradingRepository(jsonObject, assignmentEntity, externalStudentId); throw(e); } @@ -207,6 +221,19 @@ private GradingEntity parseIntoGradingEntity(final JSONObject jsonObject, final return gradingEntity; } + private void addToUnfinishedGradingRepository(final JSONObject jsonObject, final AssignmentEntity assignmentEntity, final String externalStudentId) { + // Grading is added to unfinished grading repository, so that it can be tried again, when a manual mapping was done. + UnfinishedGradingEntity unfinishedGradingEntity; + Optional foundEntityOptional = unfinishedGradingRepository.findById(new UnfinishedGradingEntity.PrimaryKey(externalStudentId, assignmentEntity.getAssessmentId())); + if (foundEntityOptional.isPresent()) { + unfinishedGradingEntity = foundEntityOptional.get(); + unfinishedGradingEntity.incrementNumberOfTries(); + } else { + unfinishedGradingEntity = UnfinishedGradingEntity.fromJson(jsonObject, assignmentEntity.getAssessmentId()); + } + unfinishedGradingRepository.save(unfinishedGradingEntity); + } + /** * Takes gradingEntity and publishes the {@link ContentProgressedEvent} to the dapr pubsub. @@ -260,19 +287,21 @@ private void logGradingImported(final GradingEntity gradingEntity) { topicPublisher.notifyUserWorkedOnContent(userProgressLogEvent); } - private UUID getStudentIdFromExternalStudentId(final String externalStudentId) throws ManualMappingRequiredException { + private UUID getStudentIdFromExternalStudentId(final String externalStudentId, final List> meitrexStudentInfoList) throws ManualMappingRequiredException, ExternalPlatformConnectionException { Optional studentMappingEntity = studentMappingRepository.findById(externalStudentId); if (studentMappingEntity.isPresent()) { return studentMappingEntity.get().getMeitrexStudentId(); } - UUID newMeitrexStudentId = findNewStudentIdFromExternalStudentId(externalStudentId); // throws exception if nothing is found + UUID newMeitrexStudentId = findNewStudentIdFromExternalStudentId(externalStudentId, meitrexStudentInfoList); // throws exception if nothing is found studentMappingRepository.save(new StudentMappingEntity(externalStudentId, newMeitrexStudentId)); return newMeitrexStudentId; } - private UUID findNewStudentIdFromExternalStudentId(final String externalStudentId) throws ManualMappingRequiredException { + private UUID findNewStudentIdFromExternalStudentId(final String externalStudentId, final List> meitrexStudentInfoList) throws ManualMappingRequiredException, ExternalPlatformConnectionException { JSONObject externalStudentInfo = getExternalStudentInfo(externalStudentId); - List> meitrexStudentInfoList = getMeitrexStudentInfoList(); + + // list is fetched from user service at the beginning, rather than for each grading + // final List> meitrexStudentInfoList = getMeitrexStudentInfoList(); Object lastName = externalStudentInfo.get("lastname"); Object firstName = externalStudentInfo.get("firstname"); @@ -282,7 +311,7 @@ private UUID findNewStudentIdFromExternalStudentId(final String externalStudentI .filter(userInfo -> userInfo.get("lastName").equals(lastName)) .toList(); if (filteredByLastName.isEmpty()) { - throw new IllegalArgumentException("No matching student found!"); // TODO create better exception + throw new ManualMappingRequiredException(externalStudentInfo); } else if (filteredByLastName.size() == 1) { return (UUID) filteredByLastName.getFirst().get("id"); } @@ -292,7 +321,7 @@ private UUID findNewStudentIdFromExternalStudentId(final String externalStudentI .filter(userInfo -> userInfo.get("firstName").equals(firstName)) .toList(); if (filteredByFirstName.isEmpty()) { - throw new IllegalArgumentException("No matching student found!"); // TODO create better exception + throw new ManualMappingRequiredException(externalStudentInfo); } else if (filteredByFirstName.size() == 1) { return (UUID) filteredByFirstName.getFirst().get("id"); } @@ -303,7 +332,7 @@ private UUID findNewStudentIdFromExternalStudentId(final String externalStudentI throw new ManualMappingRequiredException(externalStudentInfo); } - private JSONObject getExternalStudentInfo(final String externalStudentId) { + private JSONObject getExternalStudentInfo(final String externalStudentId) throws ExternalPlatformConnectionException { String body; CompletableFuture response; try (HttpClient client = HttpClient.newBuilder().build()) { @@ -315,45 +344,44 @@ private JSONObject getExternalStudentInfo(final String externalStudentId) { body = response.join(); } - // TODO create better exception - if (body == null) throw new IllegalArgumentException("Querying external student info for externalStudentId %s went wrong.".formatted(externalStudentId)); + if (body == null) throw new ExternalPlatformConnectionException("Querying external student info for externalStudentId %s went wrong.".formatted(externalStudentId)); return new JSONObject(body); } // TODO this whole thing should be in userService rather than here - private List> getMeitrexStudentInfoList() { + private List> getMeitrexStudentInfoList() throws UserServiceConnectionException { String query = "findAllUserInfos"; // TODO doesn't exist currently String queryName = "findAllUserInfos"; - List> meitrexStudentInfo = userServiceClient.document(query) + List> meitrexStudentInfoList = userServiceClient.document(query) .execute() .handle((ClientGraphQlResponse result, SynchronousSink>> sink) -> handleGraphQlResponse(result, sink, queryName)) .retry(3) .block(); - if (meitrexStudentInfo == null) { - throw new IllegalStateException("Error fetching userInfo from UserService"); // TODO create better exception + if (meitrexStudentInfoList == null) { + throw new UserServiceConnectionException("Error fetching userInfo from UserService"); } - return meitrexStudentInfo; + return meitrexStudentInfoList; } private void handleGraphQlResponse(final ClientGraphQlResponse result, final SynchronousSink>> sink, final String queryName) { if (!result.isValid()) { - sink.error(new Exception(result.getErrors().toString())); // TODO create better exception + sink.error(new UserServiceConnectionException(result.getErrors().toString())); return; } List> retrievedUserInfos = result.field(queryName).getValue(); if (retrievedUserInfos == null) { - sink.error(new Exception("Error fetching userInfo from UserService: Missing field in response.")); // TODO create better exception + sink.error(new UserServiceConnectionException("Error fetching userInfo from UserService: Missing field in response.")); return; } if (retrievedUserInfos.isEmpty()) { - sink.error(new Exception("Error fetching userInfo from UserService: Field in response is empty.")); // TODO create better exception + sink.error(new UserServiceConnectionException("Error fetching userInfo from UserService: Field in response is empty.")); } sink.next(retrievedUserInfos); @@ -378,18 +406,28 @@ public List saveStudentMappings(final UUID courseId, final List> meitrexStudentInfoList; + try { + meitrexStudentInfoList = getMeitrexStudentInfoList(); + } catch (UserServiceConnectionException e){ + throw new RuntimeException(e); // wrapping exception + // return null; TODO return null or throw wrapped exception? + } + List unfinishedGradingEntityList = unfinishedGradingRepository.findAll(); for (final UnfinishedGradingEntity unfinishedGradingEntity : unfinishedGradingEntityList) { JSONObject jsonObject = new JSONObject(unfinishedGradingEntity.getGradingJson()); AssignmentEntity assignmentEntity = assignmentService.requireAssignmentExists(unfinishedGradingEntity.getId().getAssignmentId()); try { - GradingEntity gradingEntity = parseIntoGradingEntity(jsonObject, assignmentEntity); + GradingEntity gradingEntity = parseIntoGradingEntity(jsonObject, assignmentEntity, meitrexStudentInfoList); gradingRepository.save(gradingEntity); logGradingImported(gradingEntity); unfinishedGradingRepository.deleteById(unfinishedGradingEntity.getId()); - } catch (ManualMappingRequiredException e) { - // should not happen, because all mappings should be done at this instance + } catch (ManualMappingRequiredException | ExternalPlatformConnectionException e) { + // if something goes wrong, unfinished gradings will be added to repo again + unfinishedGradingEntity.incrementNumberOfTries(); + unfinishedGradingRepository.save(unfinishedGradingEntity); } } diff --git a/src/main/resources/graphql/service/mutation.graphqls b/src/main/resources/graphql/service/mutation.graphqls index 4a238bf..b4d9120 100644 --- a/src/main/resources/graphql/service/mutation.graphqls +++ b/src/main/resources/graphql/service/mutation.graphqls @@ -24,8 +24,9 @@ type Mutation { """ Saves mappings of meitrex users to external students. - Response to ManualMappingInstances. + Used to deal with ManualMappingInstances. Returns list of all deleted ManualMappingInstance ids. + Returns null if connection to UserService failed. 🔒 The user must be an admin in the course to perform this action. """ saveStudentMappings(courseId: UUID!, studentMappingInputs: [StudentMappingInput!]!): [String]!