Merge remote-tracking branch 'origin/dev-1.2' into development
Conflicts: pom.xml src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java
This commit is contained in:
commit
3349d67d5a
22 changed files with 193 additions and 92 deletions
|
@ -686,7 +686,11 @@ public final class Utils {
|
||||||
if (StringUtils.isBlank(text)) {
|
if (StringUtils.isBlank(text)) {
|
||||||
return StringUtils.EMPTY;
|
return StringUtils.EMPTY;
|
||||||
}
|
}
|
||||||
return Constants.DOUBLE_QUOTE + text.replace("\"", "\"\"") + Constants.DOUBLE_QUOTE;
|
return Constants.DOUBLE_QUOTE +
|
||||||
|
text
|
||||||
|
.replace("\r\n", "\n")
|
||||||
|
.replace("\"", "\"\"")
|
||||||
|
+ Constants.DOUBLE_QUOTE;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,26 +55,32 @@ public class SEBClientLogExport extends AbstractDownloadServiceHandler {
|
||||||
queryParams.add(param, String.valueOf(request.getParameter(param)));
|
queryParams.add(param, String.valueOf(request.getParameter(param)));
|
||||||
}
|
}
|
||||||
|
|
||||||
final InputStream input = this.restService
|
this.restService
|
||||||
.getBuilder(ExportSEBClientLogs.class)
|
.getBuilder(ExportSEBClientLogs.class)
|
||||||
|
.withResponseExtractor(response -> {
|
||||||
|
|
||||||
|
try {
|
||||||
|
final InputStream input = response.getBody();
|
||||||
|
IOUtils.copyLarge(input, downloadOut);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
log.error(
|
||||||
|
"Unexpected error while streaming incoming config data from web-service to output-stream of download response: ",
|
||||||
|
e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
downloadOut.flush();
|
||||||
|
downloadOut.close();
|
||||||
|
} catch (final IOException e) {
|
||||||
|
log.error("Unexpected error while trying to close download output-stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
.withQueryParams(queryParams)
|
.withQueryParams(queryParams)
|
||||||
.call()
|
.call()
|
||||||
.getOrThrow();
|
.onError(error -> log.error("Download failed: ", error));
|
||||||
|
|
||||||
try {
|
|
||||||
IOUtils.copyLarge(input, downloadOut);
|
|
||||||
} catch (final IOException e) {
|
|
||||||
log.error(
|
|
||||||
"Unexpected error while streaming incoming config data from web-service to output-stream of download response: ",
|
|
||||||
e);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
downloadOut.flush();
|
|
||||||
downloadOut.close();
|
|
||||||
} catch (final IOException e) {
|
|
||||||
log.error("Unexpected error while trying to close download output-stream");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.client.ClientHttpRequest;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
|
||||||
|
public class AbstractDownloadCall extends RestCall<Boolean> {
|
||||||
|
|
||||||
|
protected AbstractDownloadCall(
|
||||||
|
final MediaType contentType,
|
||||||
|
final String path) {
|
||||||
|
|
||||||
|
super(new RestCall.TypeKey<>(CallType.UNDEFINED, null, new TypeReference<Boolean>() {
|
||||||
|
}), HttpMethod.GET, contentType, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Result<Boolean> exchange(final RestCallBuilder builder) {
|
||||||
|
|
||||||
|
return Result.tryCatch(() -> builder
|
||||||
|
.getRestTemplate()
|
||||||
|
.execute(
|
||||||
|
builder.buildURI(),
|
||||||
|
this.httpMethod,
|
||||||
|
(final ClientHttpRequest requestCallback) -> {
|
||||||
|
},
|
||||||
|
builder.getResponseExtractor(),
|
||||||
|
builder.getURIVariables()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import org.springframework.http.client.ClientHttpRequest;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
|
||||||
|
@Deprecated // This is not streaming correctly. Use AbstractDownloadCall instead
|
||||||
public abstract class AbstractExportCall extends RestCall<InputStream> {
|
public abstract class AbstractExportCall extends RestCall<InputStream> {
|
||||||
|
|
||||||
protected AbstractExportCall(
|
protected AbstractExportCall(
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.client.ResourceAccessException;
|
import org.springframework.web.client.ResourceAccessException;
|
||||||
|
import org.springframework.web.client.ResponseExtractor;
|
||||||
import org.springframework.web.client.RestClientResponseException;
|
import org.springframework.web.client.RestClientResponseException;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
@ -224,6 +225,7 @@ public abstract class RestCall<T> {
|
||||||
private final HttpHeaders httpHeaders;
|
private final HttpHeaders httpHeaders;
|
||||||
private String body = null;
|
private String body = null;
|
||||||
private InputStream streamingBody = null;
|
private InputStream streamingBody = null;
|
||||||
|
private ResponseExtractor<Boolean> responseExtractor = null;
|
||||||
|
|
||||||
private final MultiValueMap<String, String> queryParams;
|
private final MultiValueMap<String, String> queryParams;
|
||||||
private final Map<String, String> uriVariables;
|
private final Map<String, String> uriVariables;
|
||||||
|
@ -253,6 +255,15 @@ public abstract class RestCall<T> {
|
||||||
return this.restTemplate;
|
return this.restTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RestCallBuilder withResponseExtractor(final ResponseExtractor<Boolean> responseExtractor) {
|
||||||
|
this.responseExtractor = responseExtractor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseExtractor<Boolean> getResponseExtractor() {
|
||||||
|
return this.responseExtractor;
|
||||||
|
}
|
||||||
|
|
||||||
public RestCallBuilder withRestTemplate(final RestTemplate restTemplate) {
|
public RestCallBuilder withRestTemplate(final RestTemplate restTemplate) {
|
||||||
this.restTemplate = restTemplate;
|
this.restTemplate = restTemplate;
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -8,33 +8,21 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam;
|
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam;
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.AbstractExportCall;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.AbstractDownloadCall;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Component
|
@Component
|
||||||
@GuiProfile
|
@GuiProfile
|
||||||
public class ExportSEBClientLogs extends AbstractExportCall {
|
public class ExportSEBClientLogs extends AbstractDownloadCall {
|
||||||
|
|
||||||
public ExportSEBClientLogs() {
|
public ExportSEBClientLogs() {
|
||||||
super(new TypeKey<>(
|
super(MediaType.APPLICATION_FORM_URLENCODED,
|
||||||
CallType.UNDEFINED,
|
|
||||||
EntityType.CLIENT_EVENT,
|
|
||||||
new TypeReference<InputStream>() {
|
|
||||||
}),
|
|
||||||
HttpMethod.GET,
|
|
||||||
MediaType.APPLICATION_FORM_URLENCODED,
|
|
||||||
API.SEB_CLIENT_EVENT_ENDPOINT
|
API.SEB_CLIENT_EVENT_ENDPOINT
|
||||||
+ API.SEB_CLIENT_EVENT_EXPORT_PATH_SEGMENT);
|
+ API.SEB_CLIENT_EVENT_EXPORT_PATH_SEGMENT);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,17 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice;
|
package ch.ethz.seb.sebserver.webservice;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.SEBServerInit;
|
import ch.ethz.seb.sebserver.SEBServerInit;
|
||||||
import ch.ethz.seb.sebserver.WebSecurityConfig;
|
import ch.ethz.seb.sebserver.WebSecurityConfig;
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialServiceImpl;
|
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialServiceImpl;
|
||||||
|
@ -20,16 +31,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServe
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@WebServiceProfile
|
@WebServiceProfile
|
||||||
|
@ -69,7 +70,7 @@ class AdminUserInitializer {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
log.debug("Create initial admin account is switched on. Check database if exists...");
|
log.debug("Create initial admin account is switched on. Check database if exists...");
|
||||||
final Result<SEBServerUser> byUsername = this.userDAO.sebServerUserByUsername(this.adminName);
|
final Result<SEBServerUser> byUsername = this.userDAO.sebServerAdminByUsername(this.adminName);
|
||||||
if (byUsername.hasValue()) {
|
if (byUsername.hasValue()) {
|
||||||
|
|
||||||
log.debug("Initial admin account already exists. Check if the password must be reset...");
|
log.debug("Initial admin account already exists. Check if the password must be reset...");
|
||||||
|
@ -130,7 +131,7 @@ class AdminUserInitializer {
|
||||||
return account;
|
return account;
|
||||||
})
|
})
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
SEBServerInit.INIT_LOGGER.error("---->");
|
SEBServerInit.INIT_LOGGER.error("---->");
|
||||||
SEBServerInit.INIT_LOGGER.error("----> SEB Server initial admin-account creation failed: ", e);
|
SEBServerInit.INIT_LOGGER.error("----> SEB Server initial admin-account creation failed: ", e);
|
||||||
|
|
|
@ -94,7 +94,7 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
|
||||||
SEBServerInit.INIT_LOGGER.info("------> Ping update time: {}",
|
SEBServerInit.INIT_LOGGER.info("------> Ping update time: {}",
|
||||||
this.environment.getProperty("sebserver.webservice.distributed.pingUpdate"));
|
this.environment.getProperty("sebserver.webservice.distributed.pingUpdate"));
|
||||||
SEBServerInit.INIT_LOGGER.info("------> Connection update time: {}",
|
SEBServerInit.INIT_LOGGER.info("------> Connection update time: {}",
|
||||||
this.environment.getProperty("sebserver.webservice.distributed.connectionUpdate"));
|
this.environment.getProperty("sebserver.webservice.distributed.connectionUpdate", "2000"));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -49,6 +49,12 @@ public interface UserDAO extends ActivatableEntityDAO<UserInfo, UserMod>, BulkAc
|
||||||
* @return a Result of SEBServerUser for specified username. Or an exception result on error case */
|
* @return a Result of SEBServerUser for specified username. Or an exception result on error case */
|
||||||
Result<SEBServerUser> sebServerUserByUsername(String username);
|
Result<SEBServerUser> sebServerUserByUsername(String username);
|
||||||
|
|
||||||
|
/** Use this to get the SEBServerUser admin principal for a given username.
|
||||||
|
*
|
||||||
|
* @param username The username of the user to get SEBServerUser from
|
||||||
|
* @return a Result of SEBServerUser for specified username. Or an exception result on error case */
|
||||||
|
Result<SEBServerUser> sebServerAdminByUsername(String username);
|
||||||
|
|
||||||
/** Use this to get a Collection containing EntityKey's of all entities that belongs to a given User.
|
/** Use this to get a Collection containing EntityKey's of all entities that belongs to a given User.
|
||||||
*
|
*
|
||||||
* @param uuid The UUID of the user
|
* @param uuid The UUID of the user
|
||||||
|
|
|
@ -127,6 +127,21 @@ public class UserDAOImpl implements UserDAO {
|
||||||
.flatMap(this::sebServerUserFromRecord);
|
.flatMap(this::sebServerUserFromRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<SEBServerUser> sebServerAdminByUsername(final String username) {
|
||||||
|
return getSingleResource(
|
||||||
|
username,
|
||||||
|
this.userRecordMapper
|
||||||
|
.selectByExample()
|
||||||
|
.where(UserRecordDynamicSqlSupport.username, isEqualTo(username))
|
||||||
|
.and(UserRecordDynamicSqlSupport.active,
|
||||||
|
isEqualTo(BooleanUtils.toInteger(true)))
|
||||||
|
.build()
|
||||||
|
.execute())
|
||||||
|
.flatMap(this::sebServerUserFromRecord);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Result<Collection<UserInfo>> all(final Long institutionId, final Boolean active) {
|
public Result<Collection<UserInfo>> all(final Long institutionId, final Boolean active) {
|
||||||
|
|
|
@ -242,7 +242,9 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
examId,
|
examId,
|
||||||
ProctoringServiceSettings.ATTR_ENABLE_PROCTORING)
|
ProctoringServiceSettings.ATTR_ENABLE_PROCTORING)
|
||||||
.map(rec -> BooleanUtils.toBoolean(rec.getValue()))
|
.map(rec -> BooleanUtils.toBoolean(rec.getValue()))
|
||||||
.onError(error -> log.error("Failed to verify proctoring enabled for exam: {}", examId, error));
|
.onError(error -> log.warn("Failed to verify proctoring enabled for exam: {}, {}",
|
||||||
|
examId,
|
||||||
|
error.getMessage()));
|
||||||
if (result.hasError()) {
|
if (result.hasError()) {
|
||||||
return Result.of(false);
|
return Result.of(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,7 +228,7 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
|
||||||
private final String sort;
|
private final String sort;
|
||||||
|
|
||||||
private int pageNumber = 1;
|
private int pageNumber = 1;
|
||||||
private final int pageSize = 1000;
|
private final int pageSize = 10000;
|
||||||
|
|
||||||
private Collection<ClientEventRecord> nextRecords;
|
private Collection<ClientEventRecord> nextRecords;
|
||||||
|
|
||||||
|
|
|
@ -112,11 +112,13 @@ class ExamSessionControlTask implements DisposableBean {
|
||||||
@Scheduled(fixedRateString = "${sebserver.webservice.api.exam.update-ping:5000}")
|
@Scheduled(fixedRateString = "${sebserver.webservice.api.exam.update-ping:5000}")
|
||||||
public void examSessionUpdateTask() {
|
public void examSessionUpdateTask() {
|
||||||
|
|
||||||
|
this.sebClientConnectionService.updatePingEvents();
|
||||||
|
|
||||||
if (!this.webserviceInfoDAO.isMaster(this.webserviceInfo.getWebserviceUUID())) {
|
if (!this.webserviceInfoDAO.isMaster(this.webserviceInfo.getWebserviceUUID())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sebClientConnectionService.updatePingEvents();
|
this.sebClientConnectionService.cleanupInstructions();
|
||||||
this.examProcotringRoomService.updateProctoringCollectingRooms();
|
this.examProcotringRoomService.updateProctoringCollectingRooms();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,8 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato
|
||||||
return super.currentValue;
|
return super.currentValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO do this within a better reactive way like ping updates
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final Long errors = this.clientEventRecordMapper
|
final Long errors = this.clientEventRecordMapper
|
||||||
|
|
|
@ -67,9 +67,9 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator {
|
||||||
return super.currentValue;
|
return super.currentValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// TODO do this within a better reactive way like ping updates
|
||||||
|
|
||||||
System.out.println("************** loadFromPersistent");
|
try {
|
||||||
|
|
||||||
final List<ClientEventRecord> execute = this.clientEventRecordMapper.selectByExample()
|
final List<ClientEventRecord> execute = this.clientEventRecordMapper.selectByExample()
|
||||||
.where(ClientEventRecordDynamicSqlSupport.clientConnectionId, isEqualTo(this.connectionId))
|
.where(ClientEventRecordDynamicSqlSupport.clientConnectionId, isEqualTo(this.connectionId))
|
||||||
|
|
|
@ -19,8 +19,6 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
|
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventLastPingMapper;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventLastPingMapper;
|
||||||
|
@ -35,12 +33,12 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
|
||||||
private final Executor executor;
|
private final Executor executor;
|
||||||
protected final DistributedPingCache distributedPingCache;
|
protected final DistributedPingCache distributedPingCache;
|
||||||
|
|
||||||
//protected Long pingRecord = null;
|
|
||||||
protected PingUpdate pingUpdate = null;
|
protected PingUpdate pingUpdate = null;
|
||||||
|
|
||||||
protected AbstractPingIndicator(
|
protected AbstractPingIndicator(
|
||||||
final DistributedPingCache distributedPingCache,
|
final DistributedPingCache distributedPingCache,
|
||||||
@Qualifier(AsyncServiceSpringConfig.EXAM_API_PING_SERVICE_EXECUTOR_BEAN_NAME) final Executor executor) {
|
@Qualifier(AsyncServiceSpringConfig.EXAM_API_PING_SERVICE_EXECUTOR_BEAN_NAME) final Executor executor) {
|
||||||
|
|
||||||
super();
|
super();
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
this.distributedPingCache = distributedPingCache;
|
this.distributedPingCache = distributedPingCache;
|
||||||
|
@ -81,7 +79,9 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
|
||||||
try {
|
try {
|
||||||
this.executor.execute(this.pingUpdate);
|
this.executor.execute(this.pingUpdate);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
//log.warn("Failed to schedule ping task: {}" + e.getMessage());
|
if (log.isDebugEnabled()) {
|
||||||
|
log.warn("Failed to schedule ping task: {}" + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,30 +115,6 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
|
||||||
|
|
||||||
public abstract ClientEventRecord updateLogEvent(final long now);
|
public abstract ClientEventRecord updateLogEvent(final long now);
|
||||||
|
|
||||||
@Override
|
|
||||||
public double computeValueAt(final long timestamp) {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void notifyValueChange(final ClientEvent event) {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void notifyValueChange(final ClientEventRecord clientEventRecord) {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IndicatorType getType() {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static final class PingUpdate implements Runnable {
|
static final class PingUpdate implements Runnable {
|
||||||
|
|
||||||
private final ClientEventLastPingMapper clientEventLastPingMapper;
|
private final ClientEventLastPingMapper clientEventLastPingMapper;
|
||||||
|
@ -151,8 +127,12 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
this.clientEventLastPingMapper
|
try {
|
||||||
.updatePingTime(this.pingRecord, Utils.getMillisecondsNow());
|
this.clientEventLastPingMapper
|
||||||
|
.updatePingTime(this.pingRecord, Utils.getMillisecondsNow());
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Failed to update ping: {}", e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,6 +176,7 @@ public class DistributedPingCache implements DisposableBean {
|
||||||
|
|
||||||
public Long getLastPing(final Long pingRecordId, final boolean missing) {
|
public Long getLastPing(final Long pingRecordId, final boolean missing) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
Long ping = this.pingCache.get(pingRecordId);
|
Long ping = this.pingCache.get(pingRecordId);
|
||||||
if (ping == null && !missing) {
|
if (ping == null && !missing) {
|
||||||
|
|
||||||
|
@ -199,7 +200,7 @@ public class DistributedPingCache implements DisposableBean {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePings() {
|
private void updatePings() {
|
||||||
|
|
||||||
if (this.pingCache.isEmpty()) {
|
if (this.pingCache.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
|
||||||
this.missingPing = this.pingErrorThreshold < value;
|
this.missingPing = this.pingErrorThreshold < value;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Failed to initialize missingPing: {}", e.getMessage());
|
log.error("Failed to initialize missingPing: {}", e.getMessage());
|
||||||
this.missingPing = false;
|
this.missingPing = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,8 +111,8 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double getValue() {
|
public double getValue() {
|
||||||
final long now = DateTimeUtils.currentTimeMillis();
|
final double value = super.getValue();
|
||||||
return now - super.getValue();
|
return DateTimeUtils.currentTimeMillis() - value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -177,6 +177,16 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
|
||||||
.createErrorResponse(ex.getMessage());
|
.createErrorResponse(ex.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ExamNotRunningException.class)
|
||||||
|
public ResponseEntity<Object> handleExamNotRunning(
|
||||||
|
final ExamNotRunningException ex,
|
||||||
|
final WebRequest request) {
|
||||||
|
|
||||||
|
log.warn("{}", ex.getMessage());
|
||||||
|
return APIMessage.ErrorMessage.INTEGRITY_VALIDATION
|
||||||
|
.createErrorResponse(ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
@ExceptionHandler(APIConstraintViolationException.class)
|
@ExceptionHandler(APIConstraintViolationException.class)
|
||||||
public ResponseEntity<Object> handleIllegalAPIArgumentException(
|
public ResponseEntity<Object> handleIllegalAPIArgumentException(
|
||||||
final APIConstraintViolationException ex,
|
final APIConstraintViolationException ex,
|
||||||
|
|
|
@ -202,6 +202,7 @@ public class ExamMonitoringController {
|
||||||
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) {
|
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) {
|
||||||
|
|
||||||
checkPrivileges(institutionId, examId);
|
checkPrivileges(institutionId, examId);
|
||||||
|
|
||||||
return this.examSessionService
|
return this.examSessionService
|
||||||
.getConnectionData(connectionToken)
|
.getConnectionData(connectionToken)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
@ -267,6 +268,7 @@ public class ExamMonitoringController {
|
||||||
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) {
|
@PathVariable(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken) {
|
||||||
|
|
||||||
checkPrivileges(institutionId, examId);
|
checkPrivileges(institutionId, examId);
|
||||||
|
|
||||||
this.sebClientNotificationService.confirmPendingNotification(
|
this.sebClientNotificationService.confirmPendingNotification(
|
||||||
notificationId,
|
notificationId,
|
||||||
examId,
|
examId,
|
||||||
|
@ -313,8 +315,14 @@ public class ExamMonitoringController {
|
||||||
UserRole.EXAM_SUPPORTER,
|
UserRole.EXAM_SUPPORTER,
|
||||||
UserRole.EXAM_ADMIN);
|
UserRole.EXAM_ADMIN);
|
||||||
|
|
||||||
|
// check exam running
|
||||||
|
final Exam runningExam = this.examSessionService.getRunningExam(examId).getOr(null);
|
||||||
|
if (runningExam == null) {
|
||||||
|
throw new ExamNotRunningException("Exam not currently running: " + examId);
|
||||||
|
}
|
||||||
|
|
||||||
// check running exam privilege for specified exam
|
// check running exam privilege for specified exam
|
||||||
if (!hasRunningExamPrivilege(examId, institutionId)) {
|
if (!hasRunningExamPrivilege(runningExam, institutionId)) {
|
||||||
throw new PermissionDeniedException(
|
throw new PermissionDeniedException(
|
||||||
EntityType.EXAM,
|
EntityType.EXAM,
|
||||||
PrivilegeType.READ,
|
PrivilegeType.READ,
|
||||||
|
@ -322,12 +330,6 @@ public class ExamMonitoringController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasRunningExamPrivilege(final Long examId, final Long institution) {
|
|
||||||
return hasRunningExamPrivilege(
|
|
||||||
this.examSessionService.getRunningExam(examId).getOr(null),
|
|
||||||
institution);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasRunningExamPrivilege(final Exam exam, final Long institution) {
|
private boolean hasRunningExamPrivilege(final Exam exam, final Long institution) {
|
||||||
if (exam == null) {
|
if (exam == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
||||||
|
|
||||||
|
public class ExamNotRunningException extends RuntimeException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -2931666431463176875L;
|
||||||
|
|
||||||
|
public ExamNotRunningException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamNotRunningException(final String message, final Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExamNotRunningException(final String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ sebserver.webservice.internalSecret=${sebserver.password}
|
||||||
|
|
||||||
### webservice networking
|
### webservice networking
|
||||||
sebserver.webservice.forceMaster=false
|
sebserver.webservice.forceMaster=false
|
||||||
sebserver.webservice.distributed=false
|
sebserver.webservice.distributed=true
|
||||||
sebserver.webservice.distributed.pingUpdate=3000
|
sebserver.webservice.distributed.pingUpdate=3000
|
||||||
sebserver.webservice.http.external.scheme=https
|
sebserver.webservice.http.external.scheme=https
|
||||||
sebserver.webservice.http.external.servername=
|
sebserver.webservice.http.external.servername=
|
||||||
|
|
Loading…
Reference in a new issue