diff --git a/pom.xml b/pom.xml
index 8ffcc0cb..39138315 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
jar
- 1.3.2
+ 1.3.3
${sebserver-version}
${sebserver-version}
UTF-8
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/IndicatorTemplate.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/IndicatorTemplate.java
index b9dfec50..a84c7380 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/IndicatorTemplate.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/IndicatorTemplate.java
@@ -95,6 +95,17 @@ public class IndicatorTemplate implements Entity {
this.thresholds = postParams.getThresholds();
}
+ public IndicatorTemplate(final Long id, final IndicatorTemplate other) {
+ this.id = id;
+ this.examTemplateId = other.examTemplateId;
+ this.name = other.name;
+ this.type = other.type;
+ this.defaultColor = other.defaultColor;
+ this.defaultIcon = other.defaultIcon;
+ this.tags = other.tags;
+ this.thresholds = Utils.immutableListOf(other.thresholds);
+ }
+
@Override
public String getModelId() {
return (this.id == null) ? null : String.valueOf(this.id);
@@ -168,7 +179,7 @@ public class IndicatorTemplate implements Entity {
final StringBuilder builder = new StringBuilder();
builder.append("Indicator [id=");
builder.append(this.id);
- builder.append(", examId=");
+ builder.append(", examTemplateId=");
builder.append(this.examTemplateId);
builder.append(", name=");
builder.append(this.name);
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCheckbox.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCheckbox.java
index df41bdc7..36d91d24 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCheckbox.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCheckbox.java
@@ -82,10 +82,14 @@ public final class MultiSelectionCheckbox extends Composite implements Selection
WidgetFactory.setARIALabel(button, tuple._2);
this.checkboxes.put(tuple._1, button);
- @SuppressWarnings("unchecked")
- final Tuple3 tuple3 = tuple.adaptTo(Tuple3.class);
- if (tuple3 != null && StringUtils.isNotBlank(tuple3._3)) {
- button.setToolTipText(tuple3._3);
+ try {
+ @SuppressWarnings("unchecked")
+ final Tuple3 tuple3 = tuple.adaptTo(Tuple3.class);
+ if (tuple3 != null && StringUtils.isNotBlank(tuple3._3)) {
+ button.setToolTipText(tuple3._3);
+ }
+ } catch (final Exception e) {
+ // ignore
}
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/OrientationTableDuplicatesCheck.java b/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/OrientationTableDuplicatesCheck.java
index 0a391e44..ad1a226f 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/OrientationTableDuplicatesCheck.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/datalayer/checks/OrientationTableDuplicatesCheck.java
@@ -11,7 +11,9 @@ package ch.ethz.seb.sebserver.webservice.datalayer.checks;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
+import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -19,6 +21,7 @@ import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.DBIntegrityCheck;
+import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.OrientationRecord;
@@ -68,10 +71,15 @@ public class OrientationTableDuplicatesCheck implements DBIntegrityCheck {
}
if (tryFix) {
- toDelete
+ final List checkedToDelete = toDelete
+ .stream()
+ .filter(this::doubleCheck)
+ .collect(Collectors.toList());
+
+ checkedToDelete
.stream()
.forEach(this.orientationRecordMapper::deleteByPrimaryKey);
- return "Fixed duplicates by deletion: " + toDelete;
+ return "Fixed duplicates by deletion: " + checkedToDelete + " from findings:" + toDelete;
} else {
return "Found duplicates: " + toDelete;
}
@@ -79,6 +87,24 @@ public class OrientationTableDuplicatesCheck implements DBIntegrityCheck {
});
}
+ private boolean doubleCheck(final Long id) {
+ try {
+ final OrientationRecord selectByPrimaryKey = this.orientationRecordMapper.selectByPrimaryKey(id);
+ final Long count = this.orientationRecordMapper.countByExample()
+ .where(
+ OrientationRecordDynamicSqlSupport.configAttributeId,
+ SqlBuilder.isEqualTo(selectByPrimaryKey.getConfigAttributeId()))
+ .and(
+ OrientationRecordDynamicSqlSupport.templateId,
+ SqlBuilder.isEqualTo(selectByPrimaryKey.getTemplateId()))
+ .build()
+ .execute();
+ return count != null && count.longValue() > 1;
+ } catch (final Exception e) {
+ return false;
+ }
+ }
+
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java
index eadf45e1..090e3cac 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java
@@ -32,6 +32,7 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionR
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationNodeRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport;
+import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamTemplateRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.InstitutionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.LmsSetupRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordDynamicSqlSupport;
@@ -203,11 +204,11 @@ public class PaginationServiceImpl implements PaginationService {
if (StringUtils.isNotBlank(sortColumnName)) {
switch (sortOrder) {
case DESCENDING: {
- PageHelper.orderBy(sortColumnName + " DESC");
+ PageHelper.orderBy(sortColumnName + " DESC, id DESC");
break;
}
default: {
- PageHelper.orderBy(sortColumnName);
+ PageHelper.orderBy(sortColumnName + ", id");
break;
}
}
@@ -265,6 +266,18 @@ public class PaginationServiceImpl implements PaginationService {
this.sortColumnMapping.put(LmsSetupRecordDynamicSqlSupport.lmsSetupRecord.name(), lmsSetupTableMap);
this.defaultSortColumn.put(LmsSetupRecordDynamicSqlSupport.lmsSetupRecord.name(), Domain.LMS_SETUP.ATTR_ID);
+ // Exam Template Table
+ final Map examTemplateTableMap = new HashMap<>();
+ examTemplateTableMap.put(Entity.FILTER_ATTR_INSTITUTION, institutionNameRef);
+ examTemplateTableMap.put(Domain.EXAM_TEMPLATE.ATTR_NAME, ExamTemplateRecordDynamicSqlSupport.name.name());
+ examTemplateTableMap.put(Domain.EXAM_TEMPLATE.ATTR_EXAM_TYPE,
+ ExamTemplateRecordDynamicSqlSupport.examType.name());
+
+ this.sortColumnMapping.put(ExamTemplateRecordDynamicSqlSupport.examTemplateRecord.name(), examTemplateTableMap);
+ this.defaultSortColumn.put(
+ ExamTemplateRecordDynamicSqlSupport.examTemplateRecord.name(),
+ Domain.EXAM_TEMPLATE.ATTR_ID);
+
// Exam Table
final Map examTableMap = new HashMap<>();
examTableMap.put(Entity.FILTER_ATTR_INSTITUTION, institutionNameRef);
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java
index 1228bb26..51c1449e 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java
@@ -105,4 +105,10 @@ public interface ConfigurationDAO extends EntityDAO getConfigurationLastStableVersion(Long configNodeId);
+ /** Use this to get the follow-up configuration identifer for a specified configuration node.
+ *
+ * @param configurationNode ConfigurationNode to get the current follow-up configuration from
+ * @return the current follow-up configuration identifier */
+ Result getFollowupConfigurationId(Long configNodeId);
+
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamTemplateDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamTemplateDAO.java
index bc57fb24..747c53fc 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamTemplateDAO.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ExamTemplateDAO.java
@@ -8,7 +8,9 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
+import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate;
+import ch.ethz.seb.sebserver.gbl.model.exam.IndicatorTemplate;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
@@ -21,4 +23,22 @@ public interface ExamTemplateDAO extends EntityDAO,
* @return Result refer to the ExamTemplate instance or to an error when happened */
Result getInstitutionalDefault(Long institutionId);
+ /** Creates a new indicator template
+ *
+ * @param indicatorTemplate The IndicatorTemplate refer also to the exam template (examTemplateId)
+ * @return Result refer to the created IndicatorTemplate or to an error when happened */
+ Result createNewIndicatorTemplate(IndicatorTemplate indicatorTemplate);
+
+ /** Saves an already existing indicator template
+ *
+ * @param indicatorTemplate The IndicatorTemplate refer also to the exam template (examTemplateId)
+ * @return Result refer to the saved IndicatorTemplate or to an error when happened */
+ Result saveIndicatorTemplate(IndicatorTemplate indicatorTemplate);
+
+ /** Deletes an already existing indicator template
+ *
+ * @param indicatorTemplate The IndicatorTemplate refer also to the exam template (examTemplateId)
+ * @return Result refer to the EntityKey of the deleted IndicatorTemplate or to an error when happened */
+ Result deleteIndicatorTemplate(String examTemplateId, String indicatorTemplateId);
+
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserActivityLogDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserActivityLogDAO.java
index 7d432e16..34ece538 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserActivityLogDAO.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/UserActivityLogDAO.java
@@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Entity;
+import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.model.user.UserAccount;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
@@ -83,6 +84,12 @@ public interface UserActivityLogDAO extends
* @return Result of the Entity or referring to an Error if happened */
Result logDelete(E entity);
+ /** Create a user activity log entry for the current user of activity type DELETE
+ *
+ * @param entityKey the EntityKey of the deleted object
+ * @return Result of the EntityKey or referring to an Error if happened */
+ Result logDelete(EntityKey entityKey);
+
/** Used to log a successful bulk action and uses the EntityProcessingReport from the
* bulk action to log all details.
*
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java
index 5ce765ff..8f7b6cda 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationDAOImpl.java
@@ -133,8 +133,24 @@ public class ConfigurationDAOImpl implements ConfigurationDAO {
.build()
.execute()
.stream()
- .collect(Utils.toSingleton())).flatMap(ConfigurationDAOImpl::toDomainModel);
+ .collect(Utils.toSingleton()))
+ .flatMap(ConfigurationDAOImpl::toDomainModel);
+ }
+ @Override
+ @Transactional(readOnly = true)
+ public Result getFollowupConfigurationId(final Long configNodeId) {
+ return Result.tryCatch(() -> this.configurationRecordMapper.selectIdsByExample()
+ .where(
+ ConfigurationRecordDynamicSqlSupport.configurationNodeId,
+ isEqualTo(configNodeId))
+ .and(
+ ConfigurationRecordDynamicSqlSupport.followup,
+ isEqualTo(BooleanUtils.toInteger(true)))
+ .build()
+ .execute()
+ .stream()
+ .collect(Utils.toSingleton()));
}
@Override
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamTemplateDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamTemplateDAOImpl.java
index ce75daa1..a5108e47 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamTemplateDAOImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamTemplateDAOImpl.java
@@ -16,12 +16,16 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
+import org.mybatis.dynamic.sql.SqlBuilder;
+import org.mybatis.dynamic.sql.select.MyBatis3SelectModelAdapter;
+import org.mybatis.dynamic.sql.select.QueryExpressionDSL;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -42,6 +46,7 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamTemplateRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamTemplateRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.IndicatorRecordDynamicSqlSupport;
+import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.InstitutionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamTemplateRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
@@ -140,24 +145,38 @@ public class ExamTemplateDAOImpl implements ExamTemplateDAO {
final FilterMap filterMap,
final Predicate predicate) {
- return Result.tryCatch(() -> this.examTemplateRecordMapper
- .selectByExample()
- .where(
- ExamTemplateRecordDynamicSqlSupport.institutionId,
- isEqualToWhenPresent(filterMap.getInstitutionId()))
- .and(
- ExamTemplateRecordDynamicSqlSupport.name,
- isLikeWhenPresent(filterMap.getExamTemplateName()))
- .and(
- ExamTemplateRecordDynamicSqlSupport.examType,
- isEqualToWhenPresent(filterMap.getString(ExamTemplate.FILTER_ATTR_EXAM_TYPE)))
- .build()
- .execute()
- .stream()
- .map(this::toDomainModel)
- .flatMap(DAOLoggingSupport::logAndSkipOnError)
- .filter(predicate)
- .collect(Collectors.toList()));
+ return Result.tryCatch(() -> {
+ final QueryExpressionDSL>>.QueryExpressionWhereBuilder whereClause =
+ (filterMap.getBoolean(FilterMap.ATTR_ADD_INSITUTION_JOIN))
+ ? this.examTemplateRecordMapper
+ .selectByExample()
+ .join(InstitutionRecordDynamicSqlSupport.institutionRecord)
+ .on(InstitutionRecordDynamicSqlSupport.id,
+ SqlBuilder.equalTo(ExamTemplateRecordDynamicSqlSupport.institutionId))
+ .where(
+ ExamTemplateRecordDynamicSqlSupport.institutionId,
+ isEqualToWhenPresent(filterMap.getInstitutionId()))
+ : this.examTemplateRecordMapper
+ .selectByExample()
+ .where(
+ ExamTemplateRecordDynamicSqlSupport.institutionId,
+ isEqualToWhenPresent(filterMap.getInstitutionId()));
+
+ return whereClause
+ .and(
+ ExamTemplateRecordDynamicSqlSupport.name,
+ isLikeWhenPresent(filterMap.getExamTemplateName()))
+ .and(
+ ExamTemplateRecordDynamicSqlSupport.examType,
+ isEqualToWhenPresent(filterMap.getString(ExamTemplate.FILTER_ATTR_EXAM_TYPE)))
+ .build()
+ .execute()
+ .stream()
+ .map(this::toDomainModel)
+ .flatMap(DAOLoggingSupport::logAndSkipOnError)
+ .filter(predicate)
+ .collect(Collectors.toList());
+ });
}
@Override
@@ -202,11 +221,6 @@ public class ExamTemplateDAOImpl implements ExamTemplateDAO {
checkUniqueName(data);
checkUniqueDefault(data);
- final Collection indicatorTemplates = data.getIndicatorTemplates();
- final String indicatorsJSON = (indicatorTemplates != null && !indicatorTemplates.isEmpty())
- ? this.jsonMapper.writeValueAsString(indicatorTemplates)
- : null;
-
final ExamTemplateRecord newRecord = new ExamTemplateRecord(
data.id,
null,
@@ -219,7 +233,7 @@ public class ExamTemplateDAOImpl implements ExamTemplateDAO {
(data.supporter != null)
? StringUtils.join(data.supporter, Constants.LIST_SEPARATOR_CHAR)
: null,
- indicatorsJSON,
+ null,
BooleanUtils.toInteger(data.institutionalDefault));
this.examTemplateRecordMapper.updateByPrimaryKeySelective(newRecord);
@@ -241,6 +255,137 @@ public class ExamTemplateDAOImpl implements ExamTemplateDAO {
.onError(TransactionHandler::rollback);
}
+ @Override
+ @Transactional
+ public Result createNewIndicatorTemplate(final IndicatorTemplate indicatorTemplate) {
+ return Result.tryCatch(() -> {
+
+ if (log.isDebugEnabled()) {
+ log.debug("Create new indicator template: {}", indicatorTemplate);
+ }
+
+ final Long examTemplatePK = indicatorTemplate.examTemplateId;
+ final ExamTemplateRecord examTemplateRec = this.examTemplateRecordMapper
+ .selectByPrimaryKey(examTemplatePK);
+ final String indicatorTemplatesJSON = examTemplateRec.getIndicatorTemplates();
+ final Collection indicators = (StringUtils.isNotBlank(indicatorTemplatesJSON))
+ ? this.jsonMapper.readValue(
+ indicatorTemplatesJSON,
+ new TypeReference>() {
+ })
+ : Collections.emptyList();
+
+ checkUniqueIndicatorName(indicatorTemplate, indicators);
+
+ final IndicatorTemplate newIndicatorTemplate = new IndicatorTemplate(
+ getNextIndicatorId(indicators),
+ indicatorTemplate);
+
+ final List newIndicators = new ArrayList<>(indicators);
+ newIndicators.add(newIndicatorTemplate);
+
+ final String newIndicatorTemplatesJSON = newIndicators.isEmpty()
+ ? StringUtils.EMPTY
+ : this.jsonMapper.writeValueAsString(newIndicators);
+
+ final ExamTemplateRecord newRecord = new ExamTemplateRecord(
+ examTemplatePK, null, null, null, null, null, null,
+ newIndicatorTemplatesJSON, null);
+
+ this.examTemplateRecordMapper.updateByPrimaryKeySelective(newRecord);
+
+ return newIndicatorTemplate;
+ })
+ .onError(TransactionHandler::rollback);
+ }
+
+ @Override
+ @Transactional
+ public Result saveIndicatorTemplate(final IndicatorTemplate indicatorTemplate) {
+ return Result.tryCatch(() -> {
+
+ if (log.isDebugEnabled()) {
+ log.debug("Save indicator template: {}", indicatorTemplate);
+ }
+
+ final Long examTemplatePK = indicatorTemplate.examTemplateId;
+ final ExamTemplateRecord examTemplateRec = this.examTemplateRecordMapper
+ .selectByPrimaryKey(examTemplatePK);
+ final String indicatorTemplatesJSON = examTemplateRec.getIndicatorTemplates();
+ final Collection indicators = (StringUtils.isNotBlank(indicatorTemplatesJSON))
+ ? this.jsonMapper.readValue(
+ indicatorTemplatesJSON,
+ new TypeReference>() {
+ })
+ : Collections.emptyList();
+
+ checkUniqueIndicatorName(indicatorTemplate, indicators);
+
+ final List newIndicators = indicators
+ .stream()
+ .map(i -> indicatorTemplate.id.equals(i.id) ? indicatorTemplate : i)
+ .collect(Collectors.toList());
+
+ final String newIndicatorTemplatesJSON = newIndicators.isEmpty()
+ ? StringUtils.EMPTY
+ : this.jsonMapper.writeValueAsString(newIndicators);
+
+ final ExamTemplateRecord newRecord = new ExamTemplateRecord(
+ examTemplatePK, null, null, null, null, null, null,
+ newIndicatorTemplatesJSON, null);
+
+ this.examTemplateRecordMapper.updateByPrimaryKeySelective(newRecord);
+
+ return indicatorTemplate;
+ })
+ .onError(TransactionHandler::rollback);
+ }
+
+ @Override
+ @Transactional
+ public Result deleteIndicatorTemplate(
+ final String examTemplateId,
+ final String indicatorTemplateId) {
+
+ return Result.tryCatch(() -> {
+
+ if (log.isDebugEnabled()) {
+ log.debug(
+ "Delete indicator template for exam template: {} indicator template id",
+ examTemplateId,
+ indicatorTemplateId);
+ }
+
+ final Long examTemplatePK = Long.valueOf(examTemplateId);
+ final ExamTemplateRecord examTemplateRec = this.examTemplateRecordMapper
+ .selectByPrimaryKey(examTemplatePK);
+ final String indicatorTemplatesJSON = examTemplateRec.getIndicatorTemplates();
+ final Collection indicators = (StringUtils.isNotBlank(indicatorTemplatesJSON))
+ ? this.jsonMapper.readValue(
+ indicatorTemplatesJSON,
+ new TypeReference>() {
+ })
+ : Collections.emptyList();
+
+ final List newIndicators = indicators.stream()
+ .filter(indicatorTemplate -> !indicatorTemplateId.equals(indicatorTemplate.getModelId()))
+ .collect(Collectors.toList());
+
+ final String newIndicatorTemplatesJSON = newIndicators.isEmpty()
+ ? StringUtils.EMPTY
+ : this.jsonMapper.writeValueAsString(newIndicators);
+
+ final ExamTemplateRecord newRecord = new ExamTemplateRecord(
+ examTemplatePK, null, null, null, null, null, null,
+ newIndicatorTemplatesJSON, null);
+
+ this.examTemplateRecordMapper.updateByPrimaryKeySelective(newRecord);
+
+ return new EntityKey(indicatorTemplateId, EntityType.INDICATOR);
+ })
+ .onError(TransactionHandler::rollback);
+ }
+
@Override
public Set getDependencies(final BulkAction bulkAction) {
return Collections.emptySet();
@@ -251,7 +396,9 @@ public class ExamTemplateDAOImpl implements ExamTemplateDAO {
public Result> delete(final Set all) {
return Result.tryCatch(() -> {
- log.info("Delete exam templates: {}", all);
+ if (log.isDebugEnabled()) {
+ log.debug("Delete exam templates: {}", all);
+ }
final List ids = extractListOfPKs(all);
if (ids == null || ids.isEmpty()) {
@@ -387,4 +534,24 @@ public class ExamTemplateDAOImpl implements ExamTemplateDAO {
}
}
+ private void checkUniqueIndicatorName(final IndicatorTemplate indicatorTemplate,
+ final Collection indicators) {
+ // check unique name
+ indicators.stream()
+ .filter(it -> Objects.equals(it.name, indicatorTemplate.name))
+ .findAny()
+ .ifPresent(it -> {
+ throw new FieldValidationException(
+ "name",
+ "indicatorTemplate:name:exists");
+ });
+ }
+
+ private long getNextIndicatorId(final Collection indicators) {
+ return indicators.stream()
+ .map(IndicatorTemplate::getId)
+ .max(Long::compare)
+ .orElse(-1L) + 1;
+ }
+
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java
index 23c6f586..dabfeda0 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/UserActivityLogDAOImpl.java
@@ -166,6 +166,15 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
return log(UserLogActivityType.DELETE, entity);
}
+ @Override
+ @Transactional
+ public Result logDelete(final EntityKey entityKey) {
+ return Result.tryCatch(() -> {
+ log(UserLogActivityType.DELETE, entityKey.entityType, entityKey.modelId, null);
+ return entityKey;
+ });
+ }
+
@Override
@Transactional
public Result logBulkAction(final EntityProcessingReport bulkActionReport) {
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupLmsAPITemplate.java
index ec71bb8e..0efaf594 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupLmsAPITemplate.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/mockup/MockupLmsAPITemplate.java
@@ -130,6 +130,13 @@ public class MockupLmsAPITemplate implements LmsAPITemplate {
DateTime.now(DateTimeZone.UTC).plus(6 * Constants.MINUTE_IN_MILLIS)
.toString(Constants.DEFAULT_DATE_TIME_FORMAT),
"http://lms.mockup.com/api/"));
+ this.mockups.add(new QuizData(
+ "quiz11", institutionId, lmsSetupId, lmsType, "Demo Quiz 11 (MOCKUP)",
+ "Starts in a minute and ends never",
+ DateTime.now(DateTimeZone.UTC).plus(Constants.MINUTE_IN_MILLIS)
+ .toString(Constants.DEFAULT_DATE_TIME_FORMAT),
+ null,
+ "http://lms.mockup.com/api/"));
}
@Override
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ExamConfigService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ExamConfigService.java
index 89c1dba7..41736016 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ExamConfigService.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ExamConfigService.java
@@ -35,6 +35,13 @@ public interface ExamConfigService {
* @throws FieldValidationException on validation exception */
void validate(ConfigurationTableValues tableValue) throws FieldValidationException;
+ /** Get the follow-up configuration identifier for a given configuration node identifier.
+ *
+ * @param examConfigNodeId the exam configuration node identifier
+ * @return Result refer to the follow-up configuration identifier of the given config node or to an error when
+ * happened */
+ Result getFollowupConfigurationId(final Long examConfigNodeId);
+
/** Used to export a specified SEB Exam Configuration as plain XML
* This exports the values of the follow-up configuration defined by a given
* ConfigurationNode (configurationNodeId)
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java
index 629e2c4b..b0ebf199 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java
@@ -128,6 +128,10 @@ public class ExamConfigServiceImpl implements ExamConfigService {
}
}
+ public Result getFollowupConfigurationId(final Long examConfigNodeId) {
+ return this.configurationDAO.getFollowupConfigurationId(examConfigNodeId);
+ }
+
@Override
public void exportPlainXML(
final OutputStream out,
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java
index 112a6e92..3d673263 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionCacheService.java
@@ -173,8 +173,12 @@ public class ExamSessionCacheService {
byteOut,
institutionId,
examId);
+ final Long followupId = this.sebExamConfigService
+ .getFollowupConfigurationId(configId)
+ .onError(error -> log.error("Failed to get follow-up id for config node: {}", configId, error))
+ .getOr(-1L);
- return new InMemorySEBConfig(configId, examId, byteOut.toByteArray());
+ return new InMemorySEBConfig(configId, followupId, examId, byteOut.toByteArray());
} catch (final Exception e) {
log.error("Unexpected error while getting default exam configuration for running exam; {}", examId, e);
@@ -182,6 +186,19 @@ public class ExamSessionCacheService {
}
}
+ public boolean isUpToDate(final InMemorySEBConfig inMemorySEBConfig) {
+ try {
+ final Long followupId = this.sebExamConfigService
+ .getFollowupConfigurationId(inMemorySEBConfig.configId)
+ .getOrThrow();
+
+ return followupId.equals(inMemorySEBConfig.follwupId);
+ } catch (final Exception e) {
+ log.error("Failed to check if InMemorySEBConfig is up to date for: {}", inMemorySEBConfig);
+ return true;
+ }
+ }
+
@CacheEvict(
cacheNames = CACHE_NAME_SEB_CONFIG_EXAM,
key = "#examId")
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java
index c5d335c2..5e4a4fb7 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java
@@ -161,7 +161,7 @@ public class ExamSessionControlTask implements DisposableBean {
.getOrThrow()
.stream()
.filter(exam -> exam.startTime.minus(this.examTimePrefix).isBefore(now))
- .filter(exam -> exam.endTime != null && exam.endTime.plus(this.examTimeSuffix).isAfter(now))
+ .filter(exam -> exam.endTime == null || exam.endTime.plus(this.examTimeSuffix).isAfter(now))
.flatMap(exam -> Result.skipOnError(this.examUpdateHandler.setRunning(exam, updateId)))
.collect(Collectors.toMap(Exam::getId, Exam::getName));
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
index 5ee1bb0c..2cb520c0 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java
@@ -285,7 +285,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
log.trace("Trying to get exam from InMemorySEBConfig");
}
- final InMemorySEBConfig sebConfigForExam = this.examSessionCacheService
+ InMemorySEBConfig sebConfigForExam = this.examSessionCacheService
.getDefaultSEBConfigForExam(connection.examId, institutionId);
if (sebConfigForExam == null) {
@@ -293,6 +293,23 @@ public class ExamSessionServiceImpl implements ExamSessionService {
return;
}
+ // for distributed setups check if cached config is still up to date. Flush and reload if not.
+ if (this.distributedSetup && !this.examSessionCacheService.isUpToDate(sebConfigForExam)) {
+
+ if (log.isDebugEnabled()) {
+ log.debug("Detected new version of exam configuration for exam {} ...flush cache", connection.examId);
+ }
+
+ this.examSessionCacheService.evictDefaultSEBConfig(connection.examId);
+ sebConfigForExam = this.examSessionCacheService
+ .getDefaultSEBConfigForExam(connection.examId, institutionId);
+ }
+
+ if (sebConfigForExam == null) {
+ log.error("Failed to get and cache InMemorySEBConfig for connection: {}", connection);
+ return;
+ }
+
try {
if (log.isTraceEnabled()) {
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InMemorySEBConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InMemorySEBConfig.java
index a648741c..35cea567 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InMemorySEBConfig.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/InMemorySEBConfig.java
@@ -11,12 +11,19 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
public final class InMemorySEBConfig {
public final Long configId;
+ public final Long follwupId;
public final Long examId;
private final byte[] data;
- protected InMemorySEBConfig(final Long configId, final Long examId, final byte[] data) {
+ protected InMemorySEBConfig(
+ final Long configId,
+ final Long follwupId,
+ final Long examId,
+ final byte[] data) {
+
super();
this.configId = configId;
+ this.follwupId = follwupId;
this.examId = examId;
this.data = data;
}
@@ -39,6 +46,7 @@ public final class InMemorySEBConfig {
int result = 1;
result = prime * result + ((this.configId == null) ? 0 : this.configId.hashCode());
result = prime * result + ((this.examId == null) ? 0 : this.examId.hashCode());
+ result = prime * result + ((this.follwupId == null) ? 0 : this.follwupId.hashCode());
return result;
}
@@ -61,7 +69,25 @@ public final class InMemorySEBConfig {
return false;
} else if (!this.examId.equals(other.examId))
return false;
+ if (this.follwupId == null) {
+ if (other.follwupId != null)
+ return false;
+ } else if (!this.follwupId.equals(other.follwupId))
+ return false;
return true;
}
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("InMemorySEBConfig [configId=");
+ builder.append(this.configId);
+ builder.append(", follwupId=");
+ builder.append(this.follwupId);
+ builder.append(", examId=");
+ builder.append(this.examId);
+ builder.append("]");
+ return builder.toString();
+ }
+
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java
index dfefafe5..4c223a93 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamTemplateController.java
@@ -8,7 +8,6 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -21,8 +20,6 @@ import javax.validation.Valid;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlTable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
@@ -48,7 +45,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
-import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamTemplateDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
@@ -59,12 +55,12 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_TEMPLATE_ENDPOINT)
public class ExamTemplateController extends EntityController {
- private static final Logger log = LoggerFactory.getLogger(ExamTemplateController.class);
+ private final ExamTemplateDAO examTemplateDAO;
protected ExamTemplateController(
final AuthorizationService authorization,
final BulkActionService bulkActionService,
- final EntityDAO entityDAO,
+ final ExamTemplateDAO entityDAO,
final UserActivityLogDAO userActivityLogDAO,
final PaginationService paginationService,
final BeanValidationService beanValidationService) {
@@ -76,6 +72,8 @@ public class ExamTemplateController extends EntityController indicators = new ArrayList<>(examTemplate.indicatorTemplates);
- indicators.add(newIndicator);
- final ExamTemplate newExamTemplate = new ExamTemplate(
- examTemplate.id,
- null, null, null, null, null, null,
- examTemplate.institutionalDefault,
- indicators,
- null);
-
- super.entityDAO
- .save(newExamTemplate)
- .getOrThrow();
-
- this.userActivityLogDAO.logCreate(newIndicator)
- .onError(error -> log.error("Failed to log indicator template creation: {}", newIndicator, error));
-
- return newIndicator;
}
@RequestMapping(
@@ -228,46 +202,11 @@ public class ExamTemplateController extends EntityController newIndicators = examTemplate.indicatorTemplates
- .stream()
- .map(i -> {
- if (modelId.equals(i.getModelId())) {
- return new IndicatorTemplate(
- modifyData.id,
- modifyData.examTemplateId,
- modifyData.name,
- (modifyData.type != null) ? modifyData.type : i.type,
- (modifyData.defaultColor != null) ? modifyData.defaultColor : i.defaultColor,
- (modifyData.defaultIcon != null) ? modifyData.defaultIcon : i.defaultIcon,
- (modifyData.tags != null) ? modifyData.tags : i.tags,
- (modifyData.thresholds != null) ? modifyData.thresholds : i.thresholds);
- } else {
- return i;
- }
- })
- .collect(Collectors.toList());
-
- final ExamTemplate newExamTemplate = new ExamTemplate(
- examTemplate.id,
- null, null, null, null, null, null,
- examTemplate.institutionalDefault,
- newIndicators,
- null);
-
- super.entityDAO
- .save(newExamTemplate)
- .getOrThrow();
-
- this.userActivityLogDAO.logModify(modifyData)
- .onError(error -> log.error("Failed to log indicator template modification: {}", modifyData, error));
-
- return modifyData;
}
@RequestMapping(
@@ -286,35 +225,9 @@ public class ExamTemplateController extends EntityController modelId.equals(i.getModelId()))
- .findFirst()
- .orElse(null);
-
- final List newIndicators = new ArrayList<>(examTemplate.indicatorTemplates);
- newIndicators.remove(toDelete);
-
- final ExamTemplate newExamTemplate = new ExamTemplate(
- examTemplate.id,
- null, null, null, null, null, null,
- examTemplate.institutionalDefault,
- newIndicators,
- null);
-
- super.entityDAO
- .save(newExamTemplate)
- .getOrThrow();
-
- this.userActivityLogDAO.logDelete(toDelete)
- .onError(error -> log.error("Failed to log indicator template modification: {}", toDelete, error));
-
- return new EntityKey(modelId, EntityType.INDICATOR);
}
@Override
diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/AdministrationAPIIntegrationTester.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/AdministrationAPIIntegrationTester.java
index 5722dd74..b053c2d8 100644
--- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/AdministrationAPIIntegrationTester.java
+++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/AdministrationAPIIntegrationTester.java
@@ -15,6 +15,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import java.util.Collection;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -248,11 +249,12 @@ public abstract class AdministrationAPIIntegrationTester {
}
protected String getOrderedUUIDs(final Collection extends Entity> list) {
- return list
+ final List l = list
.stream()
.map(userInfo -> userInfo.getModelId())
- .collect(Collectors.toList())
- .toString();
+ .collect(Collectors.toList());
+ l.sort((s1, s2) -> s1.compareTo(s2));
+ return l.toString();
}
}
diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/QuizDataTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/QuizDataTest.java
index f0d9f4a6..b1f7d465 100644
--- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/QuizDataTest.java
+++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/QuizDataTest.java
@@ -59,7 +59,7 @@ public class QuizDataTest extends AdministrationAPIIntegrationTester {
});
assertNotNull(quizzes);
- assertTrue(quizzes.content.size() == 8);
+ assertTrue(quizzes.content.size() == 9);
// for the inactive LmsSetup we should'nt get any quizzes
quizzes = new RestAPITestHelper()
@@ -109,7 +109,7 @@ public class QuizDataTest extends AdministrationAPIIntegrationTester {
});
assertNotNull(quizzes);
- assertTrue(quizzes.content.size() == 8);
+ assertTrue(quizzes.content.size() == 9);
// but for the now active lmsSetup2 we should get the quizzes
quizzes = new RestAPITestHelper()
@@ -120,7 +120,7 @@ public class QuizDataTest extends AdministrationAPIIntegrationTester {
});
assertNotNull(quizzes);
- assertTrue(quizzes.content.size() == 8);
+ assertTrue(quizzes.content.size() == 9);
}
@Test
diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java
index 5d3b8495..272101d9 100644
--- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java
+++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java
@@ -268,7 +268,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
assertTrue(userInfos.numberOfPages == 1);
assertNotNull(userInfos.content);
assertTrue(userInfos.content.size() == 3);
- assertEquals("[user5, user2, user1]", getOrderedUUIDs(userInfos.content));
+ assertEquals("[user1, user2, user5]", getOrderedUUIDs(userInfos.content));
}
@Test
@@ -347,7 +347,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
assertTrue(userInfos.numberOfPages == 2);
assertNotNull(userInfos.content);
assertTrue(userInfos.content.size() == 3);
- assertEquals("[user7, user6, user4]", getOrderedUUIDs(userInfos.content));
+ assertEquals("[user4, user6, user7]", getOrderedUUIDs(userInfos.content));
}
@Test