SEBSERV-191 gui implementation and download streaming

This commit is contained in:
anhefti 2021-11-04 16:52:48 +01:00
parent 32d8c8a978
commit 06ce72a76f
20 changed files with 396 additions and 74 deletions

View file

@ -24,6 +24,8 @@ import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege;
/** Global Constants used in SEB Server web-service as well as in web-gui component */
public final class Constants {
public static final String FILE_EXT_CSV = ".csv";
public static final String DEFAULT_LANG_CODE = "en";
public static final String DEFAULT_TIME_ZONE_CODE = "UTC";
public static final String TOOLTIP_TEXT_KEY_SUFFIX = ".tooltip";

View file

@ -199,6 +199,8 @@ public final class API {
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_EXPORT_INCLUDE_CONNECTIONS = "includeConnectionDetails";
public static final String SEB_CLIENT_EVENT_EXPORT_INCLUDE_EXAMS = "includeExamDetails";
public static final String SEB_CLIENT_EVENT_EXTENDED_PAGE_ENDPOINT = SEB_CLIENT_EVENT_ENDPOINT
+ SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT;

View file

@ -262,6 +262,13 @@ public final class Result<T> {
}
}
public Result<T> onSuccess(final Consumer<T> handler) {
if (this.error == null) {
handler.accept(this.value);
}
return this;
}
/** Uses a given error handler to apply an error if there is one and returning itself again
* for further processing.
*

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.gui.content;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@ -15,6 +16,8 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.tomcat.util.buf.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.UrlLauncher;
import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -24,12 +27,15 @@ import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityName;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
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.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
@ -42,6 +48,8 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService;
import ch.ethz.seb.sebserver.gui.service.remote.download.SEBClientLogExport;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetClientEventNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
@ -84,23 +92,29 @@ public class SEBClientEvents implements TemplateComposer {
private final ResourceService resourceService;
private final RestService restService;
private final I18nSupport i18nSupport;
private final DownloadService downloadService;
private final SEBClientEventDetailsPopup sebClientEventDetailsPopup;
private final SEBClientEventDeletePopup sebClientEventDeletePopup;
private final int pageSize;
private final String exportFileName;
public SEBClientEvents(
final PageService pageService,
final DownloadService downloadService,
final SEBClientEventDetailsPopup sebClientEventDetailsPopup,
final SEBClientEventDeletePopup sebClientEventDeletePopup,
@Value("${sebserver.gui.seb.client.logs.export.filename:SEBClientLogs}") final String exportFileName,
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
this.pageService = pageService;
this.downloadService = downloadService;
this.resourceService = pageService.getResourceService();
this.restService = this.resourceService.getRestService();
this.i18nSupport = this.resourceService.getI18nSupport();
this.sebClientEventDetailsPopup = sebClientEventDetailsPopup;
this.sebClientEventDeletePopup = sebClientEventDeletePopup;
this.pageSize = pageSize;
this.exportFileName = exportFileName;
this.examFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
@ -219,12 +233,52 @@ public class SEBClientEvents implements TemplateComposer {
.noEventPropagation()
.publish(false)
.newAction(ActionDefinition.LOGS_SEB_CLIENT_EXPORT_CSV)
.withExec(action -> this.exportLogs(action, ExportType.CSV, table))
.noEventPropagation()
.publishIf(() -> writeGrant, table.hasAnyContent())
.newAction(ActionDefinition.LOGS_SEB_CLIENT_DELETE_ALL)
.withExec(action -> this.getOpenDelete(action, table.getFilterCriteria()))
.noEventPropagation()
.publishIf(() -> writeGrant, table.hasAnyContent());
}
private PageAction exportLogs(
final PageAction action,
final ExportType type,
final EntityTable<ExtendedClientEvent> table) {
try {
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
final String fileName = this.exportFileName
+ Constants.UNDERLINE
+ this.i18nSupport.formatDisplayDate(Utils.getMillisecondsNow())
.replace(" ", "_")
.replace(".", "_")
+ Constants.FILE_EXT_CSV;
final Map<String, String> queryAttrs = new HashMap<>();
queryAttrs.put(API.SEB_CLIENT_EVENT_EXPORT_TYPE, type.name());
final String sortAttr = table.getSortOrder().encode(table.getSortColumn());
queryAttrs.put(Page.ATTR_SORT, sortAttr);
table.getFilterCriteria().forEach((name, value) -> queryAttrs.put(name, value.get(0)));
final String downloadURL = this.downloadService
.createDownloadURL(
SEBClientLogExport.class,
fileName,
queryAttrs);
urlLauncher.openURL(downloadURL);
} catch (final Exception e) {
log.error("Failed open export log download: ", e);
}
return action;
}
private PageAction getOpenDelete(
final PageAction pageAction,
final MultiValueMap<String, String> filterCriteria) {

View file

@ -770,6 +770,10 @@ public enum ActionDefinition {
ImageIcon.DELETE,
PageStateDefinitionImpl.SEB_CLIENT_LOGS,
ActionCategory.LOGS_SEB_CLIENT_LIST),
LOGS_SEB_CLIENT_EXPORT_CSV(
new LocTextKey("sebserver.seblogs.action.export.csv"),
ImageIcon.EXPORT,
ActionCategory.LOGS_SEB_CLIENT_LIST),
;

View file

@ -40,17 +40,8 @@ public abstract class AbstractDownloadServiceHandler implements DownloadServiceH
log.debug("download requested... trying to get needed parameter from request");
final String modelId = request.getParameter(API.PARAM_MODEL_ID);
if (StringUtils.isBlank(modelId)) {
log.error(
"Mandatory modelId parameter not found within HttpServletRequest. Download request is ignored");
return;
}
if (log.isDebugEnabled()) {
log.debug(
"Found modelId: {} for {} download. Trying to request webservice...",
modelId,
downloadFileName);
log.debug("Found modelId: {} for {} download.", modelId);
}
final String parentModelId = request.getParameter(API.PARAM_PARENT_MODEL_ID);
@ -67,7 +58,7 @@ public abstract class AbstractDownloadServiceHandler implements DownloadServiceH
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, header);
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
webserviceCall(modelId, parentModelId, response.getOutputStream());
webserviceCall(modelId, parentModelId, response.getOutputStream(), request);
} catch (final Exception e) {
log.error(
@ -76,6 +67,10 @@ public abstract class AbstractDownloadServiceHandler implements DownloadServiceH
}
}
protected abstract void webserviceCall(String modelId, String parentModelId, OutputStream downloadOut);
protected abstract void webserviceCall(
String modelId,
String parentModelId,
OutputStream downloadOut,
HttpServletRequest request);
}

View file

@ -8,9 +8,14 @@
package ch.ethz.seb.sebserver.gui.service.remote.download;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.service.ServiceHandler;
@ -19,12 +24,10 @@ import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
/** Implements a eclipse RAP ServiceHandler to handle downloads */
@Lazy
@ -73,6 +76,33 @@ public class DownloadService implements ServiceHandler {
.processDownload(request, response);
}
public String createDownloadURL(
final Class<? extends DownloadServiceHandler> handlerClass,
final String downloadFileName,
final Map<String, String> queryAttrs) {
final StringBuilder url = new StringBuilder()
.append(RWT.getServiceManager()
.getServiceHandlerUrl(DownloadService.DOWNLOAD_SERVICE_NAME))
.append(Constants.FORM_URL_ENCODED_SEPARATOR)
.append(DownloadService.HANDLER_NAME_PARAMETER)
.append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
.append(handlerClass.getSimpleName())
.append(Constants.FORM_URL_ENCODED_SEPARATOR)
.append(DownloadService.DOWNLOAD_FILE_NAME)
.append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
.append(downloadFileName);
queryAttrs.forEach((name, value) -> {
url.append(Constants.FORM_URL_ENCODED_SEPARATOR)
.append(name)
.append(Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR)
.append(Utils.encodeFormURL_UTF_8(value));
});
return url.toString();
}
public String createDownloadURL(
final String modelId,
final Class<? extends DownloadServiceHandler> handlerClass,

View file

@ -12,6 +12,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@ -35,14 +37,16 @@ public class SEBClientConfigDownload extends AbstractDownloadServiceHandler {
private final RestService restService;
protected SEBClientConfigDownload(
final RestService restService) {
protected SEBClientConfigDownload(final RestService restService) {
this.restService = restService;
}
@Override
protected void webserviceCall(final String modelId, final String parentModelId, final OutputStream downloadOut) {
protected void webserviceCall(
final String modelId,
final String parentModelId,
final OutputStream downloadOut,
final HttpServletRequest request) {
final RestCall<InputStream>.RestCallBuilder restCallBuilder = this.restService
.getBuilder(ExportClientConfig.class)

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.download;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ExportSEBClientLogs;
@Lazy
@Component
@GuiProfile
public class SEBClientLogExport extends AbstractDownloadServiceHandler {
private static final Logger log = LoggerFactory.getLogger(SEBClientLogExport.class);
private final RestService restService;
protected SEBClientLogExport(final RestService restService) {
this.restService = restService;
}
@Override
protected void webserviceCall(
final String modelId,
final String parentModelId,
final OutputStream downloadOut,
final HttpServletRequest request) {
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
final Enumeration<String> paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
final String param = paramNames.nextElement();
queryParams.add(param, String.valueOf(request.getParameter(param)));
}
final InputStream input = this.restService
.getBuilder(ExportSEBClientLogs.class)
.withQueryParams(queryParams)
.call()
.getOrThrow();
try {
IOUtils.copyLarge(input, downloadOut);
} catch (final IOException e) {
log.error(
"Unexpected error while streaming incoming config data from web-service to output-stream of download response: ",
e);
} finally {
try {
downloadOut.flush();
downloadOut.close();
} catch (final IOException e) {
log.error("Unexpected error while trying to close download output-stream");
}
}
}
}

View file

@ -12,6 +12,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,7 +39,11 @@ public class SEBExamConfigDownload extends AbstractDownloadServiceHandler {
}
@Override
protected void webserviceCall(final String modelId, final String parentModelId, final OutputStream downloadOut) {
protected void webserviceCall(
final String modelId,
final String parentModelId,
final OutputStream downloadOut,
final HttpServletRequest request) {
final InputStream input = this.restService.getBuilder(ExportExamConfig.class)
.withURIVariable(API.PARAM_MODEL_ID, modelId)

View file

@ -12,6 +12,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,7 +39,11 @@ public class SEBExamConfigPlaintextDownload extends AbstractDownloadServiceHandl
}
@Override
protected void webserviceCall(final String modelId, final String parentModelId, final OutputStream downloadOut) {
protected void webserviceCall(
final String modelId,
final String parentModelId,
final OutputStream downloadOut,
final HttpServletRequest request) {
final InputStream input = this.restService.getBuilder(ExportPlainXML.class)
.withURIVariable(API.PARAM_MODEL_ID, modelId)

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam;
import java.io.InputStream;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.AbstractExportCall;
@Lazy
@Component
@GuiProfile
public class ExportSEBClientLogs extends AbstractExportCall {
public ExportSEBClientLogs() {
super(new TypeKey<>(
CallType.UNDEFINED,
EntityType.CLIENT_EVENT,
new TypeReference<InputStream>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.SEB_CLIENT_EVENT_ENDPOINT
+ API.SEB_CLIENT_EVENT_EXPORT_PATH_SEGMENT);
}
}

View file

@ -243,6 +243,14 @@ public class EntityTable<ROW> {
return this.name;
}
public String getSortColumn() {
return this.sortColumn;
}
public PageSortOrder getSortOrder() {
return this.sortOrder;
}
public EntityType getEntityType() {
if (this.pageSupplier != null) {
return this.pageSupplier.getEntityType();

View file

@ -94,9 +94,9 @@ public interface PaginationService {
* @param tableName the name of the SQL table on which the pagination is applying to
* @param delegate a collection supplier the does the underling SQL query with specified pagination attributes
* @return Result refers to a Collection of specified type of objects or to an exception on error case */
<T> Result<Collection<T>> fetch(
final int pageNumber,
final int pageSize,
<T> Result<Page<T>> getPageOf(
final Integer pageNumber,
final Integer pageSize,
final String sort,
final String tableName,
final Supplier<Result<Collection<T>>> delegate);

View file

@ -148,16 +148,25 @@ public class PaginationServiceImpl implements PaginationService {
}
@Override
public <T> Result<Collection<T>> fetch(
final int pageNumber,
final int pageSize,
public <T> Result<Page<T>> getPageOf(
final Integer pageNumber,
final Integer pageSize,
final String sort,
final String tableName,
final Supplier<Result<Collection<T>>> delegate) {
return Result.tryCatch(() -> {
setPagination(pageNumber, pageSize, sort, tableName);
return delegate.get().getOrThrow();
//final SqlTable table = SqlTable.of(tableName);
final com.github.pagehelper.Page<Object> page =
setPagination(pageNumber, pageSize, sort, tableName);
final Collection<T> list = delegate.get().getOrThrow();
return new Page<>(
page.getPages(),
page.getPageNum(),
sort,
list);
});
}

View file

@ -17,6 +17,7 @@ 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.ExportType;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
public interface SEBClientEventAdminService {
@ -30,6 +31,7 @@ public interface SEBClientEventAdminService {
String sort,
ExportType exportType,
boolean includeConnectionDetails,
boolean includeExamDetails);
boolean includeExamDetails,
final SEBServerUser currentUser);
}

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collection;
@ -30,6 +31,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
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.Page;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.ExportType;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
@ -40,7 +42,6 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecord
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;
@ -58,19 +59,16 @@ 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 AuthorizationService authorizationService) {
final Collection<SEBClientEventExporter> exporter) {
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));
@ -110,15 +108,32 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
final String sort,
final ExportType exportType,
final boolean includeConnectionDetails,
final boolean includeExamDetails) {
final boolean includeExamDetails,
final SEBServerUser currentUser) {
new exportRunner(
this.exporter.get(exportType),
includeConnectionDetails,
includeExamDetails,
new Pager(filterMap, sort),
output)
.run();
try {
new exportRunner(
this.exporter.get(exportType),
includeConnectionDetails,
includeExamDetails,
new Pager(filterMap, sort),
output)
.run(currentUser);
} catch (final Exception e) {
log.error("Unexpected error during export SEB logs: ", e);
} finally {
try {
output.flush();
} catch (final IOException e) {
e.printStackTrace();
}
try {
output.close();
} catch (final IOException e) {
e.printStackTrace();
}
}
}
@ -146,15 +161,12 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
this.pager = pager;
this.output = output;
this.connectionCache = includeConnectionDetails ? new HashMap<>() : null;
this.examCache = includeExamDetails ? new HashMap<>() : null;
this.connectionCache = new HashMap<>();
this.examCache = new HashMap<>();
}
public void run() {
public void run(final SEBServerUser currentUser) {
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);
@ -182,7 +194,7 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
if (!this.connectionCache.containsKey(connectionId)) {
SEBClientEventAdminServiceImpl.this.sebClientEventExportTransactionHandler
.clientConnectionById(connectionId)
.map(e -> this.connectionCache.put(connectionId, e))
.onSuccess(rec -> this.connectionCache.put(rec.getId(), rec))
.onError(error -> log.error("Failed to get ClientConnectionRecord for id: {}",
connectionId,
error));
@ -197,7 +209,7 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
if (!this.examCache.containsKey(examId)) {
SEBClientEventAdminServiceImpl.this.sebClientEventExportTransactionHandler
.examById(examId)
.map(e -> this.examCache.put(examId, e))
.onSuccess(e -> this.examCache.put(examId, e))
.onError(error -> log.error("Failed to get Exam for id: {}",
examId,
error));
@ -212,7 +224,7 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
private final FilterMap filterMap;
private final String sort;
private int pageNumber = 0;
private int pageNumber = 1;
private final int pageSize = 100;
private Collection<ClientEventRecord> nextRecords;
@ -242,16 +254,22 @@ public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminServic
private void fetchNext() {
try {
this.nextRecords = SEBClientEventAdminServiceImpl.this.paginationService.fetch(
this.pageNumber,
this.pageSize,
this.sort,
ClientEventRecordDynamicSqlSupport.clientEventRecord.name(),
() -> SEBClientEventAdminServiceImpl.this.sebClientEventExportTransactionHandler
.allMatching(this.filterMap, Utils.truePredicate()))
final Page<ClientEventRecord> nextPage = SEBClientEventAdminServiceImpl.this.paginationService
.getPageOf(
this.pageNumber,
this.pageSize,
this.sort,
ClientEventRecordDynamicSqlSupport.clientEventRecord.name(),
() -> SEBClientEventAdminServiceImpl.this.sebClientEventExportTransactionHandler
.allMatching(this.filterMap, Utils.truePredicate()))
.getOrThrow();
this.pageNumber++;
if (nextPage.getPageNumber() == this.pageNumber) {
this.nextRecords = nextPage.content;
this.pageNumber++;
} else {
this.nextRecords = null;
}
} catch (final Exception e) {
log.error("Failed to fetch next batch: ", e);

View file

@ -13,16 +13,22 @@ import java.io.OutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
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.profile.WebServiceProfile;
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;
@Lazy
@Component
@WebServiceProfile
public class SEBClientEventCSVExporter implements SEBClientEventExporter {
private static final Logger log = LoggerFactory.getLogger(SEBClientEventCSVExporter.class);
@ -55,6 +61,12 @@ public class SEBClientEventCSVExporter implements SEBClientEventExporter {
output.write(Utils.toByteArray(builder));
} catch (final IOException e) {
log.error("Failed to stream header: ", e);
} finally {
try {
output.flush();
} catch (final IOException e) {
e.printStackTrace();
}
}
}
@ -69,23 +81,36 @@ public class SEBClientEventCSVExporter implements SEBClientEventExporter {
final EventType type = EventType.byId(eventData.getType());
builder.append(type.name());
builder.append(Constants.COMMA);
builder.append(Utils.toCSVString(eventData.getText()));
builder.append(eventData.getNumericValue());
builder.append(Constants.COMMA);
builder.append(eventData.getNumericValue() != null ? eventData.getNumericValue() : "");
builder.append(Constants.COMMA);
builder.append(Utils.toDateTimeUTC(eventData.getClientTime()));
builder.append(Constants.COMMA);
builder.append(Utils.toDateTimeUTC(eventData.getServerTime()));
if (connectionData != null) {
builder.append(Constants.COMMA);
builder.append(Utils.toCSVString(connectionData.getExamUserSessionId()));
builder.append(Constants.COMMA);
builder.append(Utils.toCSVString(connectionData.getClientAddress()));
builder.append(Constants.COMMA);
builder.append(connectionData.getStatus());
builder.append(Constants.COMMA);
builder.append(connectionData.getConnectionToken());
}
if (examData != null) {
builder.append(Constants.COMMA);
builder.append(Utils.toCSVString(examData.getName()));
builder.append(Constants.COMMA);
builder.append(Utils.toCSVString(examData.getDescription()));
builder.append(Constants.COMMA);
builder.append(examData.getType().name());
builder.append(Constants.COMMA);
builder.append(examData.getStartTime());
builder.append(Constants.COMMA);
builder.append(examData.getEndTime());
}
@ -95,6 +120,12 @@ public class SEBClientEventCSVExporter implements SEBClientEventExporter {
output.write(Utils.toByteArray(builder));
} catch (final IOException e) {
log.error("Failed to stream header: ", e);
} finally {
try {
output.flush();
} catch (final IOException e) {
e.printStackTrace();
}
}
}

View file

@ -9,6 +9,8 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
@ -17,6 +19,7 @@ import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.mybatis.dynamic.sql.SqlTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -157,6 +160,14 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
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 = API.SEB_CLIENT_EVENT_EXPORT_INCLUDE_CONNECTIONS,
required = false,
defaultValue = "true") final boolean includeConnectionDetails,
@RequestParam(
name = API.SEB_CLIENT_EVENT_EXPORT_INCLUDE_EXAMS,
required = false,
defaultValue = "false") final boolean includeExamDetails,
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
@RequestParam final MultiValueMap<String, String> allRequestParams,
final HttpServletRequest request,
@ -169,21 +180,31 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
populateFilterMap(filterMap, institutionId, sort);
final ServletOutputStream outputStream = response.getOutputStream();
PipedOutputStream pout;
PipedInputStream pin;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
final SEBServerUser currentUser = this.authorization
.getUserService()
.getCurrentUser();
this.sebClientEventAdminService.exportSEBClientLogs(
outputStream,
pout,
filterMap,
sort,
type,
false,
false);
includeConnectionDetails,
includeExamDetails,
currentUser);
IOUtils.copyLarge(pin, outputStream);
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());
outputStream.flush();
} finally {
outputStream.flush();
outputStream.close();

View file

@ -1867,6 +1867,7 @@ sebserver.seblogs.form.column.exam.endTime=End Time
sebserver.seblogs.form.column.exam.endTime.tooltip=The end date and time of the exam
sebserver.seblogs.action.delete=Delete Logs
sebserver.seblogs.action.export.csv=Export CSV
sebserver.seblogs.delete.form.title=Delete SEB Logs
sebserver.seblogs.delete.form.info=This will delete all SEB client logs from the current filtered list.<br/>Please check carefully if all SEB client logs from the list shall be deleted.<br/><br/>There are currently {0} logs within the list.
sebserver.seblogs.delete.action.delete=Delete All Logs