Skip to content

Commit 7793314

Browse files
committed
replace hazelcast instance with disrtibutedDataProvider
1 parent 3edf114 commit 7793314

2 files changed

Lines changed: 26 additions & 29 deletions

File tree

src/main/java/de/tum/cit/aet/artemis/localvc/service/LocalVCServletService.java

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
import java.nio.file.Files;
1212
import java.nio.file.Path;
1313
import java.security.MessageDigest;
14+
import java.time.Duration;
1415
import java.time.ZonedDateTime;
1516
import java.util.Base64;
1617
import java.util.List;
1718
import java.util.Map;
1819
import java.util.Objects;
1920
import java.util.Optional;
20-
import java.util.concurrent.TimeUnit;
2121
import java.util.regex.Pattern;
2222
import java.util.regex.PatternSyntaxException;
2323

@@ -46,9 +46,6 @@
4646
import org.springframework.stereotype.Service;
4747
import org.springframework.util.StringUtils;
4848

49-
import com.hazelcast.core.HazelcastInstance;
50-
import com.hazelcast.map.IMap;
51-
5249
import de.tum.cit.aet.artemis.account.domain.User;
5350
import de.tum.cit.aet.artemis.account.repository.UserRepository;
5451
import de.tum.cit.aet.artemis.admin.service.RateLimitService;
@@ -58,6 +55,8 @@
5855
import de.tum.cit.aet.artemis.core.security.RateLimitType;
5956
import de.tum.cit.aet.artemis.core.security.SecurityUtils;
6057
import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService;
58+
import de.tum.cit.aet.artemis.core.service.distributed.api.DistributedDataProvider;
59+
import de.tum.cit.aet.artemis.core.service.distributed.api.map.DistributedMap;
6160
import de.tum.cit.aet.artemis.core.util.TimeLogUtil;
6261
import de.tum.cit.aet.artemis.exercise.domain.Exercise;
6362
import de.tum.cit.aet.artemis.exercise.domain.participation.Participation;
@@ -140,7 +139,7 @@ public class LocalVCServletService {
140139

141140
private final MailSendingService mailSendingService;
142141

143-
private final HazelcastInstance hazelcastInstance;
142+
private final DistributedDataProvider distributedDataProvider;
144143

145144
@Value("${artemis.version-control.url}")
146145
private URI localVCBaseUri;
@@ -164,7 +163,7 @@ public LocalVCServletService(AuthenticationManager authenticationManager, UserRe
164163
ProgrammingSubmissionMessagingService programmingSubmissionMessagingService, ProgrammingExerciseTestCaseChangedService programmingExerciseTestCaseChangedService,
165164
ParticipationVCSAccessTokenRepository participationVCSAccessTokenRepository, RepositoryVCSAccessTokenRepository repositoryVCSAccessTokenRepository,
166165
Optional<VcsAccessLogService> vcsAccessLogService, AuthorizationCheckService authorizationCheckService, RateLimitService rateLimitService,
167-
ExerciseVersionService exerciseVersionService, MailSendingService mailSendingService, HazelcastInstance hazelcastInstance) {
166+
ExerciseVersionService exerciseVersionService, MailSendingService mailSendingService, DistributedDataProvider distributedDataProvider) {
168167
this.authenticationManager = authenticationManager;
169168
this.userRepository = userRepository;
170169
this.programmingExerciseRepository = programmingExerciseRepository;
@@ -182,7 +181,7 @@ public LocalVCServletService(AuthenticationManager authenticationManager, UserRe
182181
this.rateLimitService = rateLimitService;
183182
this.exerciseVersionService = exerciseVersionService;
184183
this.mailSendingService = mailSendingService;
185-
this.hazelcastInstance = hazelcastInstance;
184+
this.distributedDataProvider = distributedDataProvider;
186185
}
187186

188187
/**
@@ -1257,6 +1256,7 @@ public void updateAndStoreVCSAccessLogForCloneAndPullHTTPS(HttpServletRequest re
12571256
LocalVCRepositoryUri localVCRepositoryUri = parseRepositoryUri(request);
12581257
RepositoryActionType repositoryActionType = getRepositoryActionReadType(clientOffered);
12591258
AuthenticationMechanism mechanism = resolveHTTPSAuthenticationMechanism(authorizationHeader, user, localVCRepositoryUri);
1259+
// if user does a clone operation using a PASSWORD authentication, then system should notify user about better option
12601260
if (mechanism == AuthenticationMechanism.PASSWORD && repositoryActionType == RepositoryActionType.CLONE) {
12611261
checkAndSendHttpsCloneEmail(user);
12621262
}
@@ -1270,18 +1270,16 @@ public void updateAndStoreVCSAccessLogForCloneAndPullHTTPS(HttpServletRequest re
12701270

12711271
/**
12721272
* Sends an email tip about using Token/SSH authentication instead of HTTPS password,
1273-
* limited to at most once per 24 hours per user using Hazelcast cache.
1273+
* limited to at most once per 24 hours per user using distributed cache.
12741274
*
12751275
* @param user The user performing the clone operation.
12761276
*/
12771277
private void checkAndSendHttpsCloneEmail(User user) {
12781278
if (user.getEmail() == null || user.getEmail().isBlank()) {
12791279
return;
12801280
}
1281-
1282-
IMap<Long, Boolean> cache = hazelcastInstance.getMap(HTTPS_CLONE_EMAIL_CACHE);
1283-
// putIfAbsent returns null if cache is ampty
1284-
boolean isFirstTimeIn24Hours = cache.putIfAbsent(user.getId(), Boolean.TRUE, 24, TimeUnit.HOURS) == null;
1281+
DistributedMap<Long, Boolean> cache = distributedDataProvider.getExpiringMap(HTTPS_CLONE_EMAIL_CACHE, Duration.ofHours(24));
1282+
boolean isFirstTimeIn24Hours = cache.putIfAbsent(user.getId(), Boolean.TRUE, Duration.ofHours(24)) == null;
12851283
// If cache was empty then send an email
12861284
if (isFirstTimeIn24Hours) {
12871285
MailRecipientDTO mailRecipient = new MailRecipientDTO(user.getEmail(), user.getLangKey(), user.getLogin(), user.getFirstName(), user.getLastName(), null, null);

src/test/java/de/tum/cit/aet/artemis/localvc/service/LocalVCServletServiceTest.java

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static org.assertj.core.api.Assertions.assertThat;
44
import static org.mockito.ArgumentMatchers.any;
5-
import static org.mockito.ArgumentMatchers.anyLong;
65
import static org.mockito.ArgumentMatchers.anyString;
76
import static org.mockito.ArgumentMatchers.eq;
87
import static org.mockito.Mockito.lenient;
@@ -13,9 +12,9 @@
1312
import static org.mockito.Mockito.when;
1413

1514
import java.net.URI;
15+
import java.time.Duration;
1616
import java.util.Map;
1717
import java.util.Optional;
18-
import java.util.concurrent.TimeUnit;
1918

2019
import jakarta.servlet.http.HttpServletRequest;
2120

@@ -30,13 +29,12 @@
3029
import org.springframework.security.authentication.AuthenticationManager;
3130
import org.springframework.test.util.ReflectionTestUtils;
3231

33-
import com.hazelcast.core.HazelcastInstance;
34-
import com.hazelcast.map.IMap;
35-
3632
import de.tum.cit.aet.artemis.account.domain.User;
3733
import de.tum.cit.aet.artemis.account.test_repository.UserTestRepository;
3834
import de.tum.cit.aet.artemis.admin.service.RateLimitService;
3935
import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService;
36+
import de.tum.cit.aet.artemis.core.service.distributed.api.DistributedDataProvider;
37+
import de.tum.cit.aet.artemis.core.service.distributed.api.map.DistributedMap;
4038
import de.tum.cit.aet.artemis.course.domain.Course;
4139
import de.tum.cit.aet.artemis.exercise.service.ExerciseVersionService;
4240
import de.tum.cit.aet.artemis.localci.service.ci.ContinuousIntegrationTriggerService;
@@ -112,10 +110,10 @@ class LocalVCServletServiceTest {
112110
private MailSendingService mailSendingService;
113111

114112
@Mock
115-
private HazelcastInstance hazelcastInstance;
113+
private DistributedDataProvider distributedDataProvider;
116114

117115
@Mock
118-
private IMap<Long, Boolean> httpsCloneEmailCache;
116+
private DistributedMap<Long, Boolean> httpsCloneEmailCache;
119117

120118
@InjectMocks
121119
private LocalVCServletService localVCServletService;
@@ -155,10 +153,9 @@ void setUp() throws Exception {
155153
testRepositoryUri = mock(LocalVCRepositoryUri.class);
156154
// Use lenient() to avoid unnecessary stubbing errors for tests that don't use this mock
157155
lenient().when(testRepositoryUri.getRelativeRepositoryPath()).thenReturn(java.nio.file.Path.of("test/repo"));
158-
lenient().when(testRepositoryUri.toString()).thenReturn("http://localhost/git/TEST/EXERCISE-template.git");
156+
lenient().when(testRepositoryUri.toString()).thenReturn("http://localhost/git/TEST/test-template.git");
159157

160-
lenient().when(hazelcastInstance.getMap(anyString())).thenReturn((IMap) httpsCloneEmailCache);
161-
// lenient().when(hazelcastInstance.<Long, Boolean>getMap(LocalVCServletService.HTTPS_CLONE_EMAIL_CACHE)).thenReturn(httpsCloneEmailCache);
158+
lenient().when(distributedDataProvider.<Long, Boolean>getExpiringMap(anyString(), any())).thenReturn(httpsCloneEmailCache);
162159

163160
// Setup the VcsAccessLogService as an Optional containing the mock
164161
ReflectionTestUtils.setField(localVCServletService, "vcsAccessLogService", Optional.of(vcsAccessLogService));
@@ -286,7 +283,7 @@ void testResolveAuthenticationMechanismFromSessionOrRequest_withRepositoryToken(
286283

287284
RepositoryVCSAccessToken repositoryToken = new RepositoryVCSAccessToken();
288285
repositoryToken.setVcsAccessToken(token);
289-
when(repositoryVCSAccessTokenRepository.findByUserIdAndRepositoryUri(testUser.getId(), "http://localhost/git/TEST/EXERCISE-template.git"))
286+
when(repositoryVCSAccessTokenRepository.findByUserIdAndRepositoryUri(testUser.getId(), "http://localhost/git/TEST/test-template.git"))
290287
.thenReturn(Optional.of(repositoryToken));
291288

292289
AuthenticationContext.Request context = new AuthenticationContext.Request(request);
@@ -321,14 +318,14 @@ void testRepositoryActionTypeForFailedOperations() {
321318
void testUpdateAndStoreVCSAccessLogForCloneAndPullHTTPS_sendsEmailOnPasswordCloneWhenNotInCache() {
322319
HttpServletRequest request = mock(HttpServletRequest.class);
323320
when(request.getMethod()).thenReturn("POST");
324-
when(request.getRequestURI()).thenReturn("/git/TEST/EXERCISE-testuser.git/git-upload-pack");
321+
when(request.getRequestURI()).thenReturn("/git/TEST/test-testuser.git/git-upload-pack");
325322

326323
String password = "plain-password";
327324
String authHeader = "Basic " + java.util.Base64.getEncoder().encodeToString(("testuser:" + password).getBytes());
328325

329326
when(userRepository.findOneByLogin("testuser")).thenReturn(Optional.of(testUser));
330-
// putIfAbsent return null, first clone with HTTPS
331-
when(httpsCloneEmailCache.putIfAbsent(eq(testUser.getId()), eq(Boolean.TRUE), anyLong(), any(TimeUnit.class))).thenReturn(null);
327+
// putIfAbsent returns null on first insert
328+
when(httpsCloneEmailCache.putIfAbsent(eq(testUser.getId()), eq(Boolean.TRUE), any(Duration.class))).thenReturn(null);
332329

333330
// clientOffered == 0 means CLONE operation
334331
localVCServletService.updateAndStoreVCSAccessLogForCloneAndPullHTTPS(request, authHeader, 0);
@@ -340,12 +337,14 @@ void testUpdateAndStoreVCSAccessLogForCloneAndPullHTTPS_sendsEmailOnPasswordClon
340337
void testUpdateAndStoreVCSAccessLogForCloneAndPullHTTPS_doesNotSendEmailWhenAlreadyInCache() {
341338
HttpServletRequest request = mock(HttpServletRequest.class);
342339
when(request.getMethod()).thenReturn("POST");
343-
when(request.getRequestURI()).thenReturn("/git/TEST/EXERCISE-testuser.git/git-upload-pack");
340+
when(request.getRequestURI()).thenReturn("/git/TEST/test-testuser.git/git-upload-pack");
344341

345342
String password = "plain-password";
346343
String authHeader = "Basic " + java.util.Base64.getEncoder().encodeToString(("testuser:" + password).getBytes());
347344

348345
when(userRepository.findOneByLogin("testuser")).thenReturn(Optional.of(testUser));
346+
// putIfAbsent returns existing entry (Boolean.TRUE) when already present
347+
when(httpsCloneEmailCache.putIfAbsent(eq(testUser.getId()), eq(Boolean.TRUE), any(Duration.class))).thenReturn(Boolean.TRUE);
349348

350349
localVCServletService.updateAndStoreVCSAccessLogForCloneAndPullHTTPS(request, authHeader, 0);
351350

@@ -356,7 +355,7 @@ void testUpdateAndStoreVCSAccessLogForCloneAndPullHTTPS_doesNotSendEmailWhenAlre
356355
void testUpdateAndStoreVCSAccessLogForCloneAndPullHTTPS_doesNotSendEmailWhenUsingToken() {
357356
HttpServletRequest request = mock(HttpServletRequest.class);
358357
when(request.getMethod()).thenReturn("POST");
359-
when(request.getRequestURI()).thenReturn("/git/TEST/EXERCISE-testuser.git/git-upload-pack");
358+
when(request.getRequestURI()).thenReturn("/git/TEST/test-testuser.git/git-upload-pack");
360359

361360
String token = "vcpat-" + "a".repeat(44);
362361
String authHeader = "Basic " + java.util.Base64.getEncoder().encodeToString(("testuser:" + token).getBytes());
@@ -373,7 +372,7 @@ void testUpdateAndStoreVCSAccessLogForCloneAndPullHTTPS_doesNotSendEmailWhenUsin
373372
void testUpdateAndStoreVCSAccessLogForCloneAndPullHTTPS_doesNotSendEmailOnPullOperation() {
374373
HttpServletRequest request = mock(HttpServletRequest.class);
375374
when(request.getMethod()).thenReturn("POST");
376-
when(request.getRequestURI()).thenReturn("/git/TEST/EXERCISE-testuser.git/git-upload-pack");
375+
when(request.getRequestURI()).thenReturn("/git/TEST/test-testuser.git/git-upload-pack");
377376

378377
String password = "plain-password";
379378
String authHeader = "Basic " + java.util.Base64.getEncoder().encodeToString(("testuser:" + password).getBytes());

0 commit comments

Comments
 (0)