/** * Copyright © 2002 Instituto Superior Técnico * * This file is part of FenixEdu Academic. * * FenixEdu Academic is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * FenixEdu Academic is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with FenixEdu Academic. If not, see <http://www.gnu.org/licenses/>. */ /* * Created on 29/Nov/2003 * */ package org.fenixedu.academic.service.services.manager; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; import org.fenixedu.academic.domain.Attends; import org.fenixedu.academic.domain.CourseLoad; import org.fenixedu.academic.domain.Evaluation; import org.fenixedu.academic.domain.ExecutionCourse; import org.fenixedu.academic.domain.ExecutionCourseLog; import org.fenixedu.academic.domain.ExportGrouping; import org.fenixedu.academic.domain.FinalEvaluation; import org.fenixedu.academic.domain.LessonInstance; import org.fenixedu.academic.domain.Mark; import org.fenixedu.academic.domain.Person; import org.fenixedu.academic.domain.Professorship; import org.fenixedu.academic.domain.Shift; import org.fenixedu.academic.domain.StudentGroup; import org.fenixedu.academic.domain.Summary; import org.fenixedu.academic.domain.accessControl.PersistentSpecialCriteriaOverExecutionCourseGroup; import org.fenixedu.academic.domain.accessControl.PersistentStudentGroup; import org.fenixedu.academic.domain.accessControl.PersistentTeacherGroup; import org.fenixedu.academic.domain.messaging.ConversationMessage; import org.fenixedu.academic.domain.messaging.ConversationThread; import org.fenixedu.academic.domain.messaging.ExecutionCourseForum; import org.fenixedu.academic.domain.messaging.ForumSubscription; import org.fenixedu.academic.domain.student.Registration; import org.fenixedu.academic.domain.util.email.ExecutionCourseSender; import org.fenixedu.academic.service.ServiceMonitoring; import org.fenixedu.academic.service.services.exceptions.FenixServiceException; import org.fenixedu.academic.service.services.exceptions.InvalidArgumentsServiceException; import org.fenixedu.academic.util.MultiLanguageString; import pt.ist.fenixframework.Atomic; import pt.ist.fenixframework.Atomic.TxMode; /** * @author <a href="mailto:joao.mota@ist.utl.pt"> João Mota </a> 29/Nov/2003 * */ public class MergeExecutionCourses { public static class SourceAndDestinationAreTheSameException extends FenixServiceException { private static final long serialVersionUID = 3761968254943244338L; } public static class DuplicateShiftNameException extends FenixServiceException { private static final long serialVersionUID = 3761968254943244338L; } public static class MergeNotPossibleException extends FenixServiceException { private static final long serialVersionUID = 3761968254943244338L; public MergeNotPossibleException(Set<String> blockers) { super(blockers.stream().collect(Collectors.joining("; "))); } } @FunctionalInterface public static interface SubDomainMergeHandler { default public Set<String> mergeBlockers(ExecutionCourse executionCourseFrom, ExecutionCourse executionCourseTo) { return Collections.<String> emptySet(); } public void merge(ExecutionCourse executionCourseFrom, ExecutionCourse executionCourseTo) throws FenixServiceException; } private static final ConcurrentLinkedQueue<SubDomainMergeHandler> handlers = new ConcurrentLinkedQueue<>(); public static void registerMergeHandler(SubDomainMergeHandler handler) { handlers.add(handler); } static { registerMergeHandler(new SubDomainMergeHandler() { @Override public Set<String> mergeBlockers(ExecutionCourse executionCourseFrom, ExecutionCourse executionCourseTo) { if (!isMergeAllowed(executionCourseFrom, executionCourseTo)) { return Collections.<String> singleton("Cannot merge courses of different periods"); } return Collections.<String> emptySet(); }; @Override public void merge(ExecutionCourse executionCourseFrom, ExecutionCourse executionCourseTo) throws FenixServiceException { copyShifts(executionCourseFrom, executionCourseTo); } }); registerMergeHandler(MergeExecutionCourses::copyLessonsInstances); registerMergeHandler(MergeExecutionCourses::copyProfessorships); registerMergeHandler(MergeExecutionCourses::copyAttends); registerMergeHandler(MergeExecutionCourses::copyBibliographicReference); registerMergeHandler(MergeExecutionCourses::dropEvaluationMethods); registerMergeHandler(MergeExecutionCourses::copySummaries); registerMergeHandler(MergeExecutionCourses::copyGroupPropertiesExecutionCourse); registerMergeHandler(MergeExecutionCourses::removeEvaluations); registerMergeHandler(MergeExecutionCourses::copyForuns); registerMergeHandler(MergeExecutionCourses::copyExecutionCourseLogs); registerMergeHandler(MergeExecutionCourses::copyPersistentGroups); registerMergeHandler(MergeExecutionCourses::copySenderMessages); registerMergeHandler((from, to) -> to.getAssociatedCurricularCoursesSet() .addAll(from.getAssociatedCurricularCoursesSet())); registerMergeHandler((from, to) -> to.copyLessonPlanningsFrom(from)); } @Atomic(mode = TxMode.WRITE) public static void merge(ExecutionCourse executionCourseTo, ExecutionCourse executionCourseFrom) throws FenixServiceException { if (executionCourseFrom == null) { throw new InvalidArgumentsServiceException(); } if (executionCourseTo == null) { throw new InvalidArgumentsServiceException(); } ServiceMonitoring.logService(MergeExecutionCourses.class, executionCourseTo.getExternalId(), executionCourseFrom.getExternalId()); if (executionCourseTo.equals(executionCourseFrom)) { throw new SourceAndDestinationAreTheSameException(); } if (haveShiftsWithSameName(executionCourseFrom, executionCourseTo)) { throw new DuplicateShiftNameException(); } for (SubDomainMergeHandler handler : handlers) { Set<String> blockers = handler.mergeBlockers(executionCourseFrom, executionCourseTo); if (blockers.isEmpty()) { handler.merge(executionCourseFrom, executionCourseTo); } else { throw new MergeNotPossibleException(blockers); } } executionCourseFrom.delete(); } private static boolean haveShiftsWithSameName(final ExecutionCourse executionCourseFrom, final ExecutionCourse executionCourseTo) { final Set<String> shiftNames = new HashSet<String>(); for (final Shift shift : executionCourseFrom.getAssociatedShifts()) { shiftNames.add(shift.getNome()); } for (final Shift shift : executionCourseTo.getAssociatedShifts()) { if (shiftNames.contains(shift.getNome())) { return true; } } return false; } private static boolean isMergeAllowed(final ExecutionCourse executionCourseFrom, final ExecutionCourse executionCourseTo) { return executionCourseTo != null && executionCourseFrom != null && executionCourseFrom.getExecutionPeriod().equals(executionCourseTo.getExecutionPeriod()) && executionCourseFrom != executionCourseTo; } private static void copySummaries(final ExecutionCourse executionCourseFrom, final ExecutionCourse executionCourseTo) { final List<Summary> associatedSummaries = new ArrayList<Summary>(); associatedSummaries.addAll(executionCourseFrom.getAssociatedSummariesSet()); for (final Summary summary : associatedSummaries) { summary.setExecutionCourse(executionCourseTo); } } private static void copyGroupPropertiesExecutionCourse(final ExecutionCourse executionCourseFrom, final ExecutionCourse executionCourseTo) { final List<ExportGrouping> associatedGroupPropertiesExecutionCourse = new ArrayList<ExportGrouping>(); associatedGroupPropertiesExecutionCourse.addAll(executionCourseFrom.getExportGroupingsSet()); for (final ExportGrouping groupPropertiesExecutionCourse : associatedGroupPropertiesExecutionCourse) { if (executionCourseTo.hasGrouping(groupPropertiesExecutionCourse.getGrouping())) { groupPropertiesExecutionCourse.delete(); } else { groupPropertiesExecutionCourse.setExecutionCourse(executionCourseTo); } } } private static void removeEvaluations(final ExecutionCourse executionCourseFrom, final ExecutionCourse executionCourseTo) throws FenixServiceException { while (!executionCourseFrom.getAssociatedEvaluationsSet().isEmpty()) { final Evaluation evaluation = executionCourseFrom.getAssociatedEvaluationsSet().iterator().next(); if (evaluation instanceof FinalEvaluation) { final FinalEvaluation finalEvaluationFrom = (FinalEvaluation) evaluation; if (!finalEvaluationFrom.getMarksSet().isEmpty()) { throw new FenixServiceException("error.merge.execution.course.final.evaluation.exists"); } else { finalEvaluationFrom.delete(); } } else { executionCourseTo.getAssociatedEvaluationsSet().add(evaluation); executionCourseFrom.getAssociatedEvaluationsSet().remove(evaluation); } } } private static void copyBibliographicReference(final ExecutionCourse executionCourseFrom, final ExecutionCourse executionCourseTo) { for (; !executionCourseFrom.getAssociatedBibliographicReferencesSet().isEmpty(); executionCourseTo .getAssociatedBibliographicReferencesSet().add( executionCourseFrom.getAssociatedBibliographicReferencesSet().iterator().next())) { ; } } private static void copyShifts(final ExecutionCourse executionCourseFrom, final ExecutionCourse executionCourseTo) { final List<Shift> associatedShifts = new ArrayList<Shift>(executionCourseFrom.getAssociatedShifts()); for (final Shift shift : associatedShifts) { List<CourseLoad> courseLoadsFrom = new ArrayList<CourseLoad>(shift.getCourseLoadsSet()); for (Iterator<CourseLoad> iter = courseLoadsFrom.iterator(); iter.hasNext();) { CourseLoad courseLoadFrom = iter.next(); CourseLoad courseLoadTo = executionCourseTo.getCourseLoadByShiftType(courseLoadFrom.getType()); if (courseLoadTo == null) { courseLoadTo = new CourseLoad(executionCourseTo, courseLoadFrom.getType(), courseLoadFrom.getUnitQuantity(), courseLoadFrom.getTotalQuantity()); } iter.remove(); shift.removeCourseLoads(courseLoadFrom); shift.addCourseLoads(courseLoadTo); } } } private static void copyLessonsInstances(ExecutionCourse executionCourseFrom, ExecutionCourse executionCourseTo) { final List<LessonInstance> associatedLessons = new ArrayList<LessonInstance>(executionCourseFrom.getAssociatedLessonInstances()); for (final LessonInstance lessonInstance : associatedLessons) { CourseLoad courseLoadFrom = lessonInstance.getCourseLoad(); CourseLoad courseLoadTo = executionCourseTo.getCourseLoadByShiftType(courseLoadFrom.getType()); if (courseLoadTo == null) { courseLoadTo = new CourseLoad(executionCourseTo, courseLoadFrom.getType(), courseLoadFrom.getUnitQuantity(), courseLoadFrom.getTotalQuantity()); } lessonInstance.setCourseLoad(courseLoadTo); } } private static void copyAttends(final ExecutionCourse executionCourseFrom, final ExecutionCourse executionCourseTo) throws FenixServiceException { for (Attends attends : executionCourseFrom.getAttendsSet()) { final Attends otherAttends = executionCourseTo.getAttendsByStudent(attends.getRegistration()); if (otherAttends == null) { attends.setDisciplinaExecucao(executionCourseTo); } else { if (attends.getEnrolment() != null && otherAttends.getEnrolment() == null) { otherAttends.setEnrolment(attends.getEnrolment()); } else if (otherAttends.getEnrolment() != null && attends.getEnrolment() == null) { // do nothing. } else if (otherAttends.getEnrolment() != null && attends.getEnrolment() != null) { throw new FenixServiceException("Unable to merge execution courses. Registration " + attends.getRegistration().getNumber() + " has an enrolment in both."); } for (Mark mark : attends.getAssociatedMarksSet()) { otherAttends.addAssociatedMarks(mark); } for (StudentGroup group : attends.getAllStudentGroups()) { otherAttends.addStudentGroups(group); } attends.delete(); } } final Iterator<Attends> associatedAttendsFromDestination = executionCourseTo.getAttendsSet().iterator(); final Map<String, Attends> alreadyAttendingDestination = new HashMap<String, Attends>(); while (associatedAttendsFromDestination.hasNext()) { Attends attend = associatedAttendsFromDestination.next(); Registration registration = attend.getRegistration(); if (registration == null) { // !!! Yup it's true this actually happens!!! attend.delete(); } else { Integer number = registration.getNumber(); alreadyAttendingDestination.put(number.toString(), attend); } } final List<Attends> associatedAttendsFromSource = new ArrayList<Attends>(); associatedAttendsFromSource.addAll(executionCourseFrom.getAttendsSet()); for (final Attends attend : associatedAttendsFromSource) { if (!alreadyAttendingDestination.containsKey(attend.getRegistration().getNumber().toString())) { attend.setDisciplinaExecucao(executionCourseTo); } } } private static void copyProfessorships(final ExecutionCourse executionCourseFrom, final ExecutionCourse executionCourseTo) { for (Professorship professorship : executionCourseFrom.getProfessorshipsSet()) { Professorship otherProfessorship = findProfessorShip(executionCourseTo, professorship.getPerson()); if (otherProfessorship == null) { otherProfessorship = Professorship.create(professorship.getResponsibleFor(), executionCourseTo, professorship.getPerson()); } for (; !professorship.getAssociatedSummariesSet().isEmpty(); otherProfessorship.addAssociatedSummaries(professorship .getAssociatedSummariesSet().iterator().next())) { ; } for (; !professorship.getAssociatedShiftProfessorshipSet().isEmpty(); otherProfessorship .addAssociatedShiftProfessorship(professorship.getAssociatedShiftProfessorshipSet().iterator().next())) { ; } } } private static Professorship findProfessorShip(final ExecutionCourse executionCourseTo, final Person person) { for (final Professorship professorship : executionCourseTo.getProfessorshipsSet()) { if (professorship.getPerson() == person) { return professorship; } } return null; } private static void copyForuns(final ExecutionCourse executionCourseFrom, final ExecutionCourse executionCourseTo) throws FenixServiceException { while (!executionCourseFrom.getForuns().isEmpty()) { ExecutionCourseForum sourceForum = executionCourseFrom.getForuns().iterator().next(); MultiLanguageString forumName = sourceForum.getName(); ExecutionCourseForum targetForum = executionCourseTo.getForumByName(forumName); if (targetForum == null) { sourceForum.setExecutionCourse(executionCourseTo); } else { copyForumSubscriptions(sourceForum, targetForum); copyThreads(sourceForum, targetForum); executionCourseFrom.removeForum(sourceForum); sourceForum.delete(); } } } private static void copyForumSubscriptions(ExecutionCourseForum sourceForum, ExecutionCourseForum targetForum) { while (!sourceForum.getForumSubscriptionsSet().isEmpty()) { ForumSubscription sourceForumSubscription = sourceForum.getForumSubscriptionsSet().iterator().next(); Person sourceForumSubscriber = sourceForumSubscription.getPerson(); ForumSubscription targetForumSubscription = targetForum.getPersonSubscription(sourceForumSubscriber); if (targetForumSubscription == null) { sourceForumSubscription.setForum(targetForum); } else { if (sourceForumSubscription.getReceivePostsByEmail() == true) { targetForumSubscription.setReceivePostsByEmail(true); } if (sourceForumSubscription.getFavorite() == true) { targetForumSubscription.setFavorite(true); } sourceForum.removeForumSubscriptions(sourceForumSubscription); sourceForumSubscription.delete(); } } } private static void copyThreads(ExecutionCourseForum sourceForum, ExecutionCourseForum targetForum) { while (!sourceForum.getConversationThreadSet().isEmpty()) { ConversationThread sourceConversationThread = sourceForum.getConversationThreadSet().iterator().next(); if (!targetForum.hasConversationThreadWithSubject(sourceConversationThread.getTitle())) { sourceConversationThread.setForum(targetForum); } else { ConversationThread targetConversionThread = targetForum.getConversationThreadBySubject(sourceConversationThread.getTitle()); for (ConversationMessage message : sourceConversationThread.getMessageSet()) { message.setConversationThread(targetConversionThread); } sourceForum.removeConversationThread(sourceConversationThread); sourceConversationThread.delete(); } } } private static void copyExecutionCourseLogs(ExecutionCourse executionCourseFrom, ExecutionCourse executionCourseTo) { for (ExecutionCourseLog executionCourseLog : executionCourseFrom.getExecutionCourseLogsSet()) { executionCourseLog.setExecutionCourse(executionCourseTo); } } private static void copyPersistentGroups(ExecutionCourse executionCourseFrom, ExecutionCourse executionCourseTo) { for (PersistentStudentGroup group : executionCourseFrom.getStudentGroupSet()) { group.setExecutionCourse(executionCourseTo); } for (PersistentSpecialCriteriaOverExecutionCourseGroup group : executionCourseFrom .getSpecialCriteriaOverExecutionCourseGroupSet()) { group.setExecutionCourse(executionCourseTo); } for (PersistentTeacherGroup group : executionCourseFrom.getTeacherGroupSet()) { group.setExecutionCourse(executionCourseTo); } } private static void copySenderMessages(ExecutionCourse executionCourseFrom, ExecutionCourse executionCourseTo) { if (executionCourseFrom.getSender() != null) { ExecutionCourseSender courseSenderTo = ExecutionCourseSender.newInstance(executionCourseTo); courseSenderTo.getMessagesSet().addAll(executionCourseFrom.getSender().getMessagesSet()); } } private static void dropEvaluationMethods(ExecutionCourse executionCourseFrom, ExecutionCourse executionCourseTo) { if (executionCourseFrom.getEvaluationMethod() != null) { executionCourseFrom.getEvaluationMethod().delete(); } } }