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:
anhefti 2021-12-06 13:41:25 +01:00
commit 3349d67d5a
22 changed files with 193 additions and 92 deletions

View file

@ -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;
} }
} }

View file

@ -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");
}
}
} }
} }

View file

@ -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()));
}
}

View file

@ -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(

View file

@ -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;

View file

@ -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);
} }

View file

@ -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);

View file

@ -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 {

View file

@ -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

View file

@ -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) {

View file

@ -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);
} }

View file

@ -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;

View file

@ -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();
} }

View file

@ -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

View file

@ -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))

View file

@ -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());
}
} }
} }

View file

@ -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;
} }

View file

@ -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

View file

@ -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,

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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=