SEBSERV-191 finished back-end implementation
This commit is contained in:
parent
2f2a3670b7
commit
c414586fec
6 changed files with 152 additions and 27 deletions
|
@ -197,6 +197,8 @@ public final class API {
|
|||
|
||||
public static final String SEB_CLIENT_EVENT_ENDPOINT = "/seb-client-event";
|
||||
public static final String SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT = "/search";
|
||||
public static final String SEB_CLIENT_EVENT_EXPORT_PATH_SEGMENT = "/export";
|
||||
public static final String SEB_CLIENT_EVENT_EXPORT_TYPE = "exportType";
|
||||
public static final String SEB_CLIENT_EVENT_EXTENDED_PAGE_ENDPOINT = SEB_CLIENT_EVENT_ENDPOINT
|
||||
+ SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT;
|
||||
|
||||
|
|
|
@ -666,4 +666,11 @@ public final class Utils {
|
|||
}
|
||||
}
|
||||
|
||||
public static String toCSVString(final String text) {
|
||||
if (StringUtils.isBlank(text)) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
return Constants.DOUBLE_QUOTE + text.replace("\"", "\"\"") + Constants.DOUBLE_QUOTE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,13 +10,11 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.exam;
|
|||
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.ExportType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
|
@ -30,7 +28,6 @@ public interface SEBClientEventAdminService {
|
|||
OutputStream output,
|
||||
FilterMap filterMap,
|
||||
String sort,
|
||||
final Predicate<ClientEvent> predicate,
|
||||
ExportType exportType,
|
||||
boolean includeConnectionDetails,
|
||||
boolean includeExamDetails);
|
||||
|
|
|
@ -13,11 +13,11 @@ import java.util.Arrays;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
@ -31,14 +31,17 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
|||
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport.ErrorEntry;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.ExportType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
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.ClientEventRecordDynamicSqlSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
|
||||
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.impl.SEBServerUser;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.SEBClientEventAdminService;
|
||||
|
@ -55,16 +58,19 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
|
|||
private final ClientEventDAO clientEventDAO;
|
||||
private final SEBClientEventExportTransactionHandler sebClientEventExportTransactionHandler;
|
||||
private final EnumMap<ExportType, SEBClientEventExporter> exporter;
|
||||
private final AuthorizationService authorizationService;
|
||||
|
||||
public SEBClientEventAdminServiceImpl(
|
||||
final PaginationService paginationService,
|
||||
final ClientEventDAO clientEventDAO,
|
||||
final SEBClientEventExportTransactionHandler sebClientEventExportTransactionHandler,
|
||||
final Collection<SEBClientEventExporter> exporter) {
|
||||
final Collection<SEBClientEventExporter> exporter,
|
||||
final AuthorizationService authorizationService) {
|
||||
|
||||
this.paginationService = paginationService;
|
||||
this.clientEventDAO = clientEventDAO;
|
||||
this.sebClientEventExportTransactionHandler = sebClientEventExportTransactionHandler;
|
||||
this.authorizationService = authorizationService;
|
||||
|
||||
this.exporter = new EnumMap<>(ExportType.class);
|
||||
exporter.forEach(exp -> this.exporter.putIfAbsent(exp.exportType(), exp));
|
||||
|
@ -102,7 +108,6 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
|
|||
final OutputStream output,
|
||||
final FilterMap filterMap,
|
||||
final String sort,
|
||||
final Predicate<ClientEvent> predicate,
|
||||
final ExportType exportType,
|
||||
final boolean includeConnectionDetails,
|
||||
final boolean includeExamDetails) {
|
||||
|
@ -111,7 +116,7 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
|
|||
this.exporter.get(exportType),
|
||||
includeConnectionDetails,
|
||||
includeExamDetails,
|
||||
new Pager(filterMap, sort, predicate),
|
||||
new Pager(filterMap, sort),
|
||||
output)
|
||||
.run();
|
||||
|
||||
|
@ -147,17 +152,28 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
|
|||
|
||||
public void run() {
|
||||
|
||||
final SEBServerUser currentUser = SEBClientEventAdminServiceImpl.this.authorizationService
|
||||
.getUserService()
|
||||
.getCurrentUser();
|
||||
final EnumSet<UserRole> userRoles = currentUser.getUserRoles();
|
||||
final boolean isSupporterOnly = userRoles.size() == 1 && userRoles.contains(UserRole.EXAM_SUPPORTER);
|
||||
|
||||
// first stream header line
|
||||
this.exporter.streamHeader(this.output, this.includeConnectionDetails, this.includeExamDetails);
|
||||
|
||||
// then batch with the pager and stream line per line
|
||||
while (this.pager.hasNext()) {
|
||||
this.pager.next().forEach(rec -> {
|
||||
this.exporter.streamData(
|
||||
this.output,
|
||||
rec,
|
||||
this.includeConnectionDetails ? getConnection(rec.getClientConnectionId()) : null,
|
||||
this.includeExamDetails ? getExam(rec.getClientConnectionId()) : null);
|
||||
|
||||
final Exam exam = getExam(rec.getClientConnectionId());
|
||||
|
||||
if (!isSupporterOnly || exam.isOwner(currentUser.uuid())) {
|
||||
this.exporter.streamData(
|
||||
this.output,
|
||||
rec,
|
||||
this.includeConnectionDetails ? getConnection(rec.getClientConnectionId()) : null,
|
||||
this.includeExamDetails ? getExam(rec.getClientConnectionId()) : null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -195,7 +211,6 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
|
|||
|
||||
private final FilterMap filterMap;
|
||||
private final String sort;
|
||||
private final Predicate<ClientEvent> predicate;
|
||||
|
||||
private int pageNumber = 0;
|
||||
private final int pageSize = 100;
|
||||
|
@ -204,12 +219,10 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
|
|||
|
||||
public Pager(
|
||||
final FilterMap filterMap,
|
||||
final String sort,
|
||||
final Predicate<ClientEvent> predicate) {
|
||||
final String sort) {
|
||||
|
||||
this.filterMap = filterMap;
|
||||
this.sort = sort;
|
||||
this.predicate = predicate;
|
||||
|
||||
fetchNext();
|
||||
}
|
||||
|
@ -235,7 +248,7 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
|
|||
this.sort,
|
||||
ClientEventRecordDynamicSqlSupport.clientEventRecord.name(),
|
||||
() -> SEBClientEventAdminServiceImpl.this.sebClientEventExportTransactionHandler
|
||||
.allMatching(this.filterMap, this.predicate))
|
||||
.allMatching(this.filterMap, Utils.truePredicate()))
|
||||
.getOrThrow();
|
||||
|
||||
this.pageNumber++;
|
||||
|
|
|
@ -8,16 +8,25 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.ExportType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.SEBClientEventExporter;
|
||||
|
||||
public class SEBClientEventCSVExporter implements SEBClientEventExporter {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SEBClientEventCSVExporter.class);
|
||||
|
||||
@Override
|
||||
public ExportType exportType() {
|
||||
return ExportType.CSV;
|
||||
|
@ -29,7 +38,24 @@ public class SEBClientEventCSVExporter implements SEBClientEventExporter {
|
|||
final boolean includeConnectionDetails,
|
||||
final boolean includeExamDetails) {
|
||||
|
||||
// TODO
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("Event Type,Message,Value,Client Time (UTC),Server Time (UTC)");
|
||||
|
||||
if (includeConnectionDetails) {
|
||||
builder.append(",User Session-ID,Client Machine,Connection Status,Connection Token");
|
||||
}
|
||||
|
||||
if (includeExamDetails) {
|
||||
builder.append("Exam Name,Exam Description,Exam Type,Start Time (LMS),End Time (LMS)");
|
||||
}
|
||||
|
||||
builder.append(Constants.CARRIAGE_RETURN);
|
||||
|
||||
try {
|
||||
output.write(Utils.toByteArray(builder));
|
||||
} catch (final IOException e) {
|
||||
log.error("Failed to stream header: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -39,11 +65,37 @@ public class SEBClientEventCSVExporter implements SEBClientEventExporter {
|
|||
final ClientConnectionRecord connectionData,
|
||||
final Exam examData) {
|
||||
|
||||
// TODO
|
||||
}
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final EventType type = EventType.byId(eventData.getType());
|
||||
|
||||
private String toCSVString(final String text) {
|
||||
builder.append(type.name());
|
||||
builder.append(Utils.toCSVString(eventData.getText()));
|
||||
builder.append(eventData.getNumericValue());
|
||||
builder.append(Utils.toDateTimeUTC(eventData.getClientTime()));
|
||||
builder.append(Utils.toDateTimeUTC(eventData.getServerTime()));
|
||||
|
||||
if (connectionData != null) {
|
||||
builder.append(Utils.toCSVString(connectionData.getExamUserSessionId()));
|
||||
builder.append(Utils.toCSVString(connectionData.getClientAddress()));
|
||||
builder.append(connectionData.getStatus());
|
||||
builder.append(connectionData.getConnectionToken());
|
||||
}
|
||||
|
||||
if (examData != null) {
|
||||
builder.append(Utils.toCSVString(examData.getName()));
|
||||
builder.append(Utils.toCSVString(examData.getDescription()));
|
||||
builder.append(examData.getType().name());
|
||||
builder.append(examData.getStartTime());
|
||||
builder.append(examData.getEndTime());
|
||||
}
|
||||
|
||||
builder.append(Constants.CARRIAGE_RETURN);
|
||||
|
||||
try {
|
||||
output.write(Utils.toByteArray(builder));
|
||||
} catch (final IOException e) {
|
||||
log.error("Failed to stream header: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,13 +8,19 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
@ -31,6 +37,7 @@ import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
|
|||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Page;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.ExportType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
|
@ -54,6 +61,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
|
|||
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_EVENT_ENDPOINT)
|
||||
public class ClientEventController extends ReadonlyEntityController<ClientEvent, ClientEvent> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ClientEventController.class);
|
||||
|
||||
private final ExamDAO examDao;
|
||||
private final ClientEventDAO clientEventDAO;
|
||||
private final SEBClientEventAdminService sebClientEventAdminService;
|
||||
|
@ -138,6 +147,49 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
|
|||
.getOrThrow();
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.SEB_CLIENT_EVENT_EXPORT_PATH_SEGMENT,
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||
public void exportEvents(
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||
@RequestParam(name = API.SEB_CLIENT_EVENT_EXPORT_TYPE, required = true) final ExportType type,
|
||||
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
|
||||
@RequestParam final MultiValueMap<String, String> allRequestParams,
|
||||
final HttpServletRequest request,
|
||||
final HttpServletResponse response) throws IOException {
|
||||
|
||||
// at least current user must have base read access for specified entity type within its own institution
|
||||
checkReadPrivilege(institutionId);
|
||||
|
||||
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
|
||||
populateFilterMap(filterMap, institutionId, sort);
|
||||
|
||||
final ServletOutputStream outputStream = response.getOutputStream();
|
||||
|
||||
try {
|
||||
|
||||
this.sebClientEventAdminService.exportSEBClientLogs(
|
||||
outputStream,
|
||||
filterMap,
|
||||
sort,
|
||||
type,
|
||||
false,
|
||||
false);
|
||||
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
} catch (final Exception e) {
|
||||
log.error("Unexpected error while trying to export SEB client logs: ", e);
|
||||
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
} finally {
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<EntityDependency> getDependencies(
|
||||
final String modelId,
|
||||
|
@ -154,12 +206,14 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
|
|||
|
||||
@Override
|
||||
protected Result<ClientEvent> checkReadAccess(final ClientEvent entity) {
|
||||
final EnumSet<UserRole> userRoles = this.authorization
|
||||
.getUserService()
|
||||
.getCurrentUser()
|
||||
.getUserRoles();
|
||||
final boolean isSupporterOnly = userRoles.size() == 1 && userRoles.contains(UserRole.EXAM_SUPPORTER);
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
final EnumSet<UserRole> userRoles = this.authorization
|
||||
.getUserService()
|
||||
.getCurrentUser()
|
||||
.getUserRoles();
|
||||
final boolean isSupporterOnly = userRoles.size() == 1 && userRoles.contains(UserRole.EXAM_SUPPORTER);
|
||||
|
||||
if (isSupporterOnly) {
|
||||
// check owner grant be getting exam
|
||||
return super.checkReadAccess(entity)
|
||||
|
|
Loading…
Reference in a new issue