Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development: Enforce Usage of Optional<T> Over @RequestParam(required = false) in Method Parameters #9146

Closed
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
396408a
Add ArchTest to check method parameters for RequestParam annotation w…
pvlov Jul 28, 2024
f4b291d
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Jul 28, 2024
227743e
Remove unnecessary newline
pvlov Jul 28, 2024
4f0aaf8
add check if defaultValue is set
pvlov Jul 28, 2024
ce683bd
check if defaultValue is actually set
pvlov Jul 28, 2024
732f879
Use ValueConstants over copied literal
pvlov Jul 29, 2024
a84cea3
Added check for required set to false on Optional<T> param
pvlov Jul 29, 2024
aeff3e8
Fix some of the Violations
pvlov Jul 29, 2024
73798a9
Remove check for primitive classes
pvlov Jul 29, 2024
57ec751
Complete the fix of the Violations
pvlov Jul 29, 2024
028e4fb
fix test failures
pvlov Jul 30, 2024
43b2746
Fix last failing Tests, replace Min and Max with Range Annotation
pvlov Aug 5, 2024
3ccb9c0
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Aug 5, 2024
14943bc
Incorporate feedback: Refactor method and remove required = false whe…
pvlov Aug 6, 2024
1e6df05
Incorporate automatic reviews
pvlov Aug 6, 2024
2579c41
Adding changes from review
pvlov Aug 6, 2024
5581732
revert getComplaintByCourseId and fix param name
pvlov Aug 6, 2024
86fae49
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Aug 10, 2024
7ec6c45
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Aug 11, 2024
ca8ac55
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Aug 15, 2024
f314a86
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Aug 17, 2024
c6f0206
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Aug 20, 2024
3576f39
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Aug 21, 2024
a4b06a3
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Aug 26, 2024
cc8534f
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Aug 30, 2024
015634a
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Sep 8, 2024
19d6777
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Sep 20, 2024
90a7fb3
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Sep 26, 2024
0acbb3f
Merge remote-tracking branch 'upstream/develop' into chore/testing/ad…
pvlov Oct 7, 2024
133e730
Merge branch 'develop' into chore/testing/add-check-for-required-fals…
pvlov Oct 14, 2024
091e01f
doc: Add documentation for the use of Optionals to the server guidelines
pvlov Oct 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public BonusResource(BonusService bonusService, BonusRepository bonusRepository,
*/
@GetMapping("courses/{courseId}/exams/{examId}/bonus")
@EnforceAtLeastStudent
public ResponseEntity<Bonus> getBonusForExam(@PathVariable Long courseId, @PathVariable Long examId, @RequestParam(required = false) boolean includeSourceGradeSteps) {
public ResponseEntity<Bonus> getBonusForExam(@PathVariable Long courseId, @PathVariable Long examId, @RequestParam(defaultValue = "false") boolean includeSourceGradeSteps) {
log.debug("REST request to get bonus for exam: {}", examId);
examAccessService.checkCourseAndExamAccessForStudentElseThrow(courseId, examId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ public ResponseEntity<List<Complaint>> getComplaintsForTestRunDashboard(Principa
*/
@GetMapping(value = "complaints", params = { "courseId", "complaintType" })
@EnforceAtLeastTutor
public ResponseEntity<List<Complaint>> getComplaintsByCourseId(@RequestParam Long courseId, @RequestParam ComplaintType complaintType,
@RequestParam(required = false) Long tutorId, @RequestParam(required = false) boolean allComplaintsForTutor) {
public ResponseEntity<List<Complaint>> getComplaintsByCourseId(@RequestParam Long courseId, @RequestParam ComplaintType complaintType, @RequestParam Optional<Long> tutorId,
@RequestParam(defaultValue = "false") boolean allComplaintsForTutor) {
// Filtering by courseId
Course course = courseRepository.findByIdElseThrow(courseId);

Expand All @@ -224,11 +224,12 @@ public ResponseEntity<List<Complaint>> getComplaintsByCourseId(@RequestParam Lon
boolean isAtLeastInstructor = authCheckService.isAtLeastInstructorInCourse(course, user);

if (!isAtLeastInstructor) {
tutorId = user.getId();
tutorId = Optional.of(user.getId());
}

List<Complaint> complaints;

if (tutorId == null) {
if (tutorId.isEmpty()) {
complaints = complaintService.getAllComplaintsByCourseId(courseId);
filterOutUselessDataFromComplaints(complaints, !isAtLeastInstructor);
}
Expand All @@ -239,7 +240,7 @@ else if (allComplaintsForTutor) {
complaints.forEach(complaint -> complaint.filterForeignReviewer(user));
}
else {
complaints = complaintService.getAllComplaintsByCourseIdAndTutorId(courseId, tutorId);
complaints = complaintService.getAllComplaintsByCourseIdAndTutorId(courseId, tutorId.get());
filterOutUselessDataFromComplaints(complaints, !isAtLeastInstructor);
}

Expand All @@ -257,7 +258,7 @@ else if (allComplaintsForTutor) {
@GetMapping(value = "complaints", params = { "exerciseId", "complaintType" })
@EnforceAtLeastTutor
public ResponseEntity<List<Complaint>> getComplaintsByExerciseId(@RequestParam Long exerciseId, @RequestParam ComplaintType complaintType,
@RequestParam(required = false) Long tutorId) {
@RequestParam Optional<Long> tutorId) {
// Filtering by exerciseId
Exercise exercise = exerciseRepository.findByIdElseThrow(exerciseId);
User user = userRepository.getUserWithGroupsAndAuthorities();
Expand All @@ -267,19 +268,13 @@ public ResponseEntity<List<Complaint>> getComplaintsByExerciseId(@RequestParam L

// Only instructors can access all complaints about an exercise without filtering by tutorId
if (!isAtLeastInstructor) {
tutorId = userRepository.getUser().getId();
tutorId = Optional.of(userRepository.getUser().getId());
}

List<Complaint> complaints;
List<Complaint> complaints = tutorId.map(id -> complaintService.getAllComplaintsByExerciseIdAndTutorId(exerciseId, id))
.orElseGet(() -> complaintService.getAllComplaintsByExerciseId(exerciseId));

if (tutorId == null) {
complaints = complaintService.getAllComplaintsByExerciseId(exerciseId);
filterOutUselessDataFromComplaints(complaints, !isAtLeastInstructor);
}
else {
complaints = complaintService.getAllComplaintsByExerciseIdAndTutorId(exerciseId, tutorId);
filterOutUselessDataFromComplaints(complaints, !isAtLeastInstructor);
}
filterOutUselessDataFromComplaints(complaints, !isAtLeastInstructor);

return ResponseEntity.ok(getComplaintsByComplaintType(complaints, complaintType));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.time.ZonedDateTime;
import java.util.Optional;

import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Async;
Expand Down Expand Up @@ -37,7 +38,7 @@ public GroupNotificationScheduleService(SingleUserNotificationService singleUser
* @param exerciseAfterUpdate is the updated exercise (needed to check potential difference in release date)
* @param notificationText holds the custom change message for the notification process
*/
public void checkAndCreateAppropriateNotificationsWhenUpdatingExercise(Exercise exerciseBeforeUpdate, Exercise exerciseAfterUpdate, String notificationText) {
public void checkAndCreateAppropriateNotificationsWhenUpdatingExercise(Exercise exerciseBeforeUpdate, Exercise exerciseAfterUpdate, Optional<String> notificationText) {

// send exercise update notification
groupNotificationService.notifyAboutExerciseUpdate(exerciseAfterUpdate, notificationText);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -77,7 +78,7 @@ public GroupNotificationService(GroupNotificationRepository groupNotificationRep
* @param exercise that is updated
* @param notificationText that is used for the notification process
*/
public void notifyAboutExerciseUpdate(Exercise exercise, String notificationText) {
public void notifyAboutExerciseUpdate(Exercise exercise, Optional<String> notificationText) {

if (exercise.isExamExercise()) {
// Do not send an exercise-update notification if it's an exam exercise.
Expand All @@ -90,10 +91,8 @@ public void notifyAboutExerciseUpdate(Exercise exercise, String notificationText
return;
}

if (notificationText != null) {
// sends an exercise-update notification
notifyStudentAndEditorAndInstructorGroupAboutExerciseUpdate(exercise, notificationText);
}
// sends an exercise-update notification
notificationText.ifPresent(text -> notifyStudentAndEditorAndInstructorGroupAboutExerciseUpdate(exercise, text));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ public ResponseEntity<List<ResponsibleUserDTO>> getResponsibleUsersForCodeOfCond
@GetMapping("{courseId}/conversations/{conversationId}/members/search")
@EnforceAtLeastStudent
public ResponseEntity<List<ConversationUserDTO>> searchMembersOfConversation(@PathVariable Long courseId, @PathVariable Long conversationId,
@RequestParam("loginOrName") String loginOrName, @RequestParam(value = "filter", required = false) ConversationMemberSearchFilters filter, Pageable pageable) {
@RequestParam("loginOrName") String loginOrName, @RequestParam(value = "filter") Optional<ConversationMemberSearchFilters> filter, Pageable pageable) {
log.debug("REST request to get members of conversation : {} with login or name : {} in course: {}", conversationId, loginOrName, courseId);
if (pageable.getPageSize() > 20) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "The page size must not be greater than 20");
Expand All @@ -269,7 +269,7 @@ public ResponseEntity<List<ConversationUserDTO>> searchMembersOfConversation(@Pa
}
}
var searchTerm = loginOrName != null ? loginOrName.toLowerCase().trim() : "";
var originalPage = conversationService.searchMembersOfConversation(course, conversationFromDatabase, pageable, searchTerm, Optional.ofNullable(filter));
var originalPage = conversationService.searchMembersOfConversation(course, conversationFromDatabase, pageable, searchTerm, filter);

var resultDTO = new ArrayList<ConversationUserDTO>();
for (var user : originalPage) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -904,8 +904,8 @@ public boolean isAllowedToGetExamResult(Exercise exercise, StudentParticipation
* @return true if caller is allowed to assess submissions
*/
@CheckReturnValue
public boolean isAllowedToAssessExercise(Exercise exercise, User user, Long resultId) {
return this.isAtLeastTeachingAssistantForExercise(exercise, user) && (resultId == null || isAtLeastInstructorForExercise(exercise, user));
public boolean isAllowedToAssessExercise(Exercise exercise, User user, Optional<Long> resultId) {
return this.isAtLeastTeachingAssistantForExercise(exercise, user) && (resultId.isEmpty() || isAtLeastInstructorForExercise(exercise, user));
}

public void checkGivenExerciseIdSameForExerciseInRequestBodyElseThrow(Long exerciseId, Exercise exerciseInRequestBody) {
Expand All @@ -914,7 +914,7 @@ public void checkGivenExerciseIdSameForExerciseInRequestBodyElseThrow(Long exerc
}
}

public void checkIsAllowedToAssessExerciseElseThrow(Exercise exercise, User user, Long resultId) {
public void checkIsAllowedToAssessExerciseElseThrow(Exercise exercise, User user, Optional<Long> resultId) {
if (!isAllowedToAssessExercise(exercise, user, resultId)) {
throw new AccessForbiddenException("You are not allowed to assess this exercise!");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.util.Optional;

import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -141,21 +143,22 @@ public void createAndSendWorkingTimeUpdateEvent(StudentExam studentExam, int new
* @param instructor The user who performed the update
*/
@Async
public void createAndSendProblemStatementUpdateEvent(Exercise exercise, String message, User instructor) {
public void createAndSendProblemStatementUpdateEvent(Exercise exercise, Optional<String> message, User instructor) {
Exam exam = exercise.getExam();
studentExamRepository.findAllWithExercisesByExamId(exam.getId()).stream().filter(studentExam -> studentExam.getExercises().contains(exercise))
.forEach(studentExam -> this.createAndSendProblemStatementUpdateEvent(studentExam, exercise, message, instructor));
}

/**
* Send a problem statement update to the specified student.
* If the provided message is {@link Optional::empty}, the text content of the update will be set to {@link null}
*
* @param studentExam The student exam containing the exercise with updated problem statement
* @param exercise The updated exercise
* @param message The message to send
* @param sentBy The user who performed the update
*/
public void createAndSendProblemStatementUpdateEvent(StudentExam studentExam, Exercise exercise, String message, User sentBy) {
public void createAndSendProblemStatementUpdateEvent(StudentExam studentExam, Exercise exercise, Optional<String> message, User sentBy) {
pvlov marked this conversation as resolved.
Show resolved Hide resolved
var event = new ProblemStatementUpdateEvent();

// Common fields
Expand All @@ -164,7 +167,7 @@ public void createAndSendProblemStatementUpdateEvent(StudentExam studentExam, Ex
event.setCreatedBy(sentBy.getName());

// Specific fields
event.setTextContent(message);
event.setTextContent(message.orElse(null));
event.setProblemStatement(exercise.getProblemStatement());
event.setExerciseId(exercise.getId());
event.setExerciseName(exercise.getExerciseGroup().getTitle());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1305,19 +1305,16 @@ public ResponseEntity<Set<SuspiciousExamSessionsDTO>> getAllSuspiciousExamSessio
@RequestParam("differentStudentExamsSameBrowserFingerprint") boolean analyzeSessionsWithTheSameBrowserFingerprint,
@RequestParam("sameStudentExamDifferentIPAddresses") boolean analyzeSessionsForTheSameStudentExamWithDifferentIpAddresses,
@RequestParam("sameStudentExamDifferentBrowserFingerprints") boolean analyzeSessionsForTheSameStudentExamWithDifferentBrowserFingerprints,
@RequestParam("ipOutsideOfRange") boolean analyzeSessionsIpOutsideOfRange, @RequestParam(required = false) String ipSubnet) {
@RequestParam("ipOutsideOfRange") boolean analyzeSessionsIpOutsideOfRange, @RequestParam Optional<String> ipSubnet) {
log.debug("REST request to get all exam sessions that are suspicious for exam : {}", examId);
Course course = courseRepository.findByIdElseThrow(courseId);
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, course, null);
if (analyzeSessionsIpOutsideOfRange) {
if (ipSubnet == null) {
throw new BadRequestAlertException("If you want to analyze sessions with IP outside of range, you need to provide a subnet", ENTITY_NAME,
"missingLowerOrUpperBoundIp");
}
if (analyzeSessionsIpOutsideOfRange && ipSubnet.isEmpty()) {
throw new BadRequestAlertException("If you want to analyze sessions with IP outside of range, you need to provide a subnet", ENTITY_NAME, "missingLowerOrUpperBoundIp");
}
SuspiciousSessionsAnalysisOptions options = new SuspiciousSessionsAnalysisOptions(analyzeSessionsWithTheSameIp, analyzeSessionsWithTheSameBrowserFingerprint,
analyzeSessionsForTheSameStudentExamWithDifferentIpAddresses, analyzeSessionsForTheSameStudentExamWithDifferentBrowserFingerprints,
analyzeSessionsIpOutsideOfRange);
return ResponseEntity.ok(examSessionService.retrieveAllSuspiciousExamSessionsByExamId(examId, options, Optional.ofNullable(ipSubnet)));
return ResponseEntity.ok(examSessionService.retrieveAllSuspiciousExamSessionsByExamId(examId, options, ipSubnet));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -528,11 +529,11 @@ public ResponseEntity<StudentExam> getStudentExamForSummary(@PathVariable Long c
@GetMapping("courses/{courseId}/exams/{examId}/student-exams/{studentExamId}/grade-summary")
@EnforceAtLeastStudent
public ResponseEntity<StudentExamWithGradeDTO> getStudentExamGradesForSummary(@PathVariable long courseId, @PathVariable long examId, @PathVariable long studentExamId,
@RequestParam(required = false) Long userId) {
@RequestParam Optional<Long> userId) {
long start = System.currentTimeMillis();
User currentUser = userRepository.getUserWithGroupsAndAuthorities();
log.debug("REST request to get the student exam grades of user with id {} for exam {} by user {}", userId, examId, currentUser.getLogin());
User targetUser = userId == null ? currentUser : userRepository.findByIdWithGroupsAndAuthoritiesElseThrow(userId);
User targetUser = userId.map(userRepository::findByIdWithGroupsAndAuthoritiesElseThrow).orElse(currentUser);
StudentExam studentExam = findStudentExamWithExercisesElseThrow(targetUser, examId, courseId, studentExamId);

boolean isAtLeastInstructor = authorizationCheckService.isAtLeastInstructorInCourse(studentExam.getExam().getCourse(), currentUser);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -360,8 +361,8 @@ public void setExampleSubmission(Boolean exampleSubmission) {
* @param correctionRound for which not to remove results
* @param resultId specific resultId
*/
public void removeNotNeededResults(int correctionRound, Long resultId) {
if (correctionRound == 0 && resultId == null && getResults().size() >= 2) {
public void removeNotNeededResults(int correctionRound, Optional<Long> resultId) {
if (correctionRound == 0 && resultId.isEmpty() && getResults().size() >= 2) {
var resultList = new ArrayList<Result>();
resultList.add(getFirstManualResult());
setResults(resultList);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,9 @@ SELECT COUNT(DISTINCT team)
* @param teamOwnerId Optional user id by which to filter teams on their owner
* @return List of teams
*/
default List<Team> findAllByExerciseIdWithEagerStudents(Exercise exercise, Long teamOwnerId) {
if (teamOwnerId != null) {
return findAllByExerciseIdAndTeamOwnerIdWithEagerStudents(exercise.getId(), teamOwnerId);
}
else {
return findAllByExerciseIdWithEagerStudents(exercise.getId());
}
default List<Team> findAllByExerciseIdWithEagerStudents(Exercise exercise, Optional<Long> teamOwnerId) {
return teamOwnerId.map(id -> findAllByExerciseIdAndTeamOwnerIdWithEagerStudents(exercise.getId(), id))
.orElseGet(() -> findAllByExerciseIdWithEagerStudents(exercise.getId()));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,7 @@ public void removeCompetency(@NotNull Set<Exercise> exercises, @NotNull CourseCo
* @param updatedExercise the updated exercise
* @param notificationText custom notification text
*/
public void notifyAboutExerciseChanges(Exercise originalExercise, Exercise updatedExercise, String notificationText) {
public void notifyAboutExerciseChanges(Exercise originalExercise, Exercise updatedExercise, Optional<String> notificationText) {
if (originalExercise.isCourseExercise()) {
groupNotificationScheduleService.checkAndCreateAppropriateNotificationsWhenUpdatingExercise(originalExercise, updatedExercise, notificationText);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ public ResponseEntity<Team> getTeam(@PathVariable long exerciseId, @PathVariable
*/
@GetMapping("exercises/{exerciseId}/teams")
@EnforceAtLeastTutor
public ResponseEntity<List<Team>> getTeamsForExercise(@PathVariable long exerciseId, @RequestParam(value = "teamOwnerId", required = false) Long teamOwnerId) {
public ResponseEntity<List<Team>> getTeamsForExercise(@PathVariable long exerciseId, @RequestParam(value = "teamOwnerId") Optional<Long> teamOwnerId) {
log.debug("REST request to get all Teams for the exercise with id : {}", exerciseId);
Exercise exercise = exerciseRepository.findByIdElseThrow(exerciseId);
authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.TEACHING_ASSISTANT, exercise, null);
Expand Down
Loading
Loading