|
| 1 | +package ca.bc.gov.educ.api.pen.replication.orchestrator; |
| 2 | + |
| 3 | +import ca.bc.gov.educ.api.pen.replication.constants.SagaEnum; |
| 4 | +import ca.bc.gov.educ.api.pen.replication.constants.SagaTopicsEnum; |
| 5 | +import ca.bc.gov.educ.api.pen.replication.exception.PenReplicationAPIRuntimeException; |
| 6 | +import ca.bc.gov.educ.api.pen.replication.messaging.MessagePublisher; |
| 7 | +import ca.bc.gov.educ.api.pen.replication.model.Saga; |
| 8 | +import ca.bc.gov.educ.api.pen.replication.model.SagaEvent; |
| 9 | +import ca.bc.gov.educ.api.pen.replication.model.StudXcrseId; |
| 10 | +import ca.bc.gov.educ.api.pen.replication.model.TraxStudentCourseEntity; |
| 11 | +import ca.bc.gov.educ.api.pen.replication.orchestrator.base.BaseOrchestrator; |
| 12 | +import ca.bc.gov.educ.api.pen.replication.rest.RestUtils; |
| 13 | +import ca.bc.gov.educ.api.pen.replication.service.SagaService; |
| 14 | +import ca.bc.gov.educ.api.pen.replication.service.TraxStudentCourseService; |
| 15 | +import ca.bc.gov.educ.api.pen.replication.service.TraxStudentService; |
| 16 | +import ca.bc.gov.educ.api.pen.replication.struct.Event; |
| 17 | +import ca.bc.gov.educ.api.pen.replication.struct.StudentCourse; |
| 18 | +import ca.bc.gov.educ.api.pen.replication.struct.saga.StudentCourseUpdateSagaData; |
| 19 | +import ca.bc.gov.educ.api.pen.replication.util.JsonUtil; |
| 20 | +import com.fasterxml.jackson.core.JsonProcessingException; |
| 21 | +import jakarta.persistence.EntityManagerFactory; |
| 22 | +import lombok.extern.slf4j.Slf4j; |
| 23 | +import lombok.val; |
| 24 | +import org.apache.commons.lang3.StringUtils; |
| 25 | +import org.springframework.stereotype.Component; |
| 26 | + |
| 27 | +import java.util.ArrayList; |
| 28 | +import java.util.List; |
| 29 | +import java.util.Objects; |
| 30 | + |
| 31 | +import static ca.bc.gov.educ.api.pen.replication.constants.EventOutcome.*; |
| 32 | +import static ca.bc.gov.educ.api.pen.replication.constants.EventType.*; |
| 33 | + |
| 34 | +@Component |
| 35 | +@Slf4j |
| 36 | +public class StudentCourseUpdateOrchestrator extends BaseOrchestrator<StudentCourseUpdateSagaData> { |
| 37 | + |
| 38 | + private final TraxStudentCourseService traxStudentCourseService; |
| 39 | + private final TraxStudentService traxStudentService; |
| 40 | + private final RestUtils restUtils; |
| 41 | + |
| 42 | + public StudentCourseUpdateOrchestrator(final SagaService sagaService, |
| 43 | + final MessagePublisher messagePublisher, |
| 44 | + final EntityManagerFactory entityManagerFactory, |
| 45 | + final TraxStudentCourseService traxStudentCourseService, |
| 46 | + final TraxStudentService traxStudentService, |
| 47 | + final RestUtils restUtils) { |
| 48 | + super(entityManagerFactory, sagaService, messagePublisher, StudentCourseUpdateSagaData.class, |
| 49 | + SagaEnum.PEN_REPLICATION_STUDENT_COURSE_UPDATE_SAGA, |
| 50 | + SagaTopicsEnum.PEN_REPLICATION_STUDENT_COURSE_UPDATE_SAGA_TOPIC); |
| 51 | + this.traxStudentCourseService = traxStudentCourseService; |
| 52 | + this.traxStudentService = traxStudentService; |
| 53 | + this.restUtils = restUtils; |
| 54 | + } |
| 55 | + |
| 56 | + @Override |
| 57 | + public void populateStepsToExecuteMap() { |
| 58 | + this.stepBuilder() |
| 59 | + .begin(PREPARE_STUDENT_COURSE_UPDATE, this::prepareStudentCourseUpdate) |
| 60 | + .step(PREPARE_STUDENT_COURSE_UPDATE, STUDENT_COURSE_UPDATE_PREPARED, DELETE_STUDENT_COURSES, this::deleteStudentCourses) |
| 61 | + .step(PREPARE_STUDENT_COURSE_UPDATE, STUDENT_NOT_FOUND, MARK_SAGA_COMPLETE, this::markSagaComplete) |
| 62 | + .step(DELETE_STUDENT_COURSES, STUDENT_COURSES_DELETED, SAVE_STUDENT_COURSES, this::saveStudentCourses) |
| 63 | + .end(SAVE_STUDENT_COURSES, STUDENT_COURSES_SAVED); |
| 64 | + } |
| 65 | + |
| 66 | + private void prepareStudentCourseUpdate(final Event event, final Saga saga, final StudentCourseUpdateSagaData sagaData) { |
| 67 | + saga.setSagaState(PREPARE_STUDENT_COURSE_UPDATE.toString()); |
| 68 | + final SagaEvent eventStates = this.createEventState(saga, event.getEventType(), event.getEventOutcome(), event.getEventPayload()); |
| 69 | + this.getSagaService().updateAttachedSagaWithEvents(saga, eventStates); |
| 70 | + |
| 71 | + try { |
| 72 | + var studentPEN = restUtils.getStudentPen(sagaData.getStudentID()); |
| 73 | + val existingTraxStudentRecord = this.traxStudentService.findTraxStudentByPen(StringUtils.rightPad(studentPEN, 10)); |
| 74 | + |
| 75 | + if (existingTraxStudentRecord.isEmpty()) { |
| 76 | + log.error("Student course event not processed for saga {} :: student does not yet exist in TRAX STUDENT_MASTER", saga.getSagaId()); |
| 77 | + throw new PenReplicationAPIRuntimeException("Student not found in TRAX with ID: " + studentPEN + "This should never happen."); |
| 78 | + } |
| 79 | + var existingCourses = this.traxStudentCourseService.findTraxStudentCoursesByPen(studentPEN); |
| 80 | + existingCourses = existingCourses.stream().filter(Objects::nonNull).toList(); |
| 81 | + val newCourses = getStudentCourseEntityList(studentPEN, sagaData.getStudentCourses(), existingCourses); |
| 82 | + |
| 83 | + sagaData.setStudentPEN(studentPEN); |
| 84 | + sagaData.setNewCourses(newCourses); |
| 85 | + |
| 86 | + try { |
| 87 | + val updatedPayload = JsonUtil.getJsonStringFromObject(sagaData); |
| 88 | + saga.setPayload(updatedPayload); |
| 89 | + this.getSagaService().updateAttachedEntityDuringSagaProcess(saga); |
| 90 | + } catch (JsonProcessingException e) { |
| 91 | + log.error("Error updating saga payload for saga: {}", saga.getSagaId(), e); |
| 92 | + throw new RuntimeException("Error updating saga payload", e); |
| 93 | + } |
| 94 | + |
| 95 | + log.debug("Prepared student course update for PEN: {}, existing courses to delete: {}, new courses to save: {}", |
| 96 | + studentPEN, |
| 97 | + existingCourses.size(), |
| 98 | + newCourses.size()); |
| 99 | + |
| 100 | + val nextEvent = Event.builder().sagaId(saga.getSagaId()) |
| 101 | + .eventType(PREPARE_STUDENT_COURSE_UPDATE) |
| 102 | + .eventOutcome(STUDENT_COURSE_UPDATE_PREPARED) |
| 103 | + .eventPayload("PREPARED") |
| 104 | + .build(); |
| 105 | + this.postMessageToTopic(this.getTopicToSubscribe().getCode(), nextEvent); |
| 106 | + log.debug("responded via NATS to {} for {} Event. :: {}", this.getTopicToSubscribe(), PREPARE_STUDENT_COURSE_UPDATE, saga.getSagaId()); |
| 107 | + } catch (Exception e) { |
| 108 | + log.error("Error preparing student course update for saga: {}", saga.getSagaId(), e); |
| 109 | + throw e; |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + private void deleteStudentCourses(final Event event, final Saga saga, final StudentCourseUpdateSagaData sagaData) { |
| 114 | + saga.setSagaState(DELETE_STUDENT_COURSES.toString()); |
| 115 | + final SagaEvent eventStates = this.createEventState(saga, event.getEventType(), event.getEventOutcome(), event.getEventPayload()); |
| 116 | + this.getSagaService().updateAttachedSagaWithEvents(saga, eventStates); |
| 117 | + |
| 118 | + try { |
| 119 | + this.traxStudentCourseService.deleteTraxStudentCourses(sagaData.getStudentPEN()); |
| 120 | + |
| 121 | + val nextEvent = Event.builder().sagaId(saga.getSagaId()) |
| 122 | + .eventType(DELETE_STUDENT_COURSES) |
| 123 | + .eventOutcome(STUDENT_COURSES_DELETED) |
| 124 | + .eventPayload("DELETED") |
| 125 | + .build(); |
| 126 | + this.postMessageToTopic(this.getTopicToSubscribe().getCode(), nextEvent); |
| 127 | + log.info("responded via NATS to {} for {} Event. :: {}", this.getTopicToSubscribe(), DELETE_STUDENT_COURSES, saga.getSagaId()); |
| 128 | + } catch (Exception e) { |
| 129 | + log.error("Error deleting student courses for saga: {}", saga.getSagaId(), e); |
| 130 | + throw e; |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + private void saveStudentCourses(final Event event, final Saga saga, final StudentCourseUpdateSagaData sagaData) { |
| 135 | + saga.setSagaState(SAVE_STUDENT_COURSES.toString()); |
| 136 | + final SagaEvent eventStates = this.createEventState(saga, event.getEventType(), event.getEventOutcome(), event.getEventPayload()); |
| 137 | + this.getSagaService().updateAttachedSagaWithEvents(saga, eventStates); |
| 138 | + |
| 139 | + try { |
| 140 | + this.traxStudentCourseService.saveTraxStudentCourses(sagaData.getNewCourses()); |
| 141 | + |
| 142 | + val nextEvent = Event.builder().sagaId(saga.getSagaId()) |
| 143 | + .eventType(SAVE_STUDENT_COURSES) |
| 144 | + .eventOutcome(STUDENT_COURSES_SAVED) |
| 145 | + .eventPayload("SAVED") |
| 146 | + .build(); |
| 147 | + this.postMessageToTopic(this.getTopicToSubscribe().getCode(), nextEvent); |
| 148 | + log.info("responded via NATS to {} for {} Event. :: {}", this.getTopicToSubscribe(), SAVE_STUDENT_COURSES, saga.getSagaId()); |
| 149 | + } catch (Exception e) { |
| 150 | + log.error("Error saving student courses for saga: {}", saga.getSagaId(), e); |
| 151 | + throw e; |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + private List<TraxStudentCourseEntity> getStudentCourseEntityList(String studentPEN, |
| 156 | + List<StudentCourse> studentCourse, |
| 157 | + List<TraxStudentCourseEntity> existingTraxStudentCourses) { |
| 158 | + var entityList = new ArrayList<TraxStudentCourseEntity>(); |
| 159 | + studentCourse.forEach(student -> { |
| 160 | + TraxStudentCourseEntity traxStudentCourseEntity = new TraxStudentCourseEntity(); |
| 161 | + traxStudentCourseEntity.setStudXcrseId(new StudXcrseId()); |
| 162 | + setCourseCodeAndLevel(traxStudentCourseEntity, student.getCourseID()); |
| 163 | + traxStudentCourseEntity.getStudXcrseId().setStudNo(StringUtils.trimToNull(studentPEN)); |
| 164 | + traxStudentCourseEntity.getStudXcrseId().setCourseSession(StringUtils.trimToNull(student.getCourseSession())); |
| 165 | + traxStudentCourseEntity.setFinalLetterGrade(StringUtils.trimToNull(student.getFinalLetterGrade())); |
| 166 | + traxStudentCourseEntity.setFinalPercentage(student.getFinalPercent() != null ? student.getFinalPercent().toString() : null); |
| 167 | + traxStudentCourseEntity.setNumberOfCredits(student.getCredits() != null ? student.getCredits().toString() : null); |
| 168 | + setStudyTypeAndUsedForGradFields(traxStudentCourseEntity, existingTraxStudentCourses); |
| 169 | + entityList.add(traxStudentCourseEntity); |
| 170 | + }); |
| 171 | + return entityList; |
| 172 | + } |
| 173 | + |
| 174 | + private void setStudyTypeAndUsedForGradFields(TraxStudentCourseEntity traxStudentCourseEntity, List<TraxStudentCourseEntity> existingTraxStudentCourses){ |
| 175 | + for(TraxStudentCourseEntity course: existingTraxStudentCourses){ |
| 176 | + if(course != null) { |
| 177 | + String courseCourseCode = StringUtils.trimToNull(course.getStudXcrseId().getCourseCode()); |
| 178 | + String courseCourseLevel = StringUtils.trimToNull(course.getStudXcrseId().getCourseLevel()); |
| 179 | + String courseCourseSession = StringUtils.trimToNull(course.getStudXcrseId().getCourseSession()); |
| 180 | + |
| 181 | + String traxCourseCode = StringUtils.trimToNull(traxStudentCourseEntity.getStudXcrseId().getCourseCode()); |
| 182 | + String traxCourseLevel = StringUtils.trimToNull(traxStudentCourseEntity.getStudXcrseId().getCourseLevel()); |
| 183 | + String traxCourseSession = StringUtils.trimToNull(traxStudentCourseEntity.getStudXcrseId().getCourseSession()); |
| 184 | + |
| 185 | + if (Objects.equals(courseCourseCode, traxCourseCode) && |
| 186 | + Objects.equals(courseCourseLevel, traxCourseLevel) && |
| 187 | + Objects.equals(courseCourseSession, traxCourseSession)) { |
| 188 | + traxStudentCourseEntity.setStudyType(course.getStudyType()); |
| 189 | + traxStudentCourseEntity.setUsedForGrad(course.getUsedForGrad()); |
| 190 | + break; |
| 191 | + } |
| 192 | + } |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + private void setCourseCodeAndLevel(TraxStudentCourseEntity traxStudentCourseEntity, String courseID){ |
| 197 | + var optionalCourse = restUtils.getCoreg39CourseByID(courseID); |
| 198 | + if (optionalCourse.isPresent()) { |
| 199 | + var course = optionalCourse.get(); |
| 200 | + if(course.getExternalCode().length() > 5) { |
| 201 | + traxStudentCourseEntity.getStudXcrseId().setCourseCode(StringUtils.trimToNull(course.getExternalCode().substring(0, 4))); |
| 202 | + traxStudentCourseEntity.getStudXcrseId().setCourseLevel(StringUtils.trimToNull(course.getExternalCode().substring(5))); |
| 203 | + }else{ |
| 204 | + traxStudentCourseEntity.getStudXcrseId().setCourseCode(StringUtils.trimToNull(course.getExternalCode())); |
| 205 | + traxStudentCourseEntity.getStudXcrseId().setCourseLevel(" ");//trax needs the whitespace |
| 206 | + } |
| 207 | + }else{ |
| 208 | + log.info("No course was found for ID {} :: this should not have happened", courseID); |
| 209 | + throw new PenReplicationAPIRuntimeException("No course was found for ID " + courseID + " :: this should not have happened"); |
| 210 | + } |
| 211 | + } |
| 212 | +} |
| 213 | + |
0 commit comments