SEBSERV-10 back-end implementation

This commit is contained in:
anhefti 2020-07-08 12:43:38 +02:00
parent d7f79fb3cc
commit 2a0afe902a
17 changed files with 349 additions and 32 deletions

View file

@ -27,6 +27,7 @@ public final class API {
public static final String PARAM_PARENT_MODEL_ID = "parentModelId"; public static final String PARAM_PARENT_MODEL_ID = "parentModelId";
public static final String PARAM_ENTITY_TYPE = "entityType"; public static final String PARAM_ENTITY_TYPE = "entityType";
public static final String PARAM_BULK_ACTION_TYPE = "bulkActionType"; public static final String PARAM_BULK_ACTION_TYPE = "bulkActionType";
public static final String PARAM_BULK_ACTION_INCLUDES = "bulkActionIncludes";
public static final String PARAM_VIEW_ID = "viewId"; public static final String PARAM_VIEW_ID = "viewId";
public static final String PARAM_INSTRUCTION_TYPE = "instructionType"; public static final String PARAM_INSTRUCTION_TYPE = "instructionType";
public static final String PARAM_INSTRUCTION_ATTRIBUTES = "instructionAttributes"; public static final String PARAM_INSTRUCTION_ATTRIBUTES = "instructionAttributes";

View file

@ -28,6 +28,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
* / | \ \ * / | \ \
* LMS Setup | User-Account Client Configuration * LMS Setup | User-Account Client Configuration
* | | * | |
* | ______________+______________/
* |/ |/
* Exam Exam Configuration * Exam Exam Configuration
* |\ / * |\ /
* | Exam Config Mapping * | Exam Config Mapping

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
@ -35,6 +36,9 @@ public final class BulkAction {
public final EntityType sourceType; public final EntityType sourceType;
/** A Set of EntityKey defining all source-entities of the BulkAction */ /** A Set of EntityKey defining all source-entities of the BulkAction */
public final Set<EntityKey> sources; public final Set<EntityKey> sources;
/** A Set defining the types of dependencies to include into the bulk action
* Null means all dependencies are included (ignore) and empty means no dependencies are included */
public final EnumSet<EntityType> includeDependencies;
/** A Set of EntityKey containing collected depending entities during dependency collection and processing phase */ /** A Set of EntityKey containing collected depending entities during dependency collection and processing phase */
final Set<EntityKey> dependencies; final Set<EntityKey> dependencies;
/** A Set of EntityKey containing collected bulk action processing results during processing phase */ /** A Set of EntityKey containing collected bulk action processing results during processing phase */
@ -46,10 +50,19 @@ public final class BulkAction {
final BulkActionType type, final BulkActionType type,
final EntityType sourceType, final EntityType sourceType,
final Collection<EntityKey> sources) { final Collection<EntityKey> sources) {
this(type, sourceType, sources, null);
}
public BulkAction(
final BulkActionType type,
final EntityType sourceType,
final Collection<EntityKey> sources,
final EnumSet<EntityType> includeDependencies) {
this.type = type; this.type = type;
this.sourceType = sourceType; this.sourceType = sourceType;
this.sources = Utils.immutableSetOf(sources); this.sources = Utils.immutableSetOf(sources);
this.includeDependencies = includeDependencies;
this.dependencies = new LinkedHashSet<>(); this.dependencies = new LinkedHashSet<>();
this.result = new HashSet<>(); this.result = new HashSet<>();
@ -64,6 +77,10 @@ public final class BulkAction {
this(type, sourceType, (sources != null) ? Arrays.asList(sources) : Collections.emptyList()); this(type, sourceType, (sources != null) ? Arrays.asList(sources) : Collections.emptyList());
} }
public boolean includesDependencyType(final EntityType type) {
return this.includeDependencies == null || this.includeDependencies.contains(type);
}
public Set<EntityKey> getDependencies() { public Set<EntityKey> getDependencies() {
return Collections.unmodifiableSet(this.dependencies); return Collections.unmodifiableSet(this.dependencies);
} }

View file

@ -11,6 +11,8 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -37,6 +39,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
@WebServiceProfile @WebServiceProfile
public class BulkActionServiceImpl implements BulkActionService { public class BulkActionServiceImpl implements BulkActionService {
private final EnumMap<EntityType, EnumSet<EntityType>> directDependancyMap =
new EnumMap<>(EntityType.class);
private final Map<EntityType, BulkActionSupportDAO<?>> supporter; private final Map<EntityType, BulkActionSupportDAO<?>> supporter;
private final UserActivityLogDAO userActivityLogDAO; private final UserActivityLogDAO userActivityLogDAO;
private final ApplicationEventPublisher applicationEventPublisher; private final ApplicationEventPublisher applicationEventPublisher;
@ -55,6 +60,20 @@ public class BulkActionServiceImpl implements BulkActionService {
this.userActivityLogDAO = userActivityLogDAO; this.userActivityLogDAO = userActivityLogDAO;
this.applicationEventPublisher = applicationEventPublisher; this.applicationEventPublisher = applicationEventPublisher;
this.jsonMapper = jsonMapper; this.jsonMapper = jsonMapper;
this.directDependancyMap.put(EntityType.INSTITUTION, EnumSet.of(
EntityType.LMS_SETUP,
EntityType.SEB_CLIENT_CONFIGURATION,
EntityType.CONFIGURATION_NODE,
EntityType.USER));
this.directDependancyMap.put(EntityType.LMS_SETUP, EnumSet.of(
EntityType.EXAM));
this.directDependancyMap.put(EntityType.EXAM, EnumSet.of(
EntityType.EXAM_CONFIGURATION_MAP,
EntityType.INDICATOR,
EntityType.CLIENT_CONNECTION));
this.directDependancyMap.put(EntityType.CONFIGURATION_NODE,
EnumSet.noneOf(EntityType.class));
} }
@Override @Override
@ -172,6 +191,8 @@ public class BulkActionServiceImpl implements BulkActionService {
return dependantSupporterInHierarchicalOrder return dependantSupporterInHierarchicalOrder
.stream() .stream()
.filter(Objects::nonNull) .filter(Objects::nonNull)
.filter(dao -> action.includeDependencies == null ||
action.includeDependencies.contains(dao.entityType()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
default: default:
@ -191,13 +212,13 @@ public class BulkActionServiceImpl implements BulkActionService {
this.supporter.get(EntityType.EXAM_CONFIGURATION_MAP), this.supporter.get(EntityType.EXAM_CONFIGURATION_MAP),
this.supporter.get(EntityType.CLIENT_CONNECTION), this.supporter.get(EntityType.CLIENT_CONNECTION),
this.supporter.get(EntityType.CONFIGURATION_NODE)); this.supporter.get(EntityType.CONFIGURATION_NODE));
// case USER: case USER:
// return Arrays.asList( return Arrays.asList(
// this.supporter.get(EntityType.EXAM_CONFIGURATION_MAP), this.supporter.get(EntityType.EXAM),
// this.supporter.get(EntityType.EXAM), this.supporter.get(EntityType.INDICATOR),
// this.supporter.get(EntityType.INDICATOR), this.supporter.get(EntityType.CLIENT_CONNECTION),
// this.supporter.get(EntityType.CLIENT_CONNECTION), this.supporter.get(EntityType.CONFIGURATION_NODE),
// this.supporter.get(EntityType.CONFIGURATION_NODE)); this.supporter.get(EntityType.EXAM_CONFIGURATION_MAP));
case LMS_SETUP: case LMS_SETUP:
return Arrays.asList( return Arrays.asList(
this.supporter.get(EntityType.EXAM), this.supporter.get(EntityType.EXAM),
@ -218,6 +239,15 @@ public class BulkActionServiceImpl implements BulkActionService {
} }
private void checkProcessing(final BulkAction action) { private void checkProcessing(final BulkAction action) {
// complete this.directDependancyMap if needed
if (action.includeDependencies != null) {
this.directDependancyMap.entrySet().stream()
.forEach(entry -> {
if (action.includeDependencies.contains(entry.getKey())) {
action.includeDependencies.addAll(entry.getValue());
}
});
}
if (action.alreadyProcessed) { if (action.alreadyProcessed) {
throw new IllegalStateException("Given BulkAction has already been processed. Use a new one"); throw new IllegalStateException("Given BulkAction has already been processed. Use a new one");
} }

View file

@ -17,8 +17,11 @@ import org.springframework.cache.annotation.Cacheable;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
public interface ClientConnectionDAO extends EntityDAO<ClientConnection, ClientConnection> { public interface ClientConnectionDAO extends
EntityDAO<ClientConnection, ClientConnection>,
BulkActionSupportDAO<ClientConnection> {
String CONNECTION_TOKENS_CACHE = "CONNECTION_TOKENS_CACHE"; String CONNECTION_TOKENS_CACHE = "CONNECTION_TOKENS_CACHE";

View file

@ -8,13 +8,14 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualToWhenPresent; import static org.mybatis.dynamic.sql.SqlBuilder.*;
import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -23,6 +24,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
@ -34,7 +36,11 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionR
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientInstructionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientInstructionRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
@ -49,13 +55,16 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
private final ClientConnectionRecordMapper clientConnectionRecordMapper; private final ClientConnectionRecordMapper clientConnectionRecordMapper;
private final ClientEventRecordMapper clientEventRecordMapper; private final ClientEventRecordMapper clientEventRecordMapper;
private final ClientInstructionRecordMapper clientInstructionRecordMapper;
protected ClientConnectionDAOImpl( protected ClientConnectionDAOImpl(
final ClientConnectionRecordMapper clientConnectionRecordMapper, final ClientConnectionRecordMapper clientConnectionRecordMapper,
final ClientEventRecordMapper clientEventRecordMapper) { final ClientEventRecordMapper clientEventRecordMapper,
final ClientInstructionRecordMapper clientInstructionRecordMapper) {
this.clientConnectionRecordMapper = clientConnectionRecordMapper; this.clientConnectionRecordMapper = clientConnectionRecordMapper;
this.clientEventRecordMapper = clientEventRecordMapper; this.clientEventRecordMapper = clientEventRecordMapper;
this.clientInstructionRecordMapper = clientInstructionRecordMapper;
} }
@Override @Override
@ -171,6 +180,39 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.onError(TransactionHandler::rollback); .onError(TransactionHandler::rollback);
} }
@Override
public Set<EntityKey> getDependencies(final BulkAction bulkAction) {
// only for deletion
if (bulkAction.type == BulkActionType.ACTIVATE || bulkAction.type == BulkActionType.DEACTIVATE) {
return Collections.emptySet();
}
// only if included
if (!bulkAction.includesDependencyType(EntityType.CLIENT_CONNECTION)) {
return Collections.emptySet();
}
// define the select function in case of source type
Function<EntityKey, Result<Collection<EntityKey>>> selectionFunction;
switch (bulkAction.sourceType) {
case INSTITUTION:
selectionFunction = this::allIdsOfInstitution;
break;
case LMS_SETUP:
selectionFunction = this::allIdsOfLmsSetup;
case USER:
selectionFunction = this::allIdsOfUser;
break;
case EXAM:
selectionFunction = this::allIdsOfExam;
break;
default:
selectionFunction = key -> Result.of(Collections.emptyList()); //empty select function
break;
}
return getDependencies(bulkAction, selectionFunction);
}
@Override @Override
@Transactional @Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) { public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
@ -186,6 +228,23 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.build() .build()
.execute(); .execute();
// then delete all related client instructions
final List<String> connectionTokens = this.clientConnectionRecordMapper.selectByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.id,
SqlBuilder.isIn(ids))
.build()
.execute()
.stream()
.map(r -> r.getConnectionToken())
.collect(Collectors.toList());
this.clientInstructionRecordMapper.deleteByExample()
.where(
ClientInstructionRecordDynamicSqlSupport.connectionToken,
SqlBuilder.isIn(connectionTokens))
.build()
.execute();
// then delete all requested client-connections // then delete all requested client-connections
this.clientConnectionRecordMapper.deleteByExample() this.clientConnectionRecordMapper.deleteByExample()
.where( .where(
@ -256,7 +315,62 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
record.getVirtualClientAddress(), record.getVirtualClientAddress(),
record.getCreationTime()); record.getCreationTime());
}); });
}
private Result<Collection<EntityKey>> allIdsOfInstitution(final EntityKey institutionKey) {
return Result.tryCatch(() -> this.clientConnectionRecordMapper.selectIdsByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.institutionId,
isEqualTo(Long.parseLong(institutionKey.modelId)))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM))
.collect(Collectors.toList()));
}
private Result<Collection<EntityKey>> allIdsOfLmsSetup(final EntityKey lmsSetupKey) {
return Result.tryCatch(() -> this.clientConnectionRecordMapper.selectIdsByExample()
.leftJoin(ExamRecordDynamicSqlSupport.examRecord)
.on(
ExamRecordDynamicSqlSupport.id,
equalTo(ClientConnectionRecordDynamicSqlSupport.examId))
.where(
ExamRecordDynamicSqlSupport.lmsSetupId,
isEqualTo(Long.parseLong(lmsSetupKey.modelId)))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM))
.collect(Collectors.toList()));
}
private Result<Collection<EntityKey>> allIdsOfUser(final EntityKey userKey) {
return Result.tryCatch(() -> this.clientConnectionRecordMapper.selectIdsByExample()
.leftJoin(ExamRecordDynamicSqlSupport.examRecord)
.on(
ExamRecordDynamicSqlSupport.id,
equalTo(ClientConnectionRecordDynamicSqlSupport.examId))
.where(
ExamRecordDynamicSqlSupport.owner,
isEqualTo(userKey.modelId))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM))
.collect(Collectors.toList()));
}
private Result<Collection<EntityKey>> allIdsOfExam(final EntityKey examKey) {
return Result.tryCatch(() -> this.clientConnectionRecordMapper.selectIdsByExample()
.where(
ClientConnectionRecordDynamicSqlSupport.examId,
isEqualTo(Long.parseLong(examKey.modelId)))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM))
.collect(Collectors.toList()));
} }
} }

View file

@ -24,6 +24,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException; import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
@ -134,11 +135,24 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Set<EntityKey> getDependencies(final BulkAction bulkAction) { public Set<EntityKey> getDependencies(final BulkAction bulkAction) {
// only if included
if (!bulkAction.includesDependencyType(EntityType.CONFIGURATION_NODE)) {
return Collections.emptySet();
}
// define the select function in case of source type // define the select function in case of source type
final Function<EntityKey, Result<Collection<EntityKey>>> selectionFunction = Function<EntityKey, Result<Collection<EntityKey>>> selectionFunction =
(bulkAction.sourceType == EntityType.INSTITUTION) key -> Result.of(Collections.emptyList());
? this::allIdsOfInstitution
: key -> Result.of(Collections.emptyList()); // else : empty select function if (bulkAction.sourceType == EntityType.INSTITUTION) {
selectionFunction = this::allIdsOfInstitution;
}
// in case of user deletion with configuration dependency inclusion
if (bulkAction.sourceType == EntityType.USER &&
bulkAction.type == BulkActionType.HARD_DELETE) {
selectionFunction = this::allIdsOfUser;
}
return getDependencies(bulkAction, selectionFunction); return getDependencies(bulkAction, selectionFunction);
} }
@ -252,6 +266,18 @@ public class ConfigurationNodeDAOImpl implements ConfigurationNodeDAO {
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
private Result<Collection<EntityKey>> allIdsOfUser(final EntityKey userKey) {
return Result.tryCatch(() -> this.configurationNodeRecordMapper.selectIdsByExample()
.where(
ConfigurationNodeRecordDynamicSqlSupport.owner,
isEqualTo(userKey.modelId))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.CONFIGURATION_NODE))
.collect(Collectors.toList()));
}
private Result<ConfigurationNodeRecord> recordById(final Long id) { private Result<ConfigurationNodeRecord> recordById(final Long id) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
final ConfigurationNodeRecord record = this.configurationNodeRecordMapper final ConfigurationNodeRecord record = this.configurationNodeRecordMapper

View file

@ -28,8 +28,8 @@ import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType; import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
@ -281,9 +281,14 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Set<EntityKey> getDependencies(final BulkAction bulkAction) { public Set<EntityKey> getDependencies(final BulkAction bulkAction) {
// only deletion here
if (bulkAction.type == BulkActionType.ACTIVATE || bulkAction.type == BulkActionType.DEACTIVATE) { if (bulkAction.type == BulkActionType.ACTIVATE || bulkAction.type == BulkActionType.DEACTIVATE) {
return Collections.emptySet(); return Collections.emptySet();
} }
// only if included
if (!bulkAction.includesDependencyType(EntityType.EXAM_CONFIGURATION_MAP)) {
return Collections.emptySet();
}
// define the select function in case of source type // define the select function in case of source type
Function<EntityKey, Result<Collection<EntityKey>>> selectionFunction; Function<EntityKey, Result<Collection<EntityKey>>> selectionFunction;
@ -291,6 +296,9 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
case INSTITUTION: case INSTITUTION:
selectionFunction = this::allIdsOfInstitution; selectionFunction = this::allIdsOfInstitution;
break; break;
case USER:
selectionFunction = this::allIdsOfUser;
break;
case LMS_SETUP: case LMS_SETUP:
selectionFunction = this::allIdsOfLmsSetup; selectionFunction = this::allIdsOfLmsSetup;
break; break;
@ -424,6 +432,30 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
private Result<Collection<EntityKey>> allIdsOfUser(final EntityKey userKey) {
return Result.tryCatch(() -> {
final List<Long> examsIds = this.examRecordMapper.selectByExample()
.where(
ExamRecordDynamicSqlSupport.owner,
isEqualTo(userKey.modelId))
.build()
.execute()
.stream()
.map(r -> r.getId())
.collect(Collectors.toList());
return this.examConfigurationMapRecordMapper.selectIdsByExample()
.where(
ExamConfigurationMapRecordDynamicSqlSupport.examId,
isIn(examsIds))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
.collect(Collectors.toList());
});
}
private Result<Collection<EntityKey>> allIdsOfLmsSetup(final EntityKey lmsSetupKey) { private Result<Collection<EntityKey>> allIdsOfLmsSetup(final EntityKey lmsSetupKey) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample() return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample()
.leftJoin(ExamRecordDynamicSqlSupport.examRecord) .leftJoin(ExamRecordDynamicSqlSupport.examRecord)

View file

@ -588,6 +588,10 @@ public class ExamDAOImpl implements ExamDAO {
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Set<EntityKey> getDependencies(final BulkAction bulkAction) { public Set<EntityKey> getDependencies(final BulkAction bulkAction) {
// only if included
if (!bulkAction.includesDependencyType(EntityType.EXAM)) {
return Collections.emptySet();
}
// define the select function in case of source type // define the select function in case of source type
Function<EntityKey, Result<Collection<EntityKey>>> selectionFunction; Function<EntityKey, Result<Collection<EntityKey>>> selectionFunction;
@ -598,6 +602,9 @@ public class ExamDAOImpl implements ExamDAO {
case LMS_SETUP: case LMS_SETUP:
selectionFunction = this::allIdsOfLmsSetup; selectionFunction = this::allIdsOfLmsSetup;
break; break;
case USER:
selectionFunction = this::allIdsOfUser;
break;
default: default:
selectionFunction = key -> Result.of(Collections.emptyList()); //empty select function selectionFunction = key -> Result.of(Collections.emptyList()); //empty select function
break; break;
@ -651,6 +658,17 @@ public class ExamDAOImpl implements ExamDAO {
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
private Result<Collection<EntityKey>> allIdsOfUser(final EntityKey userKey) {
return Result.tryCatch(() -> this.examRecordMapper.selectIdsByExample()
.where(ExamRecordDynamicSqlSupport.owner,
isEqualTo(userKey.modelId))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM))
.collect(Collectors.toList()));
}
private Result<ExamRecord> recordById(final Long id) { private Result<ExamRecord> recordById(final Long id) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
final ExamRecord record = this.examRecordMapper.selectByPrimaryKey(id); final ExamRecord record = this.examRecordMapper.selectByPrimaryKey(id);

View file

@ -221,9 +221,14 @@ public class IndicatorDAOImpl implements IndicatorDAO {
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Set<EntityKey> getDependencies(final BulkAction bulkAction) { public Set<EntityKey> getDependencies(final BulkAction bulkAction) {
// only for deletion
if (bulkAction.type == BulkActionType.ACTIVATE || bulkAction.type == BulkActionType.DEACTIVATE) { if (bulkAction.type == BulkActionType.ACTIVATE || bulkAction.type == BulkActionType.DEACTIVATE) {
return Collections.emptySet(); return Collections.emptySet();
} }
// only if included
if (!bulkAction.includesDependencyType(EntityType.INDICATOR)) {
return Collections.emptySet();
}
// define the select function in case of source type // define the select function in case of source type
Function<EntityKey, Result<Collection<EntityKey>>> selectionFunction; Function<EntityKey, Result<Collection<EntityKey>>> selectionFunction;
@ -233,6 +238,8 @@ public class IndicatorDAOImpl implements IndicatorDAO {
break; break;
case LMS_SETUP: case LMS_SETUP:
selectionFunction = this::allIdsOfLmsSetup; selectionFunction = this::allIdsOfLmsSetup;
case USER:
selectionFunction = this::allIdsOfUser;
break; break;
case EXAM: case EXAM:
selectionFunction = this::allIdsOfExam; selectionFunction = this::allIdsOfExam;
@ -277,6 +284,22 @@ public class IndicatorDAOImpl implements IndicatorDAO {
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
private Result<Collection<EntityKey>> allIdsOfUser(final EntityKey userKey) {
return Result.tryCatch(() -> this.indicatorRecordMapper.selectIdsByExample()
.leftJoin(ExamRecordDynamicSqlSupport.examRecord)
.on(
ExamRecordDynamicSqlSupport.id,
equalTo(IndicatorRecordDynamicSqlSupport.examId))
.where(
ExamRecordDynamicSqlSupport.owner,
isEqualTo(userKey.modelId))
.build()
.execute()
.stream()
.map(id -> new EntityKey(id, EntityType.EXAM))
.collect(Collectors.toList()));
}
private Result<Collection<EntityKey>> allIdsOfExam(final EntityKey examKey) { private Result<Collection<EntityKey>> allIdsOfExam(final EntityKey examKey) {
return Result.tryCatch(() -> this.indicatorRecordMapper.selectIdsByExample() return Result.tryCatch(() -> this.indicatorRecordMapper.selectIdsByExample()
.where( .where(

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -52,7 +53,10 @@ public class ClientConnectionController extends ReadonlyEntityController<ClientC
} }
@Override @Override
public Collection<EntityKey> getDependencies(final String modelId, final BulkActionType bulkActionType) { public Collection<EntityKey> getDependencies(
final String modelId,
final BulkActionType bulkActionType,
final List<String> includes) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -114,7 +115,10 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
} }
@Override @Override
public Collection<EntityKey> getDependencies(final String modelId, final BulkActionType bulkActionType) { public Collection<EntityKey> getDependencies(
final String modelId,
final BulkActionType bulkActionType,
final List<String> includes) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.SqlTable;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -110,7 +111,10 @@ public class ConfigurationController extends ReadonlyEntityController<Configurat
} }
@Override @Override
public Collection<EntityKey> getDependencies(final String modelId, final BulkActionType bulkActionType) { public Collection<EntityKey> getDependencies(
final String modelId,
final BulkActionType bulkActionType,
final List<String> includes) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import javax.validation.Valid; import javax.validation.Valid;
@ -87,7 +88,9 @@ public class ConfigurationValueController extends EntityController<Configuration
} }
@Override @Override
public EntityProcessingReport hardDelete(final String modelId) { public EntityProcessingReport hardDelete(
final String modelId,
final List<String> includes) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View file

@ -10,7 +10,9 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -209,7 +211,8 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
produces = MediaType.APPLICATION_JSON_UTF8_VALUE) produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Collection<EntityKey> getDependencies( public Collection<EntityKey> getDependencies(
@PathVariable final String modelId, @PathVariable final String modelId,
@RequestParam(name = API.PARAM_BULK_ACTION_TYPE, required = true) final BulkActionType bulkActionType) { @RequestParam(name = API.PARAM_BULK_ACTION_TYPE, required = true) final BulkActionType bulkActionType,
@RequestParam(name = API.PARAM_BULK_ACTION_INCLUDES, required = false) final List<String> includes) {
this.entityDAO this.entityDAO
.byModelId(modelId) .byModelId(modelId)
@ -218,7 +221,8 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
final BulkAction bulkAction = new BulkAction( final BulkAction bulkAction = new BulkAction(
bulkActionType, bulkActionType,
this.entityDAO.entityType(), this.entityDAO.entityType(),
Arrays.asList(new EntityKey(modelId, this.entityDAO.entityType()))); Arrays.asList(new EntityKey(modelId, this.entityDAO.entityType())),
convertToEntityType(includes));
this.bulkActionService.collectDependencies(bulkAction); this.bulkActionService.collectDependencies(bulkAction);
return bulkAction.getDependencies(); return bulkAction.getDependencies();
@ -320,24 +324,48 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
path = API.MODEL_ID_VAR_PATH_SEGMENT, path = API.MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.DELETE, method = RequestMethod.DELETE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE) produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public EntityProcessingReport hardDelete(@PathVariable final String modelId) { public EntityProcessingReport hardDelete(
@PathVariable final String modelId,
@RequestParam(name = API.PARAM_BULK_ACTION_INCLUDES, required = false) final List<String> includes) {
return this.entityDAO.byModelId(modelId) return this.entityDAO.byModelId(modelId)
.flatMap(this::checkWriteAccess) .flatMap(this::checkWriteAccess)
.flatMap(this::validForDelete) .flatMap(this::validForDelete)
.flatMap(this::bulkDelete) .flatMap(entity -> bulkDelete(entity, convertToEntityType(includes)))
.flatMap(this::notifyDeleted) .flatMap(this::notifyDeleted)
.flatMap(pair -> this.logBulkAction(pair.b)) .flatMap(pair -> this.logBulkAction(pair.b))
.getOrThrow(); .getOrThrow();
} }
protected Result<Pair<T, EntityProcessingReport>> bulkDelete(final T entity) { protected EnumSet<EntityType> convertToEntityType(final List<String> includes) {
return Result.tryCatch(() -> new Pair<>( final EnumSet<EntityType> includeDependencies = (includes != null)
entity, ? EnumSet.copyOf(includes.stream().map(name -> {
this.bulkActionService.createReport(new BulkAction( try {
return EntityType.valueOf(name);
} catch (final Exception e) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toSet()))
: null;
return includeDependencies;
}
protected Result<Pair<T, EntityProcessingReport>> bulkDelete(
final T entity,
final EnumSet<EntityType> includeDependencies) {
final BulkAction bulkAction = new BulkAction(
BulkActionType.HARD_DELETE, BulkActionType.HARD_DELETE,
entity.entityType(), entity.entityType(),
new EntityName(entity.getModelId(), entity.entityType(), entity.getName()))) Arrays.asList(new EntityName(entity.getModelId(), entity.entityType(), entity.getName())),
includeDependencies);
return Result.tryCatch(() -> new Pair<>(
entity,
this.bulkActionService
.createReport(bulkAction)
.getOrThrow())); .getOrThrow()));
} }

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.SqlTable;
@ -162,13 +164,15 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
path = API.MODEL_ID_VAR_PATH_SEGMENT, path = API.MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.DELETE, method = RequestMethod.DELETE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE) produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public EntityProcessingReport hardDelete(@PathVariable final String modelId) { public EntityProcessingReport hardDelete(
@PathVariable final String modelId,
@RequestParam(name = API.PARAM_BULK_ACTION_INCLUDES, required = false) final List<String> includes) {
return this.entityDAO.byModelId(modelId) return this.entityDAO.byModelId(modelId)
.flatMap(this::checkWriteAccess) .flatMap(this::checkWriteAccess)
.flatMap(entity -> this.examConfigUpdateService.processExamConfigurationMappingChange( .flatMap(entity -> this.examConfigUpdateService.processExamConfigurationMappingChange(
entity, entity,
this::bulkDelete)) e -> bulkDelete(e, convertToEntityType(includes))))
.flatMap(this::notifyDeleted) .flatMap(this::notifyDeleted)
.flatMap(pair -> this.logBulkAction(pair.b)) .flatMap(pair -> this.logBulkAction(pair.b))
.getOrThrow(); .getOrThrow();

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid; import javax.validation.Valid;
@ -59,7 +61,9 @@ public abstract class ReadonlyEntityController<T extends Entity, M extends Entit
} }
@Override @Override
public EntityProcessingReport hardDelete(final String modelId) { public EntityProcessingReport hardDelete(
final String modelId,
final List<String> includes) {
throw new UnsupportedOperationException(ONLY_READ_ACCESS); throw new UnsupportedOperationException(ONLY_READ_ACCESS);
} }