fixed caching issue with Exam Config changes on running exams

This commit is contained in:
anhefti 2019-09-13 16:57:07 +02:00
parent 278bdf4673
commit 5aa3671608
13 changed files with 136 additions and 10 deletions

1
docker/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/test/

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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