Search in sources :

Example 26 with StudentParticipation

use of de.tum.in.www1.artemis.domain.participation.StudentParticipation in project ArTEMiS by ls1intum.

the class AssessmentResource method saveAssessment.

/**
 * Save or submit manual assessment depending on the submit flag.
 *
 * @param submission the submission containing the assessment
 * @param feedbackList list of feedbacks
 * @param submit if true the assessment is submitted, else only saved
 * @param resultId resultId of the result we save the feedbackList to, null of no results exists yet
 * @return result after saving/submitting modeling assessment
 */
ResponseEntity<Result> saveAssessment(Submission submission, boolean submit, List<Feedback> feedbackList, Long resultId) {
    User user = userRepository.getUserWithGroupsAndAuthorities();
    StudentParticipation studentParticipation = (StudentParticipation) submission.getParticipation();
    long exerciseId = studentParticipation.getExercise().getId();
    Exercise exercise = exerciseRepository.findByIdElseThrow(exerciseId);
    checkAuthorization(exercise, user);
    final var isAtLeastInstructor = authCheckService.isAtLeastInstructorForExercise(exercise, user);
    if (!assessmentService.isAllowedToCreateOrOverrideResult(submission.getLatestResult(), exercise, studentParticipation, user, isAtLeastInstructor)) {
        log.debug("The user {} is not allowed to override the assessment for the submission {}", user.getLogin(), submission.getId());
        throw new AccessForbiddenException("The user is not allowed to override the assessment");
    }
    Result result = assessmentService.saveManualAssessment(submission, feedbackList, resultId);
    if (submit) {
        result = assessmentService.submitManualAssessment(result.getId(), exercise, submission.getSubmissionDate());
        Optional<User> optionalStudent = ((StudentParticipation) submission.getParticipation()).getStudent();
        if (optionalStudent.isPresent()) {
            singleUserNotificationService.checkNotificationForAssessmentExerciseSubmission(exercise, optionalStudent.get(), result);
        }
    }
    var participation = result.getParticipation();
    // remove information about the student for tutors to ensure double-blind assessment
    if (!isAtLeastInstructor) {
        participation.filterSensitiveInformation();
    }
    if (submit && (participation.getExercise().getAssessmentDueDate() == null || participation.getExercise().getAssessmentDueDate().isBefore(ZonedDateTime.now()))) {
        messagingService.broadcastNewResult(result.getParticipation(), result);
    }
    return ResponseEntity.ok(result);
}
Also used : StudentParticipation(de.tum.in.www1.artemis.domain.participation.StudentParticipation) AccessForbiddenException(de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException)

Example 27 with StudentParticipation

use of de.tum.in.www1.artemis.domain.participation.StudentParticipation in project ArTEMiS by ls1intum.

the class TextPlagiarismDetectionService method checkPlagiarism.

/**
 * Download all submissions of the exercise, run JPlag, and return the result
 *
 * @param textExercise        to detect plagiarism for
 * @param similarityThreshold ignore comparisons whose similarity is below this threshold (%)
 * @param minimumScore        consider only submissions whose score is greater or equal to this value
 * @param minimumSize         consider only submissions whose size is greater or equal to this value
 * @return a zip file that can be returned to the client
 * @throws ExitException is thrown if JPlag exits unexpectedly
 */
public TextPlagiarismResult checkPlagiarism(TextExercise textExercise, float similarityThreshold, int minimumScore, int minimumSize) throws ExitException {
    long start = System.nanoTime();
    String topic = plagiarismWebsocketService.getTextExercisePlagiarismCheckTopic(textExercise.getId());
    // TODO: why do we have such a strange folder name?
    final var submissionsFolderName = "./tmp/submissions";
    final var submissionFolderFile = new File(submissionsFolderName);
    submissionFolderFile.mkdirs();
    final List<TextSubmission> textSubmissions = textSubmissionsForComparison(textExercise, minimumScore, minimumSize);
    final var submissionsSize = textSubmissions.size();
    log.info("Save text submissions for JPlag text comparison with {} submissions", submissionsSize);
    if (textSubmissions.size() < 2) {
        log.info("Insufficient amount of submissions for plagiarism detection. Return empty result.");
        TextPlagiarismResult textPlagiarismResult = new TextPlagiarismResult();
        textPlagiarismResult.setExercise(textExercise);
        textPlagiarismResult.setSimilarityDistribution(new int[0]);
        return textPlagiarismResult;
    }
    AtomicInteger processedSubmissionCount = new AtomicInteger(1);
    textSubmissions.forEach(submission -> {
        var progressMessage = "Getting submission: " + processedSubmissionCount + "/" + textSubmissions.size();
        plagiarismWebsocketService.notifyInstructorAboutPlagiarismState(topic, PlagiarismCheckState.RUNNING, List.of(progressMessage));
        submission.setResults(new ArrayList<>());
        StudentParticipation participation = (StudentParticipation) submission.getParticipation();
        participation.setExercise(null);
        participation.setSubmissions(null);
        String participantIdentifier = participation.getParticipantIdentifier();
        if (participantIdentifier == null) {
            participantIdentifier = "unknown";
        }
        try {
            textSubmissionExportService.saveSubmissionToFile(submission, participantIdentifier, submissionsFolderName);
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        processedSubmissionCount.getAndIncrement();
    });
    log.info("Saving text submissions done");
    JPlagOptions options = new JPlagOptions(submissionsFolderName, LanguageOption.TEXT);
    options.setMinimumTokenMatch(minimumSize);
    // Important: for large courses with more than 1000 students, we might get more than one million results and 10 million files in the file system due to many 0% results,
    // therefore we limit the results to at least 50% or 0.5 similarity, the passed threshold is between 0 and 100%
    options.setSimilarityThreshold(similarityThreshold);
    log.info("Start JPlag Text comparison");
    JPlag jplag = new JPlag(options);
    JPlagResult jPlagResult = jplag.run();
    log.info("JPlag Text comparison finished with {} comparisons. Will limit the number of comparisons to 500", jPlagResult.getComparisons().size());
    log.info("Delete submission folder");
    if (submissionFolderFile.exists()) {
        FileSystemUtils.deleteRecursively(submissionFolderFile);
    }
    TextPlagiarismResult textPlagiarismResult = new TextPlagiarismResult();
    textPlagiarismResult.convertJPlagResult(jPlagResult);
    textPlagiarismResult.setExercise(textExercise);
    log.info("JPlag text comparison for {} submissions done in {}", submissionsSize, TimeLogUtil.formatDurationFrom(start));
    plagiarismWebsocketService.notifyInstructorAboutPlagiarismState(topic, PlagiarismCheckState.COMPLETED, List.of());
    return textPlagiarismResult;
}
Also used : JPlagOptions(de.jplag.options.JPlagOptions) IOException(java.io.IOException) StudentParticipation(de.tum.in.www1.artemis.domain.participation.StudentParticipation) JPlag(de.jplag.JPlag) TextPlagiarismResult(de.tum.in.www1.artemis.domain.plagiarism.text.TextPlagiarismResult) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) File(java.io.File) JPlagResult(de.jplag.JPlagResult) TextSubmission(de.tum.in.www1.artemis.domain.TextSubmission)

Example 28 with StudentParticipation

use of de.tum.in.www1.artemis.domain.participation.StudentParticipation in project ArTEMiS by ls1intum.

the class QuizScheduleService method removeUnnecessaryObjectsBeforeSendingToClient.

private void removeUnnecessaryObjectsBeforeSendingToClient(StudentParticipation participation) {
    if (participation.getExercise() != null) {
        var quizExercise = (QuizExercise) participation.getExercise();
        // we do not need the course and lectures
        quizExercise.setCourse(null);
    // students should not see statistics
    // TODO: this would be useful, but leads to problems when the quiz schedule service wants to access the statistics again later on
    // quizExercise.setQuizPointStatistic(null);
    // quizExercise.getQuizQuestions().forEach(quizQuestion -> quizQuestion.setQuizQuestionStatistic(null));
    }
    // submissions are part of results, so we do not need them twice
    participation.setSubmissions(null);
    participation.setParticipant(null);
    if (participation.getResults() != null && !participation.getResults().isEmpty()) {
        QuizSubmission quizSubmission = (QuizSubmission) participation.getResults().iterator().next().getSubmission();
        if (quizSubmission != null && quizSubmission.getSubmittedAnswers() != null) {
            for (SubmittedAnswer submittedAnswer : quizSubmission.getSubmittedAnswers()) {
                if (submittedAnswer.getQuizQuestion() != null) {
                    // we do not need all information of the questions again, they are already stored in the exercise
                    var question = submittedAnswer.getQuizQuestion();
                    submittedAnswer.setQuizQuestion(question.copyQuestionId());
                }
            }
        }
    }
}
Also used : QuizSubmission(de.tum.in.www1.artemis.domain.quiz.QuizSubmission) SubmittedAnswer(de.tum.in.www1.artemis.domain.quiz.SubmittedAnswer) QuizExercise(de.tum.in.www1.artemis.domain.quiz.QuizExercise)

Example 29 with StudentParticipation

use of de.tum.in.www1.artemis.domain.participation.StudentParticipation in project ArTEMiS by ls1intum.

the class QuizScheduleService method processCachedQuizSubmissions.

/**
 * // @formatter:off
 * 1. Check cached submissions for new submissions with “isSubmitted() == true”
 *      a. Process each Submission (set submissionType to “SubmissionType.MANUAL”) and create Participation and Result and save them to Database (DB WRITE)
 *      b. Remove processed Submissions from SubmissionHashMap and write Participation with Result into ParticipationHashMap and write Result into ResultHashMap
 * 2. If Quiz has ended:
 *      a. Process all cached Submissions that belong to this quiz i. set “isSubmitted” to “true” and submissionType to “SubmissionType.TIMEOUT”
 *          ii. Create Participation and Result and save to Database (DB WRITE)
 *          iii. Remove processed Submissions from cache and write the Participations with Result and the Results into the cache
 *      b. Send out cached Participations (including QuizExercise and Result) from to each participant and remove them from the cache (WEBSOCKET SEND)
 * 3. Update Statistics with Results from ResultHashMap (DB READ and DB WRITE) and remove from cache
 * 4. Send out new Statistics to instructors (WEBSOCKET SEND)
 */
public void processCachedQuizSubmissions() {
    log.debug("Process cached quiz submissions");
    // global try-catch for error logging
    try {
        for (QuizExerciseCache cachedQuiz : quizCache.getAllQuizExerciseCaches()) {
            // this way near cache is used (values will deserialize new objects)
            Long quizExerciseId = cachedQuiz.getExerciseId();
            // Get fresh QuizExercise from DB
            QuizExercise quizExercise = quizExerciseRepository.findOne(quizExerciseId);
            // check if quiz has been deleted
            if (quizExercise == null) {
                log.debug("Remove quiz {} from resultHashMap", quizExerciseId);
                quizCache.removeAndClear(quizExerciseId);
                continue;
            }
            // Update cached exercise object (use the expensive operation upfront)
            quizExercise = quizExerciseRepository.findByIdWithQuestionsAndStatisticsElseThrow(quizExerciseId);
            Map<Long, QuizBatch> batchCache = quizExercise.getQuizBatches().stream().collect(Collectors.toUnmodifiableMap(QuizBatch::getId, batch -> batch));
            // this is required to ensure that students cannot gain extra attempts this way
            for (var batch : cachedQuiz.getBatches().entrySet()) {
                if (batchCache.get(batch.getValue()).isEnded()) {
                    cachedQuiz.getSubmissions().putIfAbsent(batch.getKey(), new QuizSubmission());
                }
            }
            // (Boolean wrapper is safe to auto-unbox here)
            boolean hasEnded = quizExercise.isQuizEnded();
            // Note that those might not be true later on due to concurrency and a distributed system,
            // do not rely on that for actions upon the whole set, such as clear()
            boolean hasNewSubmissions = !cachedQuiz.getSubmissions().isEmpty();
            boolean hasNewParticipations = !cachedQuiz.getParticipations().isEmpty();
            boolean hasNewResults = !cachedQuiz.getResults().isEmpty();
            // Skip quizzes with no cached changes
            if (!hasNewSubmissions && !hasNewParticipations && !hasNewResults) {
                // Remove quiz if it has ended
                if (hasEnded) {
                    removeCachedQuiz(cachedQuiz);
                }
                continue;
            }
            // Save cached Submissions (this will also generate results and participations and place them in the cache)
            long start = System.nanoTime();
            if (hasNewSubmissions) {
                // Create Participations and Results if the submission was submitted or if the quiz has ended and save them to Database (DB Write)
                Map<String, QuizSubmission> submissions = cachedQuiz.getSubmissions();
                Map<String, Long> batches = cachedQuiz.getBatches();
                // This call will remove the processed Submission map entries itself
                int numberOfSubmittedSubmissions = saveQuizSubmissionWithParticipationAndResultToDatabase(quizExercise, submissions, batches, batchCache);
                // .. and likely generate new participations and results
                if (numberOfSubmittedSubmissions > 0) {
                    // .. so we set the boolean variables here again if some were submitted
                    hasNewParticipations = true;
                    hasNewResults = true;
                    log.info("Saved {} submissions to database in {} in quiz {}", numberOfSubmittedSubmissions, formatDurationFrom(start), quizExercise.getTitle());
                }
            }
            // Send out Participations from ParticipationHashMap to each user if the quiz has ended
            start = System.nanoTime();
            if (hasNewParticipations && hasEnded) {
                // Send the participation with containing result and quiz back to the users via websocket and remove the participation from the ParticipationHashMap
                Collection<Entry<String, StudentParticipation>> finishedParticipations = cachedQuiz.getParticipations().entrySet();
                // TODO maybe find a better way to optimize the performance (use an executor service with e.g. X parallel threads)
                finishedParticipations.parallelStream().forEach(entry -> {
                    StudentParticipation participation = entry.getValue();
                    if (participation.getParticipant() == null || participation.getParticipantIdentifier() == null) {
                        log.error("Participation is missing student (or student is missing username): {}", participation);
                    } else {
                        sendQuizResultToUser(quizExerciseId, participation);
                        cachedQuiz.getParticipations().remove(entry.getKey());
                    }
                });
                if (!finishedParticipations.isEmpty()) {
                    log.info("Sent out {} participations in {} for quiz {}", finishedParticipations.size(), formatDurationFrom(start), quizExercise.getTitle());
                }
            }
            // Update Statistics with Results (DB Read and DB Write) and remove the results from the cache
            start = System.nanoTime();
            if (hasNewResults) {
                // Fetch a new quiz exercise here including deeper attribute paths (this is relatively expensive, so we only do that if necessary)
                try {
                    // Get a Set because QuizStatisticService needs one (currently)
                    Set<Result> newResultsForQuiz = Set.copyOf(cachedQuiz.getResults().values());
                    // Update the statistics
                    quizStatisticService.updateStatistics(newResultsForQuiz, quizExercise);
                    log.info("Updated statistics with {} new results in {} for quiz {}", newResultsForQuiz.size(), formatDurationFrom(start), quizExercise.getTitle());
                    // Remove only processed results
                    for (Result result : newResultsForQuiz) {
                        cachedQuiz.getResults().remove(result.getId());
                    }
                } catch (Exception e) {
                    log.error("Exception in StatisticService.updateStatistics(): {}", e.getMessage(), e);
                }
            }
        }
    } catch (Exception e) {
        log.error("Exception in Quiz Schedule: {}", e.getMessage(), e);
    }
}
Also used : QuizStatisticService(de.tum.in.www1.artemis.service.QuizStatisticService) java.util(java.util) SecurityUtils(de.tum.in.www1.artemis.security.SecurityUtils) de.tum.in.www1.artemis.repository(de.tum.in.www1.artemis.repository) ApplicationReadyEvent(org.springframework.boot.context.event.ApplicationReadyEvent) ZonedDateTime(java.time.ZonedDateTime) LoggerFactory(org.slf4j.LoggerFactory) QuizBatch(de.tum.in.www1.artemis.domain.quiz.QuizBatch) QuizMessagingService(de.tum.in.www1.artemis.service.QuizMessagingService) TimeLogUtil.formatDurationFrom(de.tum.in.www1.artemis.service.util.TimeLogUtil.formatDurationFrom) Service(org.springframework.stereotype.Service) Duration(java.time.Duration) SubmittedAnswer(de.tum.in.www1.artemis.domain.quiz.SubmittedAnswer) Nullable(javax.annotation.Nullable) Config(com.hazelcast.config.Config) HazelcastInstance(com.hazelcast.core.HazelcastInstance) Logger(org.slf4j.Logger) QuizExercise(de.tum.in.www1.artemis.domain.quiz.QuizExercise) ConstraintViolationException(org.hibernate.exception.ConstraintViolationException) com.hazelcast.scheduledexecutor(com.hazelcast.scheduledexecutor) IAtomicReference(com.hazelcast.cp.IAtomicReference) QuizSubmission(de.tum.in.www1.artemis.domain.quiz.QuizSubmission) EventListener(org.springframework.context.event.EventListener) SubmissionType(de.tum.in.www1.artemis.domain.enumeration.SubmissionType) NotNull(javax.validation.constraints.NotNull) AssessmentType(de.tum.in.www1.artemis.domain.enumeration.AssessmentType) InitializationState(de.tum.in.www1.artemis.domain.enumeration.InitializationState) Collectors(java.util.stream.Collectors) Constants(de.tum.in.www1.artemis.config.Constants) TimeUnit(java.util.concurrent.TimeUnit) Result(de.tum.in.www1.artemis.domain.Result) User(de.tum.in.www1.artemis.domain.User) QuizMode(de.tum.in.www1.artemis.domain.enumeration.QuizMode) Entry(java.util.Map.Entry) StudentParticipation(de.tum.in.www1.artemis.domain.participation.StudentParticipation) SimpMessageSendingOperations(org.springframework.messaging.simp.SimpMessageSendingOperations) StudentParticipation(de.tum.in.www1.artemis.domain.participation.StudentParticipation) ConstraintViolationException(org.hibernate.exception.ConstraintViolationException) QuizExercise(de.tum.in.www1.artemis.domain.quiz.QuizExercise) Result(de.tum.in.www1.artemis.domain.Result) QuizSubmission(de.tum.in.www1.artemis.domain.quiz.QuizSubmission) Entry(java.util.Map.Entry) QuizBatch(de.tum.in.www1.artemis.domain.quiz.QuizBatch)

Example 30 with StudentParticipation

use of de.tum.in.www1.artemis.domain.participation.StudentParticipation in project ArTEMiS by ls1intum.

the class ProgrammingSubmissionResultSimulationService method createSubmission.

/**
 * This method creates a new submission for the provided user
 * @param exerciseId the exerciseId of the exercise for which a submission should be created
 * This functionality is only for testing purposes (noVersionControlAndContinuousIntegrationAvailable)
 * @return the newly created and stored submission
 */
public ProgrammingSubmission createSubmission(Long exerciseId) {
    User user = userRepository.getUserWithGroupsAndAuthorities();
    Participant participant = user;
    ProgrammingExerciseStudentParticipation programmingExerciseStudentParticipation;
    ProgrammingExercise programmingExercise = programmingExerciseRepository.findByIdWithStudentParticipationsAndLegalSubmissionsElseThrow(exerciseId);
    Optional<StudentParticipation> optionalStudentParticipation = participationService.findOneByExerciseAndStudentLoginWithEagerSubmissionsAnyState(programmingExercise, user.getLogin());
    if (optionalStudentParticipation.isEmpty()) {
        programmingExerciseStudentParticipation = createParticipation(programmingExercise, participant, user);
    } else {
        programmingExerciseStudentParticipation = (ProgrammingExerciseStudentParticipation) optionalStudentParticipation.get();
    }
    ProgrammingSubmission programmingSubmission = new ProgrammingSubmission();
    programmingSubmission.setCommitHash(VCSSimulationUtils.simulateCommitHash());
    programmingSubmission.setSubmitted(true);
    programmingSubmission.setSubmissionDate(ZonedDateTime.now());
    programmingSubmission.setType(SubmissionType.MANUAL);
    programmingExerciseStudentParticipation.addSubmission(programmingSubmission);
    programmingSubmissionRepository.save(programmingSubmission);
    return programmingSubmission;
}
Also used : Participant(de.tum.in.www1.artemis.domain.participation.Participant) ProgrammingExerciseStudentParticipation(de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseStudentParticipation) ProgrammingExerciseStudentParticipation(de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseStudentParticipation) StudentParticipation(de.tum.in.www1.artemis.domain.participation.StudentParticipation)

Aggregations

StudentParticipation (de.tum.in.www1.artemis.domain.participation.StudentParticipation)219 Test (org.junit.jupiter.api.Test)118 WithMockUser (org.springframework.security.test.context.support.WithMockUser)112 ModelingSubmission (de.tum.in.www1.artemis.domain.modeling.ModelingSubmission)60 ModelingExercise (de.tum.in.www1.artemis.domain.modeling.ModelingExercise)50 ProgrammingExerciseStudentParticipation (de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseStudentParticipation)48 ZonedDateTime (java.time.ZonedDateTime)44 QuizExercise (de.tum.in.www1.artemis.domain.quiz.QuizExercise)42 EntityNotFoundException (de.tum.in.www1.artemis.web.rest.errors.EntityNotFoundException)40 AbstractSpringIntegrationBambooBitbucketJiraTest (de.tum.in.www1.artemis.AbstractSpringIntegrationBambooBitbucketJiraTest)36 ParameterizedTest (org.junit.jupiter.params.ParameterizedTest)36 Exam (de.tum.in.www1.artemis.domain.exam.Exam)30 PreAuthorize (org.springframework.security.access.prepost.PreAuthorize)30 TextPlagiarismResult (de.tum.in.www1.artemis.domain.plagiarism.text.TextPlagiarismResult)28 de.tum.in.www1.artemis.repository (de.tum.in.www1.artemis.repository)28 ModelingPlagiarismResult (de.tum.in.www1.artemis.domain.plagiarism.modeling.ModelingPlagiarismResult)26 de.tum.in.www1.artemis.domain (de.tum.in.www1.artemis.domain)24 StudentExam (de.tum.in.www1.artemis.domain.exam.StudentExam)24 Participation (de.tum.in.www1.artemis.domain.participation.Participation)24 Collectors (java.util.stream.Collectors)24