From b0b37f32a63828ccdd54857e7b4c2efeab33881e Mon Sep 17 00:00:00 2001 From: Matti Lupari Date: Tue, 11 Jun 2024 18:48:59 +0300 Subject: [PATCH] CSCEXAM-000 Config reader as scala --- app/controllers/admin/SettingsController.java | 9 +- .../admin/StatisticsController.java | 4 +- .../assessment/ExamInspectionController.java | 4 +- app/controllers/exam/ExamController.java | 15 +- .../examination/ExaminationController.java | 21 +- .../facility/AvailabilityController.java | 11 +- .../api/CollaborativeAttachmentInterface.java | 131 ++++---- .../CollaborativeEnrolmentController.java | 3 +- .../impl/CollaborativeExamController.java | 6 +- .../CollaborativeExamSectionController.java | 39 +-- .../impl/CollaborativeReviewController.java | 55 ++-- .../transfer/impl/DataTransferController.java | 3 +- .../impl/ExternalAttachmentLoaderImpl.java | 9 +- .../impl/ExternalCalendarController.java | 11 +- .../transfer/impl/ExternalExamController.java | 12 +- .../impl/ExternalExaminationController.java | 13 +- app/controllers/user/SessionController.java | 23 +- app/impl/CalendarHandlerImpl.java | 10 +- app/impl/mail/EmailComposerImpl.scala | 3 +- app/miscellaneous/config/ConfigReader.java | 61 ---- app/miscellaneous/config/ConfigReader.scala | 63 ++++ .../config/ConfigReaderImpl.java | 279 ------------------ .../config/ConfigReaderImpl.scala | 106 +++++++ app/miscellaneous/csv/CsvBuilderImpl.java | 4 +- app/models/questions/Question.java | 17 +- app/sanitizers/CommaJoinedListSanitizer.java | 6 +- app/sanitizers/CommentSanitizer.java | 9 +- app/sanitizers/ExamUpdateSanitizer.java | 8 +- app/sanitizers/SanitizingHelper.java | 13 +- app/sanitizers/SectionQuestionSanitizer.java | 8 +- test/controllers/CalendarControllerTest.java | 3 +- test/controllers/EnrolmentControllerTest.java | 3 +- .../integration/ExamAPIControllerTest.java | 4 +- test/helpers/AttachmentServlet.java | 5 +- .../collaborative-exam-listing.component.html | 2 +- ui/src/app/exam/exam.model.ts | 3 +- .../categories/graded-logged.component.html | 2 +- .../listing/categories/graded.component.html | 2 +- .../categories/in-progress.component.html | 2 +- 39 files changed, 404 insertions(+), 578 deletions(-) delete mode 100644 app/miscellaneous/config/ConfigReader.java create mode 100644 app/miscellaneous/config/ConfigReader.scala delete mode 100644 app/miscellaneous/config/ConfigReaderImpl.java create mode 100644 app/miscellaneous/config/ConfigReaderImpl.scala diff --git a/app/controllers/admin/SettingsController.java b/app/controllers/admin/SettingsController.java index 1842dacb2..1b1c3c60d 100644 --- a/app/controllers/admin/SettingsController.java +++ b/app/controllers/admin/SettingsController.java @@ -33,6 +33,7 @@ import play.libs.ws.WSRequest; import play.mvc.Http; import play.mvc.Result; +import scala.jdk.javaapi.CollectionConverters; public class SettingsController extends BaseController { @@ -176,7 +177,7 @@ public Result getMaxFilesize() { public Result getExamDurations() { ObjectNode node = Json.newObject(); ArrayNode durations = node.putArray("examDurations"); - configReader.getExamDurations().forEach(durations::add); + configReader.getExamDurationsJava().forEach(durations::add); return ok(Json.toJson(node)); } @@ -266,16 +267,16 @@ public Result getConfig() { node.put("hasCourseSearchIntegration", configReader.isCourseSearchActive()); node.put("anonymousReviewEnabled", configReader.isAnonymousReviewEnabled()); ObjectNode courseIntegrationUrls = Json.newObject(); - configReader.getCourseIntegrationUrls().forEach(courseIntegrationUrls::put); + CollectionConverters.asJava(configReader.getCourseIntegrationUrls()).forEach(courseIntegrationUrls::put); node.set("courseSearchIntegrationUrls", courseIntegrationUrls); ArrayNode durations = Json.newArray(); - configReader.getExamDurations().forEach(durations::add); + configReader.getExamDurationsJava().forEach(durations::add); node.set("examDurations", durations); ObjectNode roles = Json.newObject(); configReader - .getRoleMapping() + .getRoleMappingJava() .forEach((k, v) -> { ArrayNode role = Json.newArray(); v.forEach(role::add); diff --git a/app/controllers/admin/StatisticsController.java b/app/controllers/admin/StatisticsController.java index 231770697..0bc1f7cf7 100644 --- a/app/controllers/admin/StatisticsController.java +++ b/app/controllers/admin/StatisticsController.java @@ -285,8 +285,8 @@ public Result getReviewsByDate(String from, String to) throws IOException { data[2] = e.getCourse().getCode(); data[3] = ISODateTimeFormat.dateTimeNoMillis().print(new DateTime(e.getCreated())); data[4] = ISODateTimeFormat.dateTimeNoMillis().print(new DateTime(e.getGradedTime())); - data[5] = parse( - () -> String.format("%s %s", e.getGradedByUser().getFirstName(), e.getGradedByUser().getLastName()) + data[5] = parse(() -> + String.format("%s %s", e.getGradedByUser().getFirstName(), e.getGradedByUser().getLastName()) ); data[6] = e.getCourse().getCredits() == null ? "" : Double.toString(e.getCourse().getCredits()); // custom credits? diff --git a/app/controllers/assessment/ExamInspectionController.java b/app/controllers/assessment/ExamInspectionController.java index b41f17be4..e97bfe0e9 100644 --- a/app/controllers/assessment/ExamInspectionController.java +++ b/app/controllers/assessment/ExamInspectionController.java @@ -137,8 +137,8 @@ public Result deleteInspection(Long id) { .getChildren() .stream() .filter(c -> c.hasState(Exam.State.REVIEW, Exam.State.STUDENT_STARTED, Exam.State.REVIEW_STARTED)) - .forEach( - c -> c.getExamInspections().stream().filter(ei -> ei.getUser().equals(inspector)).forEach(Model::delete) + .forEach(c -> + c.getExamInspections().stream().filter(ei -> ei.getUser().equals(inspector)).forEach(Model::delete) ); inspection.delete(); return ok(); diff --git a/app/controllers/exam/ExamController.java b/app/controllers/exam/ExamController.java index b80cebc34..8867db185 100644 --- a/app/controllers/exam/ExamController.java +++ b/app/controllers/exam/ExamController.java @@ -355,11 +355,10 @@ public Result updateExam(Long id, Http.Request request) { if (exam.isOwnedOrCreatedBy(user) || user.hasRole(Role.Name.ADMIN)) { return examUpdater .updateTemporalFieldsAndValidate(exam, user, request) - .orElseGet( - () -> - examUpdater - .updateStateAndValidate(exam, user, request) - .orElseGet(() -> handleExamUpdate(exam, user, request)) + .orElseGet(() -> + examUpdater + .updateStateAndValidate(exam, user, request) + .orElseGet(() -> handleExamUpdate(exam, user, request)) ); } else { return forbidden("i18n_error_access_forbidden"); @@ -428,6 +427,7 @@ public Result updateExamLanguage(Long eid, String code, Http.Request request) { @Restrict({ @Group("TEACHER"), @Group("ADMIN") }) public Result copyExam(Long id, Http.Request request) { User user = request.attrs().get(Attrs.AUTHENTICATED_USER); + String examinationType = formFactory.form().bindFromRequest(request).get("examinationType"); if ( Exam.Implementation.valueOf(examinationType) != Exam.Implementation.AQUARIUM && @@ -435,8 +435,7 @@ public Result copyExam(Long id, Http.Request request) { ) { return forbidden("i18n_access_forbidden"); } - Exam prototype = DB - .find(Exam.class) // TODO: check if all this fetching is necessary + Exam prototype = DB.find(Exam.class) // TODO: check if all this fetching is necessary .fetch("creator", "id") .fetch("examType", "id, type") .fetch("examSections", "id, name, sequenceNumber") @@ -535,7 +534,7 @@ public Result createExamDraft(Http.Request request) { exam.setPeriodStart(start); exam.setPeriodEnd(start.plusDays(1)); } - exam.setDuration(configReader.getExamDurations().getFirst()); + exam.setDuration(configReader.getExamDurationsJava().getFirst()); if (configReader.isCourseGradeScaleOverridable()) { exam.setGradeScale(DB.find(GradeScale.class).findList().getFirst()); } diff --git a/app/controllers/examination/ExaminationController.java b/app/controllers/examination/ExaminationController.java index 4006c66d9..906ffdefc 100644 --- a/app/controllers/examination/ExaminationController.java +++ b/app/controllers/examination/ExaminationController.java @@ -133,7 +133,8 @@ private CompletionStage postProcessExisting( return wrapAsPromise(forbidden()); } ExamEnrolment enrolment = optionalEnrolment.get(); - return getEnrolmentError( // allow state = initialized + return getEnrolmentError( + // allow state = initialized enrolment, request ).thenComposeAsync( @@ -170,7 +171,8 @@ private CompletionStage createClone( return wrapAsPromise(forbidden()); } ExamEnrolment enrolment = optionalEnrolment.get(); - return getEnrolmentError( // allow state = initialized + return getEnrolmentError( + // allow state = initialized enrolment, request ).thenComposeAsync( @@ -302,7 +304,8 @@ public CompletionStage turnExam(String hash, Http.Request request) { autoEvaluationHandler.autoEvaluate(exam); } return ok().withSession(session); - })); + }) + ); } @Authenticated @@ -330,7 +333,8 @@ public CompletionStage abortExam(String hash, Http.Request request) { } else { return forbidden().withSession(session); } - })); + }) + ); } @Authenticated @@ -356,7 +360,8 @@ public CompletionStage answerEssay(String hash, Long questionId, Http.Re question.setEssayAnswer(answer); question.save(); return ok(answer); - })); + }) + ); } @Authenticated @@ -377,7 +382,8 @@ public CompletionStage answerMultiChoice(String hash, Long qid, Http.Req o.update(); }); return ok(); - })); + }) + ); } @Authenticated @@ -400,7 +406,8 @@ public CompletionStage answerClozeTest(String hash, Long questionId, Htt answer.setAnswer(request.attrs().getOptional(Attrs.ESSAY_ANSWER).orElse(null)); answer.save(); return ok(answer, PathProperties.parse("(id, objectVersion, answer)")); - })); + }) + ); } private Optional findParticipation(Exam exam, User user) { diff --git a/app/controllers/facility/AvailabilityController.java b/app/controllers/facility/AvailabilityController.java index 9bf89c574..fa9219469 100644 --- a/app/controllers/facility/AvailabilityController.java +++ b/app/controllers/facility/AvailabilityController.java @@ -87,12 +87,11 @@ public Result getAvailability(Long roomId, String day) { List slotsForDate = dateTimeHandler .getWorkingHoursForDate(window, room) .stream() - .map( - oh -> - new Interval( - oh.getHours().getStart().minusMillis(oh.getTimezoneOffset()), - oh.getHours().getEnd().minusMillis(oh.getTimezoneOffset()) - ) + .map(oh -> + new Interval( + oh.getHours().getStart().minusMillis(oh.getTimezoneOffset()), + oh.getHours().getEnd().minusMillis(oh.getTimezoneOffset()) + ) ) .map(this::round) .flatMap(i -> toOneHourChunks(i).stream()) diff --git a/app/controllers/iop/collaboration/api/CollaborativeAttachmentInterface.java b/app/controllers/iop/collaboration/api/CollaborativeAttachmentInterface.java index d831e98ad..bfedea6b1 100644 --- a/app/controllers/iop/collaboration/api/CollaborativeAttachmentInterface.java +++ b/app/controllers/iop/collaboration/api/CollaborativeAttachmentInterface.java @@ -101,11 +101,10 @@ default Either, LanguageInspection> findLanguageInspecti @Override default CompletionStage deleteExamAttachment(T id, Http.Request request) { return findExternalExam(id, request) - .map( - ee -> - findExam(ee) - .map(e -> deleteExternalAttachment(e, ee, e, getUser(request))) - .getOrElseGet(Function.identity()) + .map(ee -> + findExam(ee) + .map(e -> deleteExternalAttachment(e, ee, e, getUser(request))) + .getOrElseGet(Function.identity()) ) .getOrElseGet(Function.identity()); } @@ -118,11 +117,10 @@ default CompletionStage addAttachmentToExam(Http.Request request) { Http.MultipartFormData.FilePart filePart = mf.getFilePart(); final String id = mf.getForm().get("examId")[0]; return findExternalExam(parseId(id), request) - .map( - ee -> - findExam(ee) - .map(e -> uploadAttachment(filePart, ee, e, e, getUser(request))) - .getOrElseGet(Function.identity()) + .map(ee -> + findExam(ee) + .map(e -> uploadAttachment(filePart, ee, e, e, getUser(request))) + .getOrElseGet(Function.identity()) ) .getOrElseGet(Function.identity()); } @@ -146,16 +144,14 @@ default CompletionStage addAttachmentToQuestion(Http.Request request) { final String id = mf.getForm().get("examId")[0]; final Long qid = Long.parseLong(mf.getForm().get("questionId")[0]); return findExternalExam(parseId(id), request) - .map( - ee -> - findExam(ee) - .map( - e -> - findSectionQuestion(qid, e) - .map(sq -> uploadAttachment(filePart, ee, e, sq.getQuestion(), getUser(request))) - .getOrElseGet(Function.identity()) - ) - .getOrElseGet(Function.identity()) + .map(ee -> + findExam(ee) + .map(e -> + findSectionQuestion(qid, e) + .map(sq -> uploadAttachment(filePart, ee, e, sq.getQuestion(), getUser(request))) + .getOrElseGet(Function.identity()) + ) + .getOrElseGet(Function.identity()) ) .getOrElseGet(Function.identity()); } @@ -174,16 +170,14 @@ default CompletionStage downloadQuestionAttachment(T eid, Long qid, Http @Restrict({ @Group("TEACHER"), @Group("ADMIN") }) default CompletionStage deleteQuestionAttachment(T eid, Long qid, Http.Request request) { return findExternalExam(eid, request) - .map( - ee -> - findExam(ee) - .map( - e -> - findSectionQuestion(qid, e) - .map(sq -> deleteExternalAttachment(sq.getQuestion(), ee, e, getUser(request))) - .getOrElseGet(Function.identity()) - ) - .getOrElseGet(Function.identity()) + .map(ee -> + findExam(ee) + .map(e -> + findSectionQuestion(qid, e) + .map(sq -> deleteExternalAttachment(sq.getQuestion(), ee, e, getUser(request))) + .getOrElseGet(Function.identity()) + ) + .getOrElseGet(Function.identity()) ) .getOrElseGet(Function.identity()); } @@ -207,8 +201,10 @@ default CompletionStage addAttachmentToQuestionAnswer(Http.Request reque } return uploadAttachment(filePart, ee, e, sq.getEssayAnswer(), getUser(request)); }) - .getOrElseGet(Function.identity())) - .getOrElseGet(Function.identity())) + .getOrElseGet(Function.identity()) + ) + .getOrElseGet(Function.identity()) + ) .getOrElseGet(Function.identity()); } @@ -216,21 +212,18 @@ default CompletionStage addAttachmentToQuestionAnswer(Http.Request reque @Restrict({ @Group("ADMIN"), @Group("STUDENT") }) default CompletionStage deleteQuestionAnswerAttachment(Long qid, T eid, Http.Request request) { return findExternalExam(eid, request) - .map( - ee -> - findExam(ee) - .map( - e -> - findSectionQuestion(qid, e) - .map( - sq -> - findEssayAnswerWithAttachment(sq) - .map(ea -> deleteExternalAttachment(ea, ee, e, getUser(request))) - .getOrElseGet(Function.identity()) - ) + .map(ee -> + findExam(ee) + .map(e -> + findSectionQuestion(qid, e) + .map(sq -> + findEssayAnswerWithAttachment(sq) + .map(ea -> deleteExternalAttachment(ea, ee, e, getUser(request))) .getOrElseGet(Function.identity()) - ) - .getOrElseGet(Function.identity()) + ) + .getOrElseGet(Function.identity()) + ) + .getOrElseGet(Function.identity()) ) .getOrElseGet(Function.identity()); } @@ -282,8 +275,10 @@ default CompletionStage addStatementAttachment(T id, Http.Request reques } return uploadAttachment(filePart, ee, e, li.getStatement(), user); }) - .getOrElseGet(Function.identity())) - .getOrElseGet(Function.identity())) + .getOrElseGet(Function.identity()) + ) + .getOrElseGet(Function.identity()) + ) .getOrElseGet(Function.identity()); } @@ -292,16 +287,14 @@ default CompletionStage addStatementAttachment(T id, Http.Request reques @Override default CompletionStage downloadStatementAttachment(T id, Http.Request request) { return findExternalExam(id, request) - .map( - ee -> - findExam(ee) - .map( - e -> - findLanguageInspectionWithAttachment(e) - .map(li -> downloadExternalAttachment(li.getStatement().getAttachment())) - .getOrElseGet(Function.identity()) - ) - .getOrElseGet(Function.identity()) + .map(ee -> + findExam(ee) + .map(e -> + findLanguageInspectionWithAttachment(e) + .map(li -> downloadExternalAttachment(li.getStatement().getAttachment())) + .getOrElseGet(Function.identity()) + ) + .getOrElseGet(Function.identity()) ) .getOrElseGet(Function.identity()); } @@ -311,16 +304,14 @@ default CompletionStage downloadStatementAttachment(T id, Http.Request r @Override default CompletionStage deleteStatementAttachment(T id, Http.Request request) { return findExternalExam(id, request) - .map( - ee -> - findExam(ee) - .map( - e -> - findLanguageInspectionWithAttachment(e) - .map(li -> deleteExternalAttachment(li.getStatement(), ee, e, getUser(request))) - .getOrElseGet(Function.identity()) - ) - .getOrElseGet(Function.identity()) + .map(ee -> + findExam(ee) + .map(e -> + findLanguageInspectionWithAttachment(e) + .map(li -> deleteExternalAttachment(li.getStatement(), ee, e, getUser(request))) + .getOrElseGet(Function.identity()) + ) + .getOrElseGet(Function.identity()) ) .getOrElseGet(Function.identity()); } @@ -445,8 +436,8 @@ default CompletionStage uploadAttachment( if (StringUtils.isEmpty(externalId)) { return request .post(source) - .thenComposeAsync( - wsResponse -> createExternalAttachment(externalExam, exam, container, wsResponse, user) + .thenComposeAsync(wsResponse -> + createExternalAttachment(externalExam, exam, container, wsResponse, user) ); } return request diff --git a/app/controllers/iop/collaboration/impl/CollaborativeEnrolmentController.java b/app/controllers/iop/collaboration/impl/CollaborativeEnrolmentController.java index 66d318f73..561bf990a 100644 --- a/app/controllers/iop/collaboration/impl/CollaborativeEnrolmentController.java +++ b/app/controllers/iop/collaboration/impl/CollaborativeEnrolmentController.java @@ -124,7 +124,8 @@ public CompletionStage checkIfEnrolled(Long id, Http.Request request) { .findList(); return ok(enrolments); }) - .getOrElseGet(Function.identity())); + .getOrElseGet(Function.identity()) + ); } private static ExamEnrolment makeEnrolment(CollaborativeExam exam, User user) { diff --git a/app/controllers/iop/collaboration/impl/CollaborativeExamController.java b/app/controllers/iop/collaboration/impl/CollaborativeExamController.java index 228025400..6ab152f0a 100644 --- a/app/controllers/iop/collaboration/impl/CollaborativeExamController.java +++ b/app/controllers/iop/collaboration/impl/CollaborativeExamController.java @@ -1,5 +1,4 @@ // SPDX-FileCopyrightText: 2024 The members of the EXAM Consortium -// SPDX-FileCopyrightText: 2024. The members of the EXAM Consortium // // SPDX-License-Identifier: EUPL-1.2 @@ -79,7 +78,7 @@ private Exam prepareDraft(User user) { DateTime start = DateTime.now().withTimeAtStartOfDay(); exam.setPeriodStart(start); exam.setPeriodEnd(start.plusDays(1)); - exam.setDuration(configReader.getExamDurations().getFirst()); // check + exam.setDuration(configReader.getExamDurationsJava().getFirst()); // check exam.setGradeScale(DB.find(GradeScale.class).findList().getFirst()); // check exam.setTrialCount(1); @@ -127,7 +126,8 @@ private CompletionStage getExam(Long id, Consumer postProcessor, U } postProcessor.accept(exam); return ok(serialize(exam)); - })) + }) + ) .getOrElseGet(Function.identity()); } diff --git a/app/controllers/iop/collaboration/impl/CollaborativeExamSectionController.java b/app/controllers/iop/collaboration/impl/CollaborativeExamSectionController.java index b6af12d6a..93ab856b7 100644 --- a/app/controllers/iop/collaboration/impl/CollaborativeExamSectionController.java +++ b/app/controllers/iop/collaboration/impl/CollaborativeExamSectionController.java @@ -147,11 +147,8 @@ public CompletionStage updateSection(Long examId, Long sectionId, Http.R } }; - return update( - request, - examId, - updater, - exam -> exam.getExamSections().stream().filter(es -> es.getId().equals(sectionId)).findFirst() + return update(request, examId, updater, exam -> + exam.getExamSections().stream().filter(es -> es.getId().equals(sectionId)).findFirst() ); } @@ -284,17 +281,13 @@ public CompletionStage addQuestion(Long examId, Long sectionId, Http.Req return Optional.of(notFound("i18n_error_not_found")); } }; - return update( - request, - examId, - updater, - exam -> - exam - .getExamSections() - .stream() - .flatMap(s -> s.getSectionQuestions().stream()) - .filter(sq -> sq.getId().equals(sectionQuestionId)) - .findFirst() + return update(request, examId, updater, exam -> + exam + .getExamSections() + .stream() + .flatMap(s -> s.getSectionQuestions().stream()) + .filter(sq -> sq.getId().equals(sectionQuestionId)) + .findFirst() ); } @@ -338,11 +331,8 @@ public CompletionStage removeQuestion(Long examId, Long sectionId, Long return Optional.of(notFound("i18n_error_not_found")); } }; - return update( - request, - examId, - updater, - exam -> exam.getExamSections().stream().filter(es -> es.getId().equals(sectionId)).findFirst() + return update(request, examId, updater, exam -> + exam.getExamSections().stream().filter(es -> es.getId().equals(sectionId)).findFirst() ); } @@ -363,11 +353,8 @@ public CompletionStage clearQuestions(Long examId, Long sectionId, Http. return Optional.of(notFound("i18n_error_not_found")); } }; - return update( - request, - examId, - updater, - exam -> exam.getExamSections().stream().filter(es -> es.getId().equals(sectionId)).findFirst() + return update(request, examId, updater, exam -> + exam.getExamSections().stream().filter(es -> es.getId().equals(sectionId)).findFirst() ); } diff --git a/app/controllers/iop/collaboration/impl/CollaborativeReviewController.java b/app/controllers/iop/collaboration/impl/CollaborativeReviewController.java index 6b563a879..7531d4e3c 100644 --- a/app/controllers/iop/collaboration/impl/CollaborativeReviewController.java +++ b/app/controllers/iop/collaboration/impl/CollaborativeReviewController.java @@ -109,8 +109,8 @@ private Result handleSingleAssessmentResponse(Http.Request request, WSResponse r // Manipulate cloze test answers so that they can be conveniently displayed for review stream(examNode.get("examSections")) .flatMap(es -> stream(es.get("sectionQuestions"))) - .filter( - esq -> esq.get("question").get("type").textValue().equals(Question.Type.ClozeTestQuestion.toString()) + .filter(esq -> + esq.get("question").get("type").textValue().equals(Question.Type.ClozeTestQuestion.toString()) ) .forEach(esq -> { if (!esq.get("clozeTestAnswer").isObject() || esq.get("clozeTestAnswer").isEmpty()) { @@ -164,23 +164,16 @@ private void scoreAnswer(JsonNode examNode, Long qid, Double score) { public CompletionStage listAssessments(Long id, Http.Request request) { User user = request.attrs().get(Attrs.AUTHENTICATED_USER); return findCollaborativeExam(id) - .map( - ce -> - getRequest(ce, null) - .map( - wsr -> - wsr - .get() - .thenApplyAsync( - response -> - handleMultipleAssessmentResponse( - request, - response, - user.hasRole(Role.Name.ADMIN) - ) - ) - ) - .getOrElseGet(Function.identity()) + .map(ce -> + getRequest(ce, null) + .map(wsr -> + wsr + .get() + .thenApplyAsync(response -> + handleMultipleAssessmentResponse(request, response, user.hasRole(Role.Name.ADMIN)) + ) + ) + .getOrElseGet(Function.identity()) ) .getOrElseGet(Function.identity()); } @@ -280,8 +273,10 @@ public CompletionStage exportAssessments(Long id, Http.Request request) "Content-Disposition", contentDisposition ); - })) - .getOrElseGet(Function.identity())) + }) + ) + .getOrElseGet(Function.identity()) + ) .getOrElseGet(Function.identity()); } @@ -341,7 +336,8 @@ public CompletionStage updateAnswerScore(Long id, String ref, Long qid, }; return wsRequest.get().thenComposeAsync(onSuccess); }) - .getOrElseGet(Function.identity())) + .getOrElseGet(Function.identity()) + ) .get(); } @@ -395,7 +391,8 @@ public CompletionStage sendInspectionMessage(Long id, String ref, Http.R }; return wsRequest.get().thenComposeAsync(onSuccess); }) - .getOrElseGet(Function.identity())) + .getOrElseGet(Function.identity()) + ) .get(); } @@ -541,7 +538,8 @@ public CompletionStage addComment(Long id, String ref, Http.Request requ }; return wsRequest.get().thenComposeAsync(onSuccess); }) - .getOrElseGet(Function.identity())) + .getOrElseGet(Function.identity()) + ) .getOrElseGet(Function.identity()); } @@ -567,7 +565,8 @@ public CompletionStage setFeedbackRead(String examRef, String assessment }; return wsRequest.get().thenComposeAsync(onSuccess); }) - .getOrElseGet(Function.identity())) + .getOrElseGet(Function.identity()) + ) .getOrElseGet(Function.identity()); } @@ -677,8 +676,10 @@ public CompletionStage finalizeAssessment(Long id, String ref, Http.Requ .getOrElseGet(Function.identity()); return wsr.get().thenComposeAsync(onSuccess); }) - .getOrElseGet(Function.identity())) - .getOrElseGet(Function.identity())) + .getOrElseGet(Function.identity()) + ) + .getOrElseGet(Function.identity()) + ) .getOrElseGet(Function.identity()); } diff --git a/app/controllers/iop/transfer/impl/DataTransferController.java b/app/controllers/iop/transfer/impl/DataTransferController.java index 1c196e57e..51c024b9c 100644 --- a/app/controllers/iop/transfer/impl/DataTransferController.java +++ b/app/controllers/iop/transfer/impl/DataTransferController.java @@ -194,7 +194,8 @@ public CompletionStage exportData(Http.Request request) throws IOExcepti e ); return null; - })); + }) + ); }) .toArray(CompletableFuture[]::new) ).thenComposeAsync(__ -> wrapAsPromise(created())); diff --git a/app/controllers/iop/transfer/impl/ExternalAttachmentLoaderImpl.java b/app/controllers/iop/transfer/impl/ExternalAttachmentLoaderImpl.java index 741d10481..4037e8fbc 100644 --- a/app/controllers/iop/transfer/impl/ExternalAttachmentLoaderImpl.java +++ b/app/controllers/iop/transfer/impl/ExternalAttachmentLoaderImpl.java @@ -79,11 +79,10 @@ public CompletableFuture fetchExternalAttachmentsAsLocal(Exam exam) { }) .filter(question -> question.getAttachment() != null) .distinct() - .forEach( - question -> - futures.add( - createFromExternalAttachment(question.getAttachment(), "question", question.getId().toString()) - ) + .forEach(question -> + futures.add( + createFromExternalAttachment(question.getAttachment(), "question", question.getId().toString()) + ) ); return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); } diff --git a/app/controllers/iop/transfer/impl/ExternalCalendarController.java b/app/controllers/iop/transfer/impl/ExternalCalendarController.java index 7df4d07d7..3fad72380 100644 --- a/app/controllers/iop/transfer/impl/ExternalCalendarController.java +++ b/app/controllers/iop/transfer/impl/ExternalCalendarController.java @@ -224,12 +224,11 @@ public Result provideSlots( .gt("endsAt", searchDate.toDate()) .findList() .stream() - .map( - p -> - new Interval( - calendarHandler.normalizeMaintenanceTime(p.getStartsAt()), - calendarHandler.normalizeMaintenanceTime(p.getEndsAt()) - ) + .map(p -> + new Interval( + calendarHandler.normalizeMaintenanceTime(p.getStartsAt()), + calendarHandler.normalizeMaintenanceTime(p.getEndsAt()) + ) ) .toList(); LocalDate endOfSearch = getEndSearchDate(end.get(), searchDate); diff --git a/app/controllers/iop/transfer/impl/ExternalExamController.java b/app/controllers/iop/transfer/impl/ExternalExamController.java index 8616209c2..77c3e9b74 100644 --- a/app/controllers/iop/transfer/impl/ExternalExamController.java +++ b/app/controllers/iop/transfer/impl/ExternalExamController.java @@ -205,8 +205,8 @@ public CompletionStage addExamForAssessment(String ref, Http.Request req if (enrolment.getCollaborativeExam() != null) { return collaborativeExamLoader .createAssessment(ep) - .thenComposeAsync( - ok -> CompletableFuture.supplyAsync(ok ? Results::created : Results::internalServerError) + .thenComposeAsync(ok -> + CompletableFuture.supplyAsync(ok ? Results::created : Results::internalServerError) ); } else { // Fetch external attachments to local exam. @@ -284,8 +284,8 @@ public CompletionStage provideEnrolment(String ref) { .map(ExamSectionQuestion::getQuestion) .filter(question -> question.getAttachment() != null) .distinct() - .forEach( - question -> futures.add(externalAttachmentLoader.createExternalAttachment(question.getAttachment())) + .forEach(question -> + futures.add(externalAttachmentLoader.createExternalAttachment(question.getAttachment())) ); return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) .thenComposeAsync(aVoid -> wrapAsPromise(ok(exam, getPath()))) @@ -357,8 +357,8 @@ public CompletionStage requestEnrolment(User user, Reservation re .getOptions() .stream() .collect( - Collectors.toCollection( - () -> new TreeSet<>(Comparator.comparingLong(esqo -> esqo.getOption().getId())) + Collectors.toCollection(() -> + new TreeSet<>(Comparator.comparingLong(esqo -> esqo.getOption().getId())) ) ); esq.setOptions(sorted); diff --git a/app/controllers/iop/transfer/impl/ExternalExaminationController.java b/app/controllers/iop/transfer/impl/ExternalExaminationController.java index e240295ac..b7386629d 100644 --- a/app/controllers/iop/transfer/impl/ExternalExaminationController.java +++ b/app/controllers/iop/transfer/impl/ExternalExaminationController.java @@ -172,7 +172,8 @@ public CompletionStage answerMultiChoice(String hash, Long qid, Http.Req return findSectionQuestion(ee, qid) .map(t -> processOptions(optionIds, t._2, ee, t._1)) .getOrElseGet(Function.identity()); - })); + }) + ); } @Authenticated @@ -219,7 +220,8 @@ public CompletionStage answerEssay(String hash, Long qid, Http.Request r return internalServerError(); } return ok(answer); - })); + }) + ); } private Either> findSectionQuestion(ExternalExam ee, Long qid) { @@ -232,8 +234,8 @@ private Either> findSectionQuestion(Ex return Either.left(internalServerError()); } return optionalQuestion - .>>map( - examSectionQuestion -> Either.right(Tuple.of(content, examSectionQuestion)) + .>>map(examSectionQuestion -> + Either.right(Tuple.of(content, examSectionQuestion)) ) .orElseGet(() -> Either.left(forbidden())); } @@ -274,7 +276,8 @@ public CompletionStage answerClozeTest(String hash, Long qid, Http.Reque return ok(answer, PathProperties.parse("(id, objectVersion, answer)")); }) .getOrElseGet(Function.identity()); - })); + }) + ); } private Optional findQuestion(Long qid, Exam content) { diff --git a/app/controllers/user/SessionController.java b/app/controllers/user/SessionController.java index 2c08e357a..6b55ef0dc 100644 --- a/app/controllers/user/SessionController.java +++ b/app/controllers/user/SessionController.java @@ -166,8 +166,8 @@ private CompletionStage handleExternalReservationAndCreateSession( ) { if (reservation != null) { try { - return handleExternalReservation(user, reservation).thenComposeAsync( - r -> createSession(user, true, request) + return handleExternalReservation(user, reservation).thenComposeAsync(r -> + createSession(user, true, request) ); } catch (MalformedURLException e) { return wrapAsPromise(internalServerError()); @@ -283,11 +283,10 @@ private String parseUserIdentifier(String src) { }, () -> new TreeMap<>( - Comparator.comparingInt( - o -> - !configReader.getMultiStudentOrganisations().contains(o) - ? 1000 - : configReader.getMultiStudentOrganisations().indexOf(o) + Comparator.comparingInt(o -> + !configReader.getMultiStudentOrganisations().contains(o) + ? 1000 + : configReader.getMultiStudentOrganisations().indexOf(o) ) ) ) @@ -300,8 +299,8 @@ private String parseUserIdentifier(String src) { } private Optional parseDisplayName(Http.Request request) { - return parse(request.header("displayName").orElse("")).map( - n -> n.indexOf(" ") > 0 ? n.substring(0, n.lastIndexOf(" ")) : n + return parse(request.header("displayName").orElse("")).map(n -> + n.indexOf(" ") > 0 ? n.substring(0, n.lastIndexOf(" ")) : n ); } @@ -349,8 +348,8 @@ private User createNewUser(String eppn, Http.Request request, boolean ignoreRole .getRoles() .addAll( parseRoles( - parse(request.header("unscoped-affiliation").orElse("")).orElseThrow( - () -> new NotFoundException("role not found") + parse(request.header("unscoped-affiliation").orElse("")).orElseThrow(() -> + new NotFoundException("role not found") ), ignoreRoleNotFound ) @@ -408,7 +407,7 @@ public Result getAttributes(Http.Request request) { private Set parseRoles(String attribute, boolean ignoreRoleNotFound) throws NotFoundException { Set userRoles = new HashSet<>(); for (String affiliation : attribute.split(";")) { - for (Map.Entry> entry : configReader.getRoleMapping().entrySet()) { + for (Map.Entry> entry : configReader.getRoleMappingJava().entrySet()) { if (entry.getValue().contains(affiliation)) { userRoles.add(entry.getKey()); break; diff --git a/app/impl/CalendarHandlerImpl.java b/app/impl/CalendarHandlerImpl.java index 7387106b3..8abefe259 100644 --- a/app/impl/CalendarHandlerImpl.java +++ b/app/impl/CalendarHandlerImpl.java @@ -112,9 +112,8 @@ public Result getSlots(User user, Exam exam, Long roomId, String day, Collection .gt("endsAt", searchDate.toDate()) .findList() .stream() - .map( - p -> - new Interval(normalizeMaintenanceTime(p.getStartsAt()), normalizeMaintenanceTime(p.getEndsAt())) + .map(p -> + new Interval(normalizeMaintenanceTime(p.getStartsAt()), normalizeMaintenanceTime(p.getEndsAt())) ) .toList(); LocalDate endOfSearch = getEndSearchDate(searchDate, new LocalDate(exam.getPeriodEnd())); @@ -458,9 +457,8 @@ public Set postProcessSlots(JsonNode node, String date .ge("endsAt", searchDate.withDayOfWeek(DateTimeConstants.MONDAY).toDate()) .findList() .stream() - .map( - p -> - new Interval(normalizeMaintenanceTime(p.getStartsAt()), normalizeMaintenanceTime(p.getEndsAt())) + .map(p -> + new Interval(normalizeMaintenanceTime(p.getStartsAt()), normalizeMaintenanceTime(p.getEndsAt())) ) .toList(); // Filter out slots that overlap a local maintenance period diff --git a/app/impl/mail/EmailComposerImpl.scala b/app/impl/mail/EmailComposerImpl.scala index e9bd67b56..62634a35a 100644 --- a/app/impl/mail/EmailComposerImpl.scala +++ b/app/impl/mail/EmailComposerImpl.scala @@ -558,9 +558,8 @@ class EmailComposerImpl @Inject() ( )(lang) val teacherName = messaging("email.template.participant.notification.teacher", getTeachers(exam))(lang) val events = exam.getExaminationEventConfigurations.asScala.toList - .map(_.getExaminationEvent.getStart) + .map(c => new DateTime(c.getExaminationEvent.getStart, timeZone)) .sorted - .map(c => new DateTime(c, timeZone)) .map(EmailComposerImpl.DTF.print) .mkString(", ") val examPeriod = diff --git a/app/miscellaneous/config/ConfigReader.java b/app/miscellaneous/config/ConfigReader.java deleted file mode 100644 index 7a39317b6..000000000 --- a/app/miscellaneous/config/ConfigReader.java +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-FileCopyrightText: 2024 The members of the EXAM Consortium -// SPDX-FileCopyrightText: 2024. The members of the EXAM Consortium -// -// SPDX-License-Identifier: EUPL-1.2 - -package miscellaneous.config; - -import java.util.List; -import java.util.Map; -import models.user.Role; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; - -public interface ConfigReader { - DateTimeZone getDefaultTimeZone(); - String getHostName(); - Integer getMaxFileSize(); - List getExamDurations(); - Integer getExamMaxDuration(); - Integer getExamMinDuration(); - Map> getRoleMapping(); - boolean isCourseGradeScaleOverridable(); - boolean isEnrolmentPermissionCheckActive(); - boolean isVisitingExaminationSupported(); - boolean isCollaborationExaminationSupported(); - boolean isHomeExaminationSupported(); - boolean isSebExaminationSupported(); - boolean isCourseSearchActive(); - Map getCourseIntegrationUrls(); - DateTime getExamExpirationDate(DateTime timeOfSubmission); - DateTime getCourseValidityDate(DateTime startDate); - String getExamExpirationPeriod(); - boolean isMaturitySupported(); - boolean isPrintoutSupported(); - String getAppVersion(); - boolean isAnonymousReviewEnabled(); - String getQuitExaminationLink(); - String getExaminationAdminPassword(); - String getSettingsPasswordEncryptionKey(); - String getHomeOrganisationRef(); - Integer getMaxByodExaminationParticipantCount(); - boolean isByodExamCreationPermissionGrantedForNewUsers(); - String getCourseCodePrefix(); - String getIopHost(); - boolean isApiKeyUsed(); - String getApiKeyName(); - String getApiKeyValue(); - String getPermissionCheckUserIdentifier(); - String getPermissionCheckUrl(); - String getBaseSystemUrl(); - String getSystemAccount(); - String getAttachmentPath(); - String getLoginType(); - String getCsrfCookie(); - boolean isMultiStudentIdEnabled(); - String getMultiStudentOrganisations(); - List getSupportedLanguages(); - - boolean hasPath(String path); - String getString(String path); -} diff --git a/app/miscellaneous/config/ConfigReader.scala b/app/miscellaneous/config/ConfigReader.scala new file mode 100644 index 000000000..c8ddb9059 --- /dev/null +++ b/app/miscellaneous/config/ConfigReader.scala @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2024 The members of the EXAM Consortium +// +// SPDX-License-Identifier: EUPL-1.2 + +package miscellaneous.config + +import com.google.inject.ImplementedBy +import models.user.Role +import org.joda.time.DateTime +import org.joda.time.DateTimeZone + +import scala.jdk.CollectionConverters._ + +@ImplementedBy(classOf[ConfigReaderImpl]) +trait ConfigReader: + def getDefaultTimeZone: DateTimeZone + def getHostName: String + def getMaxFileSize: Int + def getExamDurations: List[Int] + def getExamDurationsJava: java.util.List[Integer] = getExamDurations.map(Integer.valueOf).asJava + def getExamMaxDuration: Int + def getExamMinDuration: Int + def getRoleMapping: Map[Role, List[String]] + def getRoleMappingJava: java.util.Map[Role, java.util.List[String]] = + getRoleMapping.map((k, v) => k -> v.asJava).asJava + def isCourseGradeScaleOverridable: Boolean + def isEnrolmentPermissionCheckActive: Boolean + def isVisitingExaminationSupported: Boolean + def isCollaborationExaminationSupported: Boolean + def isHomeExaminationSupported: Boolean + def isSebExaminationSupported: Boolean + def isCourseSearchActive: Boolean + def getCourseIntegrationUrls: Map[String, String] + def getExamExpirationDate(timeOfSubmission: DateTime): DateTime + def getCourseValidityDate(startDate: DateTime): DateTime + def getExamExpirationPeriod: String + def isMaturitySupported: Boolean + def isPrintoutSupported: Boolean + def getAppVersion: String + def isAnonymousReviewEnabled: Boolean + def getQuitExaminationLink: String + def getExaminationAdminPassword: String + def getSettingsPasswordEncryptionKey: String + def getHomeOrganisationRef: String + def getMaxByodExaminationParticipantCount: Int + def isByodExamCreationPermissionGrantedForNewUsers: Boolean + def getCourseCodePrefix: String + def getIopHost: String + def isApiKeyUsed: Boolean + def getApiKeyName: String + def getApiKeyValue: String + def getPermissionCheckUserIdentifier: String + def getPermissionCheckUrl: String + def getBaseSystemUrl: String + def getSystemAccount: String + def getAttachmentPath: String + def getLoginType: String + def getCsrfCookie: String + def isMultiStudentIdEnabled: Boolean + def getMultiStudentOrganisations: String + def getSupportedLanguages: List[String] + def hasPath(path: String): Boolean + def getString(path: String): String diff --git a/app/miscellaneous/config/ConfigReaderImpl.java b/app/miscellaneous/config/ConfigReaderImpl.java deleted file mode 100644 index 90541a4ed..000000000 --- a/app/miscellaneous/config/ConfigReaderImpl.java +++ /dev/null @@ -1,279 +0,0 @@ -// SPDX-FileCopyrightText: 2024 The members of the EXAM Consortium -// -// SPDX-License-Identifier: EUPL-1.2 - -package miscellaneous.config; - -import com.typesafe.config.Config; -import io.ebean.DB; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.stream.Collectors; -import javax.inject.Inject; -import models.exam.ExamExecutionType; -import models.user.Role; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.Period; - -public class ConfigReaderImpl implements ConfigReader { - - private final Config config; - - @Inject - public ConfigReaderImpl(Config config) { - this.config = config; - } - - @Override - public DateTimeZone getDefaultTimeZone() { - String id = config.getString("exam.application.timezone"); - return DateTimeZone.forID(id); - } - - @Override - public String getHostName() { - return config.getString("exam.application.hostname"); - } - - @Override - public Integer getMaxFileSize() { - return config.getInt("exam.attachment.maxsize"); - } - - @Override - public List getExamDurations() { - String[] durations = config.getString("exam.exam.durations").split(","); - return Arrays.stream(durations).map(Integer::parseInt).toList(); - } - - @Override - public Integer getExamMaxDuration() { - return config.getInt("exam.exam.maxDuration"); - } - - @Override - public Integer getExamMinDuration() { - return config.getInt("exam.exam.minDuration"); - } - - @Override - public Map> getRoleMapping() { - Role student = DB.find(Role.class).where().eq("name", Role.Name.STUDENT.toString()).findOne(); - Role teacher = DB.find(Role.class).where().eq("name", Role.Name.TEACHER.toString()).findOne(); - Role admin = DB.find(Role.class).where().eq("name", Role.Name.ADMIN.toString()).findOne(); - Map> roles = new HashMap<>(); - roles.put(student, config.getStringList("exam.roles.student")); - roles.put(teacher, config.getStringList("exam.roles.teacher")); - roles.put(admin, config.getStringList("exam.roles.admin")); - return roles; - } - - @Override - public boolean isCourseGradeScaleOverridable() { - return config.getBoolean("exam.course.gradescale.overridable"); - } - - @Override - public boolean isEnrolmentPermissionCheckActive() { - return config.getBoolean("exam.integration.enrolmentPermissionCheck.active"); - } - - @Override - public boolean isVisitingExaminationSupported() { - return config.getBoolean("exam.integration.iop.visit.active"); - } - - @Override - public boolean isCollaborationExaminationSupported() { - return config.getBoolean("exam.integration.iop.collaboration.active"); - } - - @Override - public boolean isHomeExaminationSupported() { - return config.getBoolean("exam.byod.home.active"); - } - - @Override - public boolean isSebExaminationSupported() { - return config.getBoolean("exam.byod.seb.active"); - } - - @Override - public boolean isCourseSearchActive() { - return config.getBoolean("exam.integration.courseUnitInfo.active"); - } - - @Override - public Map getCourseIntegrationUrls() { - Config urls = config.getConfig("exam.integration.courseUnitInfo.url"); - return urls.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().render())); - } - - @Override - public DateTime getExamExpirationDate(DateTime timeOfSubmission) { - String expiresAfter = config.getString("exam.exam.expiration.period"); - Period period = Period.parse(expiresAfter); - return timeOfSubmission.plus(period); - } - - @Override - public DateTime getCourseValidityDate(DateTime startDate) { - String window = config.getString("exam.integration.courseUnitInfo.window"); - Period period = Period.parse(window); - return startDate.minus(period); - } - - @Override - public String getExamExpirationPeriod() { - return config.getString("exam.exam.expiration.period"); - } - - @Override - public boolean isMaturitySupported() { - return DB.find(ExamExecutionType.class) - .where() - .eq("type", ExamExecutionType.Type.MATURITY.toString()) - .findOneOrEmpty() - .isPresent(); - } - - @Override - public boolean isPrintoutSupported() { - return DB.find(ExamExecutionType.class) - .where() - .eq("type", ExamExecutionType.Type.PRINTOUT.toString()) - .findOneOrEmpty() - .isPresent(); - } - - @Override - public String getAppVersion() { - return config.getString("exam.release.version"); - } - - @Override - public boolean isAnonymousReviewEnabled() { - return config.getBoolean("exam.exam.anonymousReview"); - } - - @Override - public String getQuitExaminationLink() { - return config.getString("exam.exam.seb.quitLink"); - } - - @Override - public String getExaminationAdminPassword() { - return config.getBoolean("exam.exam.seb.adminPwd.randomize") - ? UUID.randomUUID().toString() - : config.getString("exam.exam.seb.adminPwd.value"); - } - - @Override - public String getSettingsPasswordEncryptionKey() { - return config.getString("exam.exam.seb.settingsPwd.encryption.key"); - } - - @Override - public String getHomeOrganisationRef() { - return config.getString("exam.integration.iop.organisationRef"); - } - - @Override - public Integer getMaxByodExaminationParticipantCount() { - return config.getInt("exam.byod.maxConcurrentParticipants"); - } - - @Override - public boolean isByodExamCreationPermissionGrantedForNewUsers() { - return config.getBoolean("exam.byod.permission.allowed"); - } - - @Override - public String getCourseCodePrefix() { - return config.getString("exam.course.code.prefix"); - } - - @Override - public String getIopHost() { - return config.getString("exam.integration.iop.host"); - } - - @Override - public boolean isApiKeyUsed() { - return config.getBoolean("exam.integration.apiKey.enabled"); - } - - @Override - public String getApiKeyName() { - return config.getString("exam.integration.apiKey.name"); - } - - @Override - public String getApiKeyValue() { - return config.getString("exam.integration.apiKey.value"); - } - - @Override - public String getPermissionCheckUserIdentifier() { - return config.getString("exam.integration.enrolmentPermissionCheck.id"); - } - - @Override - public String getPermissionCheckUrl() { - return config.getString("exam.integration.enrolmentPermissionCheck.url"); - } - - @Override - public String getBaseSystemUrl() { - return config.getString("exam.baseSystemURL"); - } - - @Override - public String getSystemAccount() { - return config.getString("exam.email.system.account"); - } - - @Override - public String getAttachmentPath() { - return config.getString("exam.attachments.path"); - } - - @Override - public String getLoginType() { - return config.getString("exam.login"); - } - - @Override - public boolean isMultiStudentIdEnabled() { - return config.getBoolean("exam.user.studentIds.multiple.enabled"); - } - - @Override - public String getMultiStudentOrganisations() { - return config.getString("exam.user.studentIds.multiple.organisations"); - } - - @Override - public String getCsrfCookie() { - return config.getString("play.filters.csrf.cookie.name"); - } - - @Override - public List getSupportedLanguages() { - return config.getStringList("play.i18n.langs"); - } - - @Override - public boolean hasPath(String path) { - return config.hasPath(path); - } - - @Override - public String getString(String path) { - return config.getString(path); - } -} diff --git a/app/miscellaneous/config/ConfigReaderImpl.scala b/app/miscellaneous/config/ConfigReaderImpl.scala new file mode 100644 index 000000000..cadc391e7 --- /dev/null +++ b/app/miscellaneous/config/ConfigReaderImpl.scala @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2024 The members of the EXAM Consortium +// +// SPDX-License-Identifier: EUPL-1.2 + +package miscellaneous.config + +import com.typesafe.config.Config +import io.ebean.DB +import miscellaneous.scala.DbApiHelper +import models.exam.ExamExecutionType +import models.user.Role +import org.joda.time.{DateTime, DateTimeZone, Period} + +import java.util.UUID +import javax.inject.Inject +import scala.jdk.CollectionConverters._ + +class ConfigReaderImpl @Inject (private val config: Config) extends ConfigReader with DbApiHelper: + override def getDefaultTimeZone: DateTimeZone = { + val id = config.getString("exam.application.timezone") + DateTimeZone.forID(id) + } + override def getHostName: String = config.getString("exam.application.hostname") + override def getMaxFileSize: Int = config.getInt("exam.attachment.maxsize") + override def getExamDurations: List[Int] = + config.getString("exam.exam.durations").split(",").map(_.toInt).toList + + override def getExamMaxDuration: Int = config.getInt("exam.exam.maxDuration") + override def getExamMinDuration: Int = config.getInt("exam.exam.minDuration") + override def getRoleMapping: Map[Role, List[String]] = + val student = DB.find(classOf[Role]).where.eq("name", Role.Name.STUDENT.toString).findOne + val teacher = DB.find(classOf[Role]).where.eq("name", Role.Name.TEACHER.toString).findOne + val admin = DB.find(classOf[Role]).where.eq("name", Role.Name.ADMIN.toString).findOne + Map( + student -> config.getStringList("exam.roles.student").asScala.toList, + teacher -> config.getStringList("exam.roles.teacher").asScala.toList, + admin -> config.getStringList("exam.roles.admin").asScala.toList + ) + + override def isCourseGradeScaleOverridable: Boolean = config.getBoolean("exam.course.gradescale.overridable") + override def isEnrolmentPermissionCheckActive: Boolean = + config.getBoolean("exam.integration.enrolmentPermissionCheck.active") + override def isVisitingExaminationSupported: Boolean = config.getBoolean("exam.integration.iop.visit.active") + override def isCollaborationExaminationSupported: Boolean = + config.getBoolean("exam.integration.iop.collaboration.active") + override def isHomeExaminationSupported: Boolean = config.getBoolean("exam.byod.home.active") + override def isSebExaminationSupported: Boolean = config.getBoolean("exam.byod.seb.active") + override def isCourseSearchActive: Boolean = config.getBoolean("exam.integration.courseUnitInfo.active") + override def getCourseIntegrationUrls: Map[String, String] = + config + .getConfig("exam.integration.courseUnitInfo.url") + .entrySet() + .asScala + .map(e => e.getKey -> e.getValue.render) + .toMap + + override def getExamExpirationDate(timeOfSubmission: DateTime): DateTime = + val expiresAfter = config.getString("exam.exam.expiration.period") + val period = Period.parse(expiresAfter) + timeOfSubmission.plus(period) + override def getCourseValidityDate(startDate: DateTime): DateTime = + val window = config.getString("exam.integration.courseUnitInfo.window") + val period = Period.parse(window) + startDate.minus(period) + override def getExamExpirationPeriod: String = config.getString("exam.exam.expiration.period") + override def isMaturitySupported: Boolean = DB + .find(classOf[ExamExecutionType]) + .where + .eq("type", ExamExecutionType.Type.MATURITY.toString) + .find + .nonEmpty + override def isPrintoutSupported: Boolean = DB + .find(classOf[ExamExecutionType]) + .where + .eq("type", ExamExecutionType.Type.PRINTOUT.toString) + .find + .nonEmpty + override def getAppVersion: String = config.getString("exam.release.version") + override def isAnonymousReviewEnabled: Boolean = config.getBoolean("exam.exam.anonymousReview") + override def getQuitExaminationLink: String = config.getString("exam.exam.seb.quitLink") + override def getExaminationAdminPassword: String = if (config.getBoolean("exam.exam.seb.adminPwd.randomize")) + UUID.randomUUID.toString + else config.getString("exam.exam.seb.adminPwd.value") + override def getSettingsPasswordEncryptionKey: String = config.getString("exam.exam.seb.settingsPwd.encryption.key") + override def getHomeOrganisationRef: String = config.getString("exam.integration.iop.organisationRef") + override def getMaxByodExaminationParticipantCount: Int = config.getInt("exam.byod.maxConcurrentParticipants") + override def isByodExamCreationPermissionGrantedForNewUsers: Boolean = + config.getBoolean("exam.byod.permission.allowed") + override def getCourseCodePrefix: String = config.getString("exam.course.code.prefix") + override def getIopHost: String = config.getString("exam.integration.iop.host") + override def isApiKeyUsed: Boolean = config.getBoolean("exam.integration.apiKey.enabled") + override def getApiKeyName: String = config.getString("exam.integration.apiKey.name") + override def getApiKeyValue: String = config.getString("exam.integration.apiKey.value") + override def getPermissionCheckUserIdentifier: String = + config.getString("exam.integration.enrolmentPermissionCheck.id") + override def getPermissionCheckUrl: String = config.getString("exam.integration.enrolmentPermissionCheck.url") + override def getBaseSystemUrl: String = config.getString("exam.baseSystemURL") + override def getSystemAccount: String = config.getString("exam.email.system.account") + override def getAttachmentPath: String = config.getString("exam.attachments.path") + override def getLoginType: String = config.getString("exam.login") + override def isMultiStudentIdEnabled: Boolean = config.getBoolean("exam.user.studentIds.multiple.enabled") + override def getMultiStudentOrganisations: String = config.getString("exam.user.studentIds.multiple.organisations") + override def getCsrfCookie: String = config.getString("play.filters.csrf.cookie.name") + override def getSupportedLanguages: List[String] = config.getStringList("play.i18n.langs").asScala.toList + override def hasPath(path: String): Boolean = config.hasPath(path) + override def getString(path: String): String = config.getString(path) diff --git a/app/miscellaneous/csv/CsvBuilderImpl.java b/app/miscellaneous/csv/CsvBuilderImpl.java index 7d5476f01..8a7dff3b0 100644 --- a/app/miscellaneous/csv/CsvBuilderImpl.java +++ b/app/miscellaneous/csv/CsvBuilderImpl.java @@ -87,8 +87,8 @@ public File build(JsonNode node) throws IOException { File file = File.createTempFile("csv-output-", ".tmp"); CSVWriter writer = new CSVWriter(new FileWriter(file)); writer.writeNext(getHeaders()); - StreamSupport.stream(node.spliterator(), false).forEach( - assessment -> writer.writeNext(values(assessment).toArray(String[]::new)) + StreamSupport.stream(node.spliterator(), false).forEach(assessment -> + writer.writeNext(values(assessment).toArray(String[]::new)) ); writer.close(); return file; diff --git a/app/models/questions/Question.java b/app/models/questions/Question.java index 0503a60ab..4e9ae576d 100644 --- a/app/models/questions/Question.java +++ b/app/models/questions/Question.java @@ -316,13 +316,12 @@ private boolean getClaimChoiceOptionsValidationResult(ArrayNode options) { !option.isEmpty()) ); }) - .map( - n -> - SanitizingHelper.parseEnum( - "claimChoiceType", - n, - MultipleChoiceOption.ClaimChoiceOptionType.class - ).orElse(null) + .map(n -> + SanitizingHelper.parseEnum( + "claimChoiceType", + n, + MultipleChoiceOption.ClaimChoiceOptionType.class + ).orElse(null) ) .filter(Objects::nonNull) .distinct() @@ -351,8 +350,8 @@ public Optional getValidationResult(JsonNode node) { if (an.size() < 2) { reason = "i18n_minimum_of_two_options_required"; } else if ( - StreamSupport.stream(an.spliterator(), false).noneMatch( - n -> n.get("correctOption").asBoolean() + StreamSupport.stream(an.spliterator(), false).noneMatch(n -> + n.get("correctOption").asBoolean() ) ) { reason = "i18n_correct_option_required"; diff --git a/app/sanitizers/CommaJoinedListSanitizer.java b/app/sanitizers/CommaJoinedListSanitizer.java index 86b02d7fd..4578b013e 100644 --- a/app/sanitizers/CommaJoinedListSanitizer.java +++ b/app/sanitizers/CommaJoinedListSanitizer.java @@ -12,9 +12,9 @@ public class CommaJoinedListSanitizer extends BaseSanitizer { protected Http.Request sanitize(Http.Request req, JsonNode body) throws SanitizingException { - String args = SanitizingHelper - .parse("ids", body, String.class) - .orElseThrow(() -> new SanitizingException("bad list")); + String args = SanitizingHelper.parse("ids", body, String.class).orElseThrow(() -> + new SanitizingException("bad list") + ); List ids = Arrays.stream(args.split(",")).map(Long::parseLong).toList(); if (ids.isEmpty()) { throw new SanitizingException("empty list"); diff --git a/app/sanitizers/CommentSanitizer.java b/app/sanitizers/CommentSanitizer.java index 9b6694e98..7ea74966e 100644 --- a/app/sanitizers/CommentSanitizer.java +++ b/app/sanitizers/CommentSanitizer.java @@ -12,8 +12,13 @@ public class CommentSanitizer extends BaseSanitizer { protected Http.Request sanitize(Http.Request req, JsonNode body) { Http.Request request = SanitizingHelper.sanitizeOptionalHtml("comment", body, Attrs.COMMENT, req); request = SanitizingHelper.sanitizeOptional("id", body, Long.class, Attrs.COMMENT_ID, request); - request = - SanitizingHelper.sanitizeOptional("feedbackStatus", body, Boolean.class, Attrs.FEEDBACK_STATUS, request); + request = SanitizingHelper.sanitizeOptional( + "feedbackStatus", + body, + Boolean.class, + Attrs.FEEDBACK_STATUS, + request + ); return request; } } diff --git a/app/sanitizers/ExamUpdateSanitizer.java b/app/sanitizers/ExamUpdateSanitizer.java index 2a2797575..6aeb0131d 100644 --- a/app/sanitizers/ExamUpdateSanitizer.java +++ b/app/sanitizers/ExamUpdateSanitizer.java @@ -130,14 +130,14 @@ protected Http.Request sanitize(Http.Request req, JsonNode body) throws Sanitizi } Grade grade = new Grade(); grade.setId( - SanitizingHelper.parse("id", evaluation.get("grade"), Integer.class).orElseThrow( - () -> new SanitizingException("invalid grade") + SanitizingHelper.parse("id", evaluation.get("grade"), Integer.class).orElseThrow(() -> + new SanitizingException("invalid grade") ) ); ge.setGrade(grade); ge.setPercentage( - SanitizingHelper.parse("percentage", evaluation, Integer.class).orElseThrow( - () -> new SanitizingException("no percentage") + SanitizingHelper.parse("percentage", evaluation, Integer.class).orElseThrow(() -> + new SanitizingException("no percentage") ) ); config.getGradeEvaluations().add(ge); diff --git a/app/sanitizers/SanitizingHelper.java b/app/sanitizers/SanitizingHelper.java index bf328ef5e..ec63e9161 100644 --- a/app/sanitizers/SanitizingHelper.java +++ b/app/sanitizers/SanitizingHelper.java @@ -13,8 +13,7 @@ public final class SanitizingHelper { - private static final Safelist SAFELIST = Safelist - .relaxed() + private static final Safelist SAFELIST = Safelist.relaxed() .addAttributes("a", "target") .addAttributes("span", "class", "id", "style", "case-sensitive", "cloze", "numeric", "precision") .addAttributes("table", "cellspacing", "cellpadding", "border", "style", "caption"); @@ -68,16 +67,18 @@ public static T parse(String fieldName, JsonNode node, Class type, T defa // Exception thrown if value is null or not found static Http.Request sanitize(String key, JsonNode node, Class type, TypedKey attr, Http.Request request) throws SanitizingException { - T value = parse(key, node, type) - .orElseThrow(() -> new SanitizingException("Missing or invalid data for key: " + key)); + T value = parse(key, node, type).orElseThrow(() -> + new SanitizingException("Missing or invalid data for key: " + key) + ); return request.addAttr(attr, value); } // Exception thrown if value is null or not found static Http.Request sanitizeHtml(String key, JsonNode node, TypedKey attr, Http.Request request) throws SanitizingException { - String value = parse(key, node, String.class) - .orElseThrow(() -> new SanitizingException("Missing or invalid data for key: " + key)); + String value = parse(key, node, String.class).orElseThrow(() -> + new SanitizingException("Missing or invalid data for key: " + key) + ); return request.addAttr(attr, Jsoup.clean(value, SAFELIST)); } diff --git a/app/sanitizers/SectionQuestionSanitizer.java b/app/sanitizers/SectionQuestionSanitizer.java index e1e8ac49d..8ac874937 100644 --- a/app/sanitizers/SectionQuestionSanitizer.java +++ b/app/sanitizers/SectionQuestionSanitizer.java @@ -18,8 +18,12 @@ protected Http.Request sanitize(Http.Request req, JsonNode body) { ); request = SanitizingHelper.sanitizeOptionalHtml("evaluationCriteria", body, Attrs.EVALUATION_CRITERIA, request); if (body.has("question")) { - request = - SanitizingHelper.sanitizeOptionalHtml("question", body.get("question"), Attrs.QUESTION_TEXT, request); + request = SanitizingHelper.sanitizeOptionalHtml( + "question", + body.get("question"), + Attrs.QUESTION_TEXT, + request + ); } return request; } diff --git a/test/controllers/CalendarControllerTest.java b/test/controllers/CalendarControllerTest.java index 42fab2085..65a46d11d 100644 --- a/test/controllers/CalendarControllerTest.java +++ b/test/controllers/CalendarControllerTest.java @@ -121,7 +121,8 @@ public void testConcurrentCreateReservation() throws Exception { ); status.add(result.status()); waiter.resume(); - }).start()); + }).start() + ); waiter.await(MAIL_TIMEOUT + 1000, callCount); assertThat(status).containsOnly(200); greenMail.purgeEmailFromAllMailboxes(); diff --git a/test/controllers/EnrolmentControllerTest.java b/test/controllers/EnrolmentControllerTest.java index 348b18044..c33fa4fb6 100644 --- a/test/controllers/EnrolmentControllerTest.java +++ b/test/controllers/EnrolmentControllerTest.java @@ -226,7 +226,8 @@ public void testConcurentCreateEnrolment() throws Exception { Json.newObject().put("code", exam.getCourse().getCode()) ); waiter.resume(); - }).start()); + }).start() + ); waiter.await(5000, callCount); final int count = DB.find(ExamEnrolment.class) diff --git a/test/controllers/integration/ExamAPIControllerTest.java b/test/controllers/integration/ExamAPIControllerTest.java index 093ffaecd..c397e1c13 100644 --- a/test/controllers/integration/ExamAPIControllerTest.java +++ b/test/controllers/integration/ExamAPIControllerTest.java @@ -83,8 +83,8 @@ public void testGetActiveExams() { assertThat(records).hasSize(exams.size() - 3); records .elements() - .forEachRemaining( - n -> assertThat(n.get("id").asLong()).isNotIn(second.getId(), third.getId(), fourth.getId()) + .forEachRemaining(n -> + assertThat(n.get("id").asLong()).isNotIn(second.getId(), third.getId(), fourth.getId()) ); String filter = DateTime.now().minusDays(3).toString("yyyy-MM-dd"); diff --git a/test/helpers/AttachmentServlet.java b/test/helpers/AttachmentServlet.java index 8e9760c99..0773bb2e0 100644 --- a/test/helpers/AttachmentServlet.java +++ b/test/helpers/AttachmentServlet.java @@ -23,8 +23,9 @@ public class AttachmentServlet extends BaseServlet { public AttachmentServlet() { final ClassLoader classLoader = AttachmentServlet.class.getClassLoader(); - this.testFile = - new File(Objects.requireNonNull(classLoader.getResource("test_files/test_image.png")).getFile()); + this.testFile = new File( + Objects.requireNonNull(classLoader.getResource("test_files/test_image.png")).getFile() + ); this.waiter = new Waiter(); } diff --git a/ui/src/app/exam/collaborative/collaborative-exam-listing.component.html b/ui/src/app/exam/collaborative/collaborative-exam-listing.component.html index 06a11c405..a14c86c35 100644 --- a/ui/src/app/exam/collaborative/collaborative-exam-listing.component.html +++ b/ui/src/app/exam/collaborative/collaborative-exam-listing.component.html @@ -137,7 +137,7 @@ {{ getStateTranslation(exam) }} diff --git a/ui/src/app/exam/exam.model.ts b/ui/src/app/exam/exam.model.ts index e2f933f0e..30521c46d 100644 --- a/ui/src/app/exam/exam.model.ts +++ b/ui/src/app/exam/exam.model.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2024 The members of the EXAM Consortium // -// SPDX-License-Identifier: EUPL-1.2import type { Organisation } from 'src/app/calendar/calendar.service'; +// SPDX-License-Identifier: EUPL-1.2 + import { Organisation } from 'src/app/calendar/calendar.service'; import type { ExamEnrolment } from 'src/app/enrolment/enrolment.model'; import type { LanguageInspection } from 'src/app/maturity/maturity.model'; diff --git a/ui/src/app/review/listing/categories/graded-logged.component.html b/ui/src/app/review/listing/categories/graded-logged.component.html index 5a4d22598..61fa16220 100644 --- a/ui/src/app/review/listing/categories/graded-logged.component.html +++ b/ui/src/app/review/listing/categories/graded-logged.component.html @@ -174,7 +174,7 @@ '/staff/assessments', exam.id, 'collaborative', - review.examParticipation._id + review.examParticipation._id, ]" > {{ review.displayName }} diff --git a/ui/src/app/review/listing/categories/graded.component.html b/ui/src/app/review/listing/categories/graded.component.html index 14ecfda35..6b761fe6f 100644 --- a/ui/src/app/review/listing/categories/graded.component.html +++ b/ui/src/app/review/listing/categories/graded.component.html @@ -184,7 +184,7 @@ '/staff/assessments', exam.id, 'collaborative', - review.examParticipation._id + review.examParticipation._id, ]" > {{ review.displayName }} diff --git a/ui/src/app/review/listing/categories/in-progress.component.html b/ui/src/app/review/listing/categories/in-progress.component.html index 95c945e15..2ab655247 100644 --- a/ui/src/app/review/listing/categories/in-progress.component.html +++ b/ui/src/app/review/listing/categories/in-progress.component.html @@ -136,7 +136,7 @@ '/staff/assessments', exam.id, 'collaborative', - review.examParticipation._id + review.examParticipation._id, ]" > {{ review.displayName }}