Skip to content

Commit 029b30f

Browse files
committed
SEBSERV-594
1 parent 07b3a05 commit 029b30f

13 files changed

+214
-164
lines changed

src/main/java/ch/ethz/seb/sps/server/ServiceConfig.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,9 @@
1010

1111
import java.util.concurrent.Executor;
1212

13-
import javax.sql.DataSource;
14-
1513
import org.springframework.context.annotation.Bean;
1614
import org.springframework.context.annotation.Configuration;
1715
import org.springframework.context.annotation.Lazy;
18-
import org.springframework.context.annotation.Primary;
1916
import org.springframework.scheduling.TaskScheduler;
2017
import org.springframework.scheduling.annotation.EnableAsync;
2118
import org.springframework.scheduling.annotation.EnableScheduling;
@@ -34,6 +31,7 @@ public class ServiceConfig {
3431
public static final String SCREENSHOT_UPLOAD_API_EXECUTOR = "SCREENSHOT_UPLOAD_API_EXECUTOR";
3532
public static final String SCREENSHOT_DOWNLOAD_API_EXECUTOR = "SCREENSHOT_DOWNLOAD_API_EXECUTOR";
3633
public static final String SCREENSHOT_STORE_API_EXECUTOR = "SCREENSHOT_STORE_API_EXECUTOR";
34+
public static final String SYSTEM_SCHEDULER = "SYSTEM_SCHEDULER";
3735

3836
@Lazy
3937
@Bean
@@ -74,7 +72,7 @@ public Executor screenhortDownloadThreadPoolTaskExecutor() {
7472
}
7573

7674
@Bean(name = SCREENSHOT_STORE_API_EXECUTOR)
77-
public TaskScheduler batchStoreScreenShotcheduler() {
75+
public TaskScheduler batchStoreScreenScheduler() {
7876
final ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
7977

8078
scheduler.setPoolSize(4);
@@ -85,4 +83,16 @@ public TaskScheduler batchStoreScreenShotcheduler() {
8583
return scheduler;
8684
}
8785

86+
@Bean(name = SYSTEM_SCHEDULER)
87+
public TaskScheduler systemScheduler() {
88+
final ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
89+
90+
scheduler.setPoolSize(4);
91+
scheduler.setThreadNamePrefix("system-");
92+
scheduler.setThreadPriority(Thread.NORM_PRIORITY);
93+
scheduler.setDaemon(true);
94+
95+
return scheduler;
96+
}
97+
8898
}

src/main/java/ch/ethz/seb/sps/server/ServiceUpdateTask.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
package ch.ethz.seb.sps.server;
1010

11+
import static ch.ethz.seb.sps.server.ServiceConfig.SYSTEM_SCHEDULER;
12+
1113
import org.springframework.beans.factory.DisposableBean;
1214
import org.springframework.beans.factory.annotation.Value;
1315
import org.springframework.context.annotation.Lazy;
@@ -47,20 +49,15 @@ private void init() {
4749

4850
@Scheduled(
4951
fixedDelayString = "${sps.webservice.distributed.update:15000}",
50-
initialDelay = 5000)
52+
initialDelay = 5000,
53+
scheduler = SYSTEM_SCHEDULER)
5154
private void examSessionUpdateTask() {
52-
5355
this.serviceInfo.updateMaster();
54-
55-
// ServiceInit.INIT_LOGGER.info("--------> Service Health: {}",
56-
// this.sessionServiceHealthControl.getOverallLoadIndicator());
57-
5856
}
5957

6058
@Override
6159
public void destroy() throws Exception {
62-
// TODO Auto-generated method stub
63-
60+
ServiceInit.INIT_LOGGER.info("-----> Should down SPS Server...");
6461
}
6562

6663
}

src/main/java/ch/ethz/seb/sps/server/datalayer/dao/ExamDAO.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam> {
1515
boolean isExamRunning(Long examId);
1616

1717
Result<Collection<Exam>> getExamsStarted(final FilterMap filterMap);
18+
19+
Result<Collection<Long>> getAllForDeletion();
20+
21+
boolean hasRunningLifeExams();
1822
}

src/main/java/ch/ethz/seb/sps/server/datalayer/dao/impl/ExamDAOBatis.java

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,49 @@ public Result<Collection<Exam>> getExamsStarted(final FilterMap filterMap) {
201201
});
202202
}
203203

204+
@Override
205+
@Transactional(readOnly = true)
206+
public Result<Collection<Long>> getAllForDeletion() {
207+
return Result.tryCatch(() -> {
208+
long now = Utils.getMillisecondsNow();
209+
return this.examRecordMapper
210+
.selectIdsByExample()
211+
.where(
212+
ExamRecordDynamicSqlSupport.deletionTime,
213+
SqlBuilder.isNotNull())
214+
.and(
215+
ExamRecordDynamicSqlSupport.deletionTime,
216+
SqlBuilder.isLessThanOrEqualTo(now))
217+
.build()
218+
.execute();
219+
});
220+
}
221+
222+
@Override
223+
@Transactional(readOnly = true)
224+
public boolean hasRunningLifeExams() {
225+
try {
226+
long now = Utils.getMillisecondsNow();
227+
return !this.examRecordMapper
228+
.selectByExample()
229+
.where(
230+
ExamRecordDynamicSqlSupport.endTime,
231+
SqlBuilder.isNotNull())
232+
.and(ExamRecordDynamicSqlSupport.endTime,
233+
SqlBuilder.isGreaterThan(now))
234+
.and(ExamRecordDynamicSqlSupport.startTime,
235+
SqlBuilder.isLessThanOrEqualTo((now)))
236+
.build()
237+
.execute()
238+
.isEmpty();
239+
240+
} catch (Exception e) {
241+
log.error("Failed to check if any running life exam exists: ", e);
242+
// If we are not sure if at least one life running exam exists, we assume it does.
243+
return true;
244+
}
245+
}
246+
204247
@Override
205248
@Transactional(readOnly = true)
206249
public Result<Set<Long>> getAllOwnedIds(final String userUUID) {
@@ -331,17 +374,6 @@ public Result<Exam> createNew(final Exam data) {
331374

332375
this.examRecordMapper.insert(newRecord);
333376

334-
// if (!data.userIds.isEmpty()) {
335-
//
336-
// // save new user ids
337-
// this.additionalAttributesDAO.saveAdditionalAttribute(
338-
// EntityType.EXAM,
339-
// newRecord.getId(),
340-
// Exam.ATTR_USER_IDS,
341-
// StringUtils.join(data.userIds, Constants.LIST_SEPARATOR)
342-
// ).onError(error -> log.warn("Failed to store exam user ids: {}", data.userIds, error));
343-
// }
344-
345377
return this.examRecordMapper.selectByPrimaryKey(newRecord.getId());
346378
})
347379
.map(this::toDomainModel)
@@ -380,23 +412,7 @@ public Result<Exam> save(final Exam data) {
380412
.where(ExamRecordDynamicSqlSupport.id, isEqualTo(pk))
381413
.build()
382414
.execute();
383-
384-
// if (!data.userIds.isEmpty()) {
385-
// // delete old user ids
386-
// this.additionalAttributesDAO.delete(
387-
// EntityType.EXAM,
388-
// pk,
389-
// Exam.ATTR_USER_IDS);
390-
//
391-
// // save new user ids
392-
// this.additionalAttributesDAO.saveAdditionalAttribute(
393-
// EntityType.EXAM,
394-
// pk,
395-
// Exam.ATTR_USER_IDS,
396-
// StringUtils.join(data.userIds, Constants.LIST_SEPARATOR)
397-
// ).onError(error -> log.warn("Failed to store exam user ids: {}", data.userIds, error));
398-
// }
399-
415+
400416
return this.examRecordMapper.selectByPrimaryKey(pk);
401417
})
402418
.map(this::toDomainModel)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright (c) 2024 ETH Zürich, IT Services
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
7+
*/
8+
9+
package ch.ethz.seb.sps.server.servicelayer;
10+
11+
import ch.ethz.seb.sps.server.ServiceInitEvent;
12+
import org.springframework.context.event.EventListener;
13+
14+
public interface AutomatedDeletionService {
15+
16+
@EventListener(ServiceInitEvent.class)
17+
void init();
18+
19+
}

src/main/java/ch/ethz/seb/sps/server/servicelayer/ScreenshotStoreService.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ void storeScreenshot(
2424
String metadata,
2525
InputStream in);
2626

27-
void storeScreenshot(String sessionUUID, InputStream in);
28-
2927
@EventListener(ServiceInitEvent.class)
3028
void init();
3129

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright (c) 2024 ETH Zürich, IT Services
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
7+
*/
8+
9+
package ch.ethz.seb.sps.server.servicelayer.impl;
10+
11+
import java.util.Collections;
12+
import java.util.HashSet;
13+
import java.util.Set;
14+
15+
import ch.ethz.seb.sps.server.ServiceConfig;
16+
import ch.ethz.seb.sps.server.datalayer.dao.ExamDAO;
17+
import ch.ethz.seb.sps.server.servicelayer.AutomatedDeletionService;
18+
import ch.ethz.seb.sps.utils.Constants;
19+
import ch.ethz.seb.sps.utils.Utils;
20+
import org.joda.time.DateTime;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
import org.springframework.beans.factory.annotation.Qualifier;
24+
import org.springframework.beans.factory.annotation.Value;
25+
import org.springframework.scheduling.TaskScheduler;
26+
import org.springframework.stereotype.Component;
27+
28+
@Component
29+
public class AutomatedDeletionServiceImpl implements AutomatedDeletionService {
30+
31+
private static final Logger log = LoggerFactory.getLogger(AutomatedDeletionServiceImpl.class);
32+
33+
34+
35+
private final ExamDAO examDAO;
36+
private final TaskScheduler taskScheduler;
37+
private final int APPLY_DELETION_AFTER_HOUR_OF_DAY;
38+
private final int APPLY_DELETION_BEFORE_HOUR_OF_DAY;
39+
40+
private final Set<Long> toDelete = new HashSet<>();
41+
42+
public AutomatedDeletionServiceImpl(
43+
final ExamDAO examDAO,
44+
@Qualifier(value = ServiceConfig.SYSTEM_SCHEDULER) final TaskScheduler taskScheduler,
45+
@Value("${sps.data.autodelete.only.after.hour.utc:0}") final int APPLY_DELETION_AFTER_HOUR_OF_DAY,
46+
@Value("${sps.data.autodelete.only.after.hour.utc:6}") final int APPLY_DELETION_BEFORE_HOUR_OF_DAY) {
47+
48+
this.examDAO = examDAO;
49+
this.taskScheduler = taskScheduler;
50+
this.APPLY_DELETION_AFTER_HOUR_OF_DAY = APPLY_DELETION_AFTER_HOUR_OF_DAY;
51+
this.APPLY_DELETION_BEFORE_HOUR_OF_DAY = APPLY_DELETION_BEFORE_HOUR_OF_DAY;
52+
}
53+
54+
@Override
55+
public void init() {
56+
// triggered every hour...
57+
this.taskScheduler.scheduleWithFixedDelay(
58+
this::update,
59+
java.time.Duration.ofMillis(Constants.HOUR_IN_MILLIS));
60+
}
61+
62+
private void update() {
63+
try {
64+
65+
if (log.isDebugEnabled()) {
66+
log.debug("Process Exam auto-delete check");
67+
}
68+
69+
toDelete.addAll(examDAO.getAllForDeletion().getOr(Collections.emptyList()));
70+
71+
if (!toDelete.isEmpty()) {
72+
DateTime now = Utils.toDateTimeUTC(Utils.getMillisecondsNow());
73+
74+
// processed only in the morning hours (UTC)
75+
if (now.hourOfDay().get() < APPLY_DELETION_BEFORE_HOUR_OF_DAY) {
76+
processDeletion();
77+
}
78+
}
79+
80+
} catch (Exception e) {
81+
log.error("Failed to update automated Exam deletion: {}", e.getMessage());
82+
}
83+
}
84+
85+
private void processDeletion() {
86+
try {
87+
88+
new HashSet<>(toDelete).forEach(id -> {
89+
90+
log.info("Automatically delete Exam: {}", id);
91+
92+
examDAO
93+
.delete(String.valueOf(id))
94+
.onError(error -> log.error(
95+
"Failed to delete Exam on automated deletion: {}",
96+
error.getMessage()))
97+
.onSuccess(key -> toDelete.remove(id));
98+
});
99+
100+
} catch (Exception e) {
101+
log.error("Failed to delete Exam on automated deletion: {}", e.getMessage());
102+
}
103+
}
104+
}

src/main/java/ch/ethz/seb/sps/server/servicelayer/impl/BeanValidationServiceImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
/** This service can be used to 'manually' validate a Bean that is annotated within bean
2929
* validation annotations.
30-
*
30+
* <p>
3131
* On validation error BeanValidationException is used to collect all validation issues
3232
* and report them within the Result. */
3333
@Service

0 commit comments

Comments
 (0)