fixed caching issue with Exam Config changes on running exams
This commit is contained in:
parent
278bdf4673
commit
5aa3671608
13 changed files with 136 additions and 10 deletions
1
docker/.gitignore
vendored
Normal file
1
docker/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/test/
|
|
@ -32,7 +32,7 @@ ENTRYPOINT exec java \
|
|||
-Dcom.sun.management.jmxremote \
|
||||
-Dcom.sun.management.jmxremote.port=9090 \
|
||||
-Dcom.sun.management.jmxremote.rmi.port=9090 \
|
||||
-Djava.rmi.server.hostname=0.0.0.0 \
|
||||
-Djava.rmi.server.hostname=127.0.0.1 \
|
||||
-Dcom.sun.management.jmxremote.ssl=false \
|
||||
-Dcom.sun.management.jmxremote.authenticate=false \
|
||||
-jar seb-server-"${SEBSERVER_VERSION}"-SNAPSHOT.jar \
|
||||
|
|
|
@ -6,7 +6,7 @@ server.servlet.session.cookie.http-only=true
|
|||
server.servlet.session.tracking-modes=cookie
|
||||
|
||||
# database server
|
||||
datastore.mariadb.server.address=test-mariadb
|
||||
datastore.mariadb.server.address=seb-server-mariadb
|
||||
datastore.mariadb.server.port=3306
|
||||
|
||||
# data source configuration
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
|
||||
|
@ -21,14 +23,14 @@ public interface ExamConfigurationMapDAO extends
|
|||
* @param examId The Exam mapping identifier
|
||||
* @param configurationNodeId the ConfigurationNode mapping identifier
|
||||
* @return Result refer to the ExamConfigurationMap with specified mapping or to an exception if happened */
|
||||
public Result<ExamConfigurationMap> byMapping(Long examId, Long configurationNodeId);
|
||||
Result<ExamConfigurationMap> byMapping(Long examId, Long configurationNodeId);
|
||||
|
||||
/** Get the password cipher of a specific ExamConfigurationMap by the mapping identifiers
|
||||
*
|
||||
* @param examId The Exam mapping identifier
|
||||
* @param configurationNodeId the ConfigurationNode mapping identifier
|
||||
* @return Result refer to the password cipher of specified mapping or to an exception if happened */
|
||||
public Result<CharSequence> getConfigPasswortCipher(Long examId, Long configurationNodeId);
|
||||
Result<CharSequence> getConfigPasswortCipher(Long examId, Long configurationNodeId);
|
||||
|
||||
/** Get the ConfigurationNode identifier of the default Exam Configuration of
|
||||
* the Exam with specified identifier.
|
||||
|
@ -36,7 +38,7 @@ public interface ExamConfigurationMapDAO extends
|
|||
* @param examId The Exam identifier
|
||||
* @return ConfigurationNode identifier of the default Exam Configuration of
|
||||
* the Exam with specified identifier */
|
||||
public Result<Long> getDefaultConfigurationForExam(Long examId);
|
||||
Result<Long> getDefaultConfigurationForExam(Long examId);
|
||||
|
||||
/** Get the ConfigurationNode identifier of the Exam Configuration of
|
||||
* the Exam for a specified user identifier.
|
||||
|
@ -45,6 +47,12 @@ public interface ExamConfigurationMapDAO extends
|
|||
* @param userId the user identifier
|
||||
* @return ConfigurationNode identifier of the Exam Configuration of
|
||||
* the Exam for a specified user identifier */
|
||||
public Result<Long> getUserConfigurationIdForExam(final Long examId, final String userId);
|
||||
Result<Long> getUserConfigurationIdForExam(final Long examId, final String userId);
|
||||
|
||||
/** Get all id of Exams that has a relation to the given configuration id.
|
||||
*
|
||||
* @param configurationId
|
||||
* @return */
|
||||
Result<Collection<Long>> getExamIdsForConfigId(Long configurationId);
|
||||
|
||||
}
|
||||
|
|
|
@ -22,12 +22,17 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
|
|||
|
||||
Result<Collection<Long>> allIdsOfInstituion(Long institutionId);
|
||||
|
||||
/** Saves the Exam and updates the running exam cache. */
|
||||
@Override
|
||||
@CacheEvict(
|
||||
cacheNames = ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM,
|
||||
key = "#exam.id")
|
||||
Result<Exam> save(Exam exam);
|
||||
|
||||
/** Get an Exam by a given ClientConnection id.
|
||||
*
|
||||
* @param connectionId
|
||||
* @return a Result containing the Exam by a given ClientConnection id or refer to an error if happened */
|
||||
Result<Exam> byClientConnection(Long connectionId);
|
||||
|
||||
}
|
||||
|
|
|
@ -35,7 +35,9 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.Configuration
|
|||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationNodeRecordDynamicSqlSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationNodeRecordMapper;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationRecordDynamicSqlSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordDynamicSqlSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordMapper;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport;
|
||||
|
@ -285,6 +287,35 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
return getDependencies(bulkAction, selectionFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<Long>> getExamIdsForConfigId(final Long configurationId) {
|
||||
return Result.tryCatch(() -> {
|
||||
final Long configNodeId = this.configurationNodeRecordMapper.selectIdsByExample()
|
||||
.leftJoin(ConfigurationRecordDynamicSqlSupport.configurationRecord)
|
||||
.on(
|
||||
ConfigurationRecordDynamicSqlSupport.configurationNodeId,
|
||||
equalTo(ConfigurationNodeRecordDynamicSqlSupport.id))
|
||||
.where(
|
||||
ConfigurationRecordDynamicSqlSupport.id,
|
||||
isEqualTo(configurationId))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.collect(Utils.toSingleton());
|
||||
|
||||
return this.examConfigurationMapRecordMapper.selectByExample()
|
||||
.where(
|
||||
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
|
||||
isEqualTo(configNodeId))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(record -> record.getExamId())
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
}
|
||||
|
||||
private Result<ExamConfigurationMapRecord> recordById(final Long id) {
|
||||
return Result.tryCatch(() -> {
|
||||
final ExamConfigurationMapRecord record = this.examConfigurationMapRecordMapper
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.servicelayer.sebconfig;
|
||||
|
||||
public class ConfigurationChangedEvent {
|
||||
|
||||
public final Long configurationId;
|
||||
|
||||
public ConfigurationChangedEvent(final Long configurationId) {
|
||||
this.configurationId = configurationId;
|
||||
}
|
||||
|
||||
}
|
|
@ -12,11 +12,14 @@ import java.io.OutputStream;
|
|||
import java.util.Collection;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.context.event.EventListener;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationChangedEvent;
|
||||
|
||||
/** A Service to handle running exam sessions */
|
||||
public interface ExamSessionService {
|
||||
|
@ -84,4 +87,7 @@ public interface ExamSessionService {
|
|||
* of a running exam */
|
||||
Result<Collection<ClientConnectionData>> getConnectionData(Long examId);
|
||||
|
||||
@EventListener(ConfigurationChangedEvent.class)
|
||||
void updateExamConfigCache(ConfigurationChangedEvent configChanged);
|
||||
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -30,8 +31,10 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
|||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationChangedEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
|
||||
@Lazy
|
||||
|
@ -44,16 +47,19 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
private final ClientConnectionDAO clientConnectionDAO;
|
||||
private final ExamSessionCacheService examSessionCacheService;
|
||||
private final ExamDAO examDAO;
|
||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||
private final CacheManager cacheManager;
|
||||
|
||||
protected ExamSessionServiceImpl(
|
||||
final ExamSessionCacheService examSessionCacheService,
|
||||
final ExamDAO examDAO,
|
||||
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
||||
final ClientConnectionDAO clientConnectionDAO,
|
||||
final CacheManager cacheManager) {
|
||||
|
||||
this.examSessionCacheService = examSessionCacheService;
|
||||
this.examDAO = examDAO;
|
||||
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
||||
this.clientConnectionDAO = clientConnectionDAO;
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
|
@ -191,6 +197,20 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@EventListener(ConfigurationChangedEvent.class)
|
||||
public void updateExamConfigCache(final ConfigurationChangedEvent configChanged) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Flush exam config cache for configuration: {}", configChanged.configurationId);
|
||||
}
|
||||
|
||||
this.examConfigurationMapDAO
|
||||
.getExamIdsForConfigId(configChanged.configurationId)
|
||||
.getOrElse(() -> Collections.emptyList())
|
||||
.forEach(this.examSessionCacheService::evictDefaultSebConfig);
|
||||
}
|
||||
|
||||
private void flushCache(final Exam exam) {
|
||||
try {
|
||||
this.examSessionCacheService.evict(exam);
|
||||
|
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
|||
import java.util.Collection;
|
||||
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
@ -23,12 +24,14 @@ import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
|
|||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationRecordDynamicSqlSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationChangedEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
||||
|
||||
@WebServiceProfile
|
||||
|
@ -37,6 +40,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
|
|||
public class ConfigurationController extends ReadonlyEntityController<Configuration, Configuration> {
|
||||
|
||||
private final ConfigurationDAO configurationDAO;
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
protected ConfigurationController(
|
||||
final AuthorizationService authorization,
|
||||
|
@ -44,7 +48,8 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
|||
final ConfigurationDAO entityDAO,
|
||||
final UserActivityLogDAO userActivityLogDAO,
|
||||
final PaginationService paginationService,
|
||||
final BeanValidationService beanValidationService) {
|
||||
final BeanValidationService beanValidationService,
|
||||
final ApplicationEventPublisher applicationEventPublisher) {
|
||||
|
||||
super(authorization,
|
||||
bulkActionService,
|
||||
|
@ -54,6 +59,7 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
|||
beanValidationService);
|
||||
|
||||
this.configurationDAO = entityDAO;
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
|
@ -67,6 +73,7 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
|||
.flatMap(this.authorization::checkModify)
|
||||
.flatMap(config -> this.configurationDAO.saveToHistory(config.configurationNodeId))
|
||||
.flatMap(this.userActivityLogDAO::logSaveToHistory)
|
||||
.flatMap(this::publishConfigChanged)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
|
@ -81,6 +88,7 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
|||
.flatMap(this.authorization::checkModify)
|
||||
.flatMap(config -> this.configurationDAO.undo(config.configurationNodeId))
|
||||
.flatMap(this.userActivityLogDAO::logUndo)
|
||||
.flatMap(this::publishConfigChanged)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
|
@ -96,6 +104,7 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
|||
return this.entityDAO.byModelId(modelId)
|
||||
.flatMap(this.authorization::checkModify)
|
||||
.flatMap(config -> this.configurationDAO.restoreToVersion(configurationNodeId, config.getId()))
|
||||
.flatMap(this::publishConfigChanged)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
|
@ -109,4 +118,9 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
|
|||
return ConfigurationRecordDynamicSqlSupport.configurationRecord;
|
||||
}
|
||||
|
||||
private Result<Configuration> publishConfigChanged(final Configuration config) {
|
||||
this.applicationEventPublisher.publishEvent(new ConfigurationChangedEvent(config.id));
|
||||
return Result.of(config);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.Objects;
|
|||
import javax.validation.Valid;
|
||||
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
@ -35,6 +36,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionServic
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationChangedEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
|
||||
|
||||
|
@ -46,6 +48,7 @@ public class ConfigurationValueController extends EntityController<Configuration
|
|||
private final ConfigurationDAO configurationDAO;
|
||||
private final ConfigurationValueDAO configurationValueDAO;
|
||||
private final SebExamConfigService sebExamConfigService;
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
protected ConfigurationValueController(
|
||||
final AuthorizationService authorization,
|
||||
|
@ -55,7 +58,8 @@ public class ConfigurationValueController extends EntityController<Configuration
|
|||
final PaginationService paginationService,
|
||||
final BeanValidationService beanValidationService,
|
||||
final ConfigurationDAO configurationDAO,
|
||||
final SebExamConfigService sebExamConfigService) {
|
||||
final SebExamConfigService sebExamConfigService,
|
||||
final ApplicationEventPublisher applicationEventPublisher) {
|
||||
|
||||
super(authorization,
|
||||
bulkActionService,
|
||||
|
@ -67,6 +71,19 @@ public class ConfigurationValueController extends EntityController<Configuration
|
|||
this.configurationDAO = configurationDAO;
|
||||
this.configurationValueDAO = entityDAO;
|
||||
this.sebExamConfigService = sebExamConfigService;
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result<ConfigurationValue> notifySaved(final ConfigurationValue entity) {
|
||||
if (entity == null) {
|
||||
return super.notifySaved(entity);
|
||||
}
|
||||
|
||||
this.applicationEventPublisher.publishEvent(
|
||||
new ConfigurationChangedEvent(entity.configurationId));
|
||||
|
||||
return super.notifySaved(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -124,6 +141,11 @@ public class ConfigurationValueController extends EntityController<Configuration
|
|||
return this.configurationDAO.byPK(tableValue.configurationId)
|
||||
.flatMap(this.authorization::checkModify)
|
||||
.flatMap(config -> this.configurationValueDAO.saveTableValues(tableValue))
|
||||
.map(config -> {
|
||||
this.applicationEventPublisher.publishEvent(
|
||||
new ConfigurationChangedEvent(config.configurationId));
|
||||
return config;
|
||||
})
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ public class HTTPClientBot {
|
|||
this.apiVersion = args.getOrDefault("apiVersion", "v1");
|
||||
this.examId = args.getOrDefault("examId", "2");
|
||||
this.institutionId = args.getOrDefault("institutionId", "1");
|
||||
this.numberOfConnections = Integer.parseInt(args.getOrDefault("numberOfConnections", "4"));
|
||||
this.numberOfConnections = Integer.parseInt(args.getOrDefault("numberOfConnections", "1"));
|
||||
this.pingInterval = Long.parseLong(args.getOrDefault("pingInterval", "200"));
|
||||
this.errorInterval = Long.parseLong(args.getOrDefault("errorInterval", String.valueOf(TEN_SECONDS)));
|
||||
this.runtime = Long.parseLong(args.getOrDefault("runtime", String.valueOf(ONE_MINUTE)));
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</encoder>
|
||||
</appender>
|
||||
|
||||
<Logger name="org.eth.demo.sebserver" level="TRACE" additivity="true" />
|
||||
<Logger name="ch.ethz.seb.sebserver.HTTPClientBot" level="TRACE" additivity="true" />
|
||||
|
||||
<root level="INFO" additivity="true">
|
||||
<appender-ref ref="STDOUT" />
|
||||
|
|
Loading…
Reference in a new issue