From 06ce72a76f35dd30df790aebaaf130c0e5af38d8 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 4 Nov 2021 16:52:48 +0100 Subject: [PATCH] SEBSERV-191 gui implementation and download streaming --- .../ch/ethz/seb/sebserver/gbl/Constants.java | 2 + .../ch/ethz/seb/sebserver/gbl/api/API.java | 2 + .../ethz/seb/sebserver/gbl/util/Result.java | 7 ++ .../gui/content/SEBClientEvents.java | 54 +++++++++++++ .../gui/content/action/ActionDefinition.java | 4 + .../AbstractDownloadServiceHandler.java | 19 ++--- .../remote/download/DownloadService.java | 48 ++++++++--- .../download/SEBClientConfigDownload.java | 12 ++- .../remote/download/SEBClientLogExport.java | 80 +++++++++++++++++++ .../download/SEBExamConfigDownload.java | 8 +- .../SEBExamConfigPlaintextDownload.java | 8 +- .../api/exam/ExportSEBClientLogs.java | 42 ++++++++++ .../seb/sebserver/gui/table/EntityTable.java | 8 ++ .../servicelayer/PaginationService.java | 6 +- .../servicelayer/PaginationServiceImpl.java | 19 +++-- .../exam/SEBClientEventAdminService.java | 4 +- .../impl/SEBClientEventAdminServiceImpl.java | 78 +++++++++++------- .../exam/impl/SEBClientEventCSVExporter.java | 33 +++++++- .../weblayer/api/ClientEventController.java | 35 ++++++-- src/main/resources/messages.properties | 1 + 20 files changed, 396 insertions(+), 74 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBClientLogExport.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/ExportSEBClientLogs.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java index 2ab1481f..8f026546 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java @@ -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"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index b89fb671..6a37a5f5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -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; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java index 5eba6fe6..fb67d943 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Result.java @@ -262,6 +262,13 @@ public final class Result { } } + public Result onSuccess(final Consumer 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. * diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEvents.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEvents.java index bc303c35..58d65b91 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEvents.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientEvents.java @@ -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 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 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 filterCriteria) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 759b47e1..b21f098c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -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), ; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/AbstractDownloadServiceHandler.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/AbstractDownloadServiceHandler.java index 593a4029..d78880a4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/AbstractDownloadServiceHandler.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/AbstractDownloadServiceHandler.java @@ -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); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/DownloadService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/DownloadService.java index 5ba0c74f..e9b2b64f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/DownloadService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/DownloadService.java @@ -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 handlerClass, + final String downloadFileName, + final Map 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 handlerClass, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBClientConfigDownload.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBClientConfigDownload.java index 55b49146..5ff68524 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBClientConfigDownload.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBClientConfigDownload.java @@ -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.RestCallBuilder restCallBuilder = this.restService .getBuilder(ExportClientConfig.class) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBClientLogExport.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBClientLogExport.java new file mode 100644 index 00000000..df46d6d4 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBClientLogExport.java @@ -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 queryParams = new LinkedMultiValueMap<>(); + + final Enumeration 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"); + } + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBExamConfigDownload.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBExamConfigDownload.java index 638322b7..bca25b02 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBExamConfigDownload.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBExamConfigDownload.java @@ -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) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBExamConfigPlaintextDownload.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBExamConfigPlaintextDownload.java index 4c72cc58..6d844c37 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBExamConfigPlaintextDownload.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBExamConfigPlaintextDownload.java @@ -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) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/ExportSEBClientLogs.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/ExportSEBClientLogs.java new file mode 100644 index 00000000..34aa0a68 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/ExportSEBClientLogs.java @@ -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() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.SEB_CLIENT_EVENT_ENDPOINT + + API.SEB_CLIENT_EVENT_EXPORT_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java index 613e4b39..0d0fcb3b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/EntityTable.java @@ -243,6 +243,14 @@ public class EntityTable { 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(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationService.java index 0d05a0fe..f35d938c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationService.java @@ -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 */ - Result> fetch( - final int pageNumber, - final int pageSize, + Result> getPageOf( + final Integer pageNumber, + final Integer pageSize, final String sort, final String tableName, final Supplier>> delegate); 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 9bb99708..6083b130 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 @@ -148,16 +148,25 @@ public class PaginationServiceImpl implements PaginationService { } @Override - public Result> fetch( - final int pageNumber, - final int pageSize, + public Result> getPageOf( + final Integer pageNumber, + final Integer pageSize, final String sort, final String tableName, final Supplier>> delegate) { return Result.tryCatch(() -> { - setPagination(pageNumber, pageSize, sort, tableName); - return delegate.get().getOrThrow(); + //final SqlTable table = SqlTable.of(tableName); + final com.github.pagehelper.Page page = + setPagination(pageNumber, pageSize, sort, tableName); + + final Collection list = delegate.get().getOrThrow(); + + return new Page<>( + page.getPages(), + page.getPageNum(), + sort, + list); }); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/SEBClientEventAdminService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/SEBClientEventAdminService.java index 9acacb4a..db63ff5a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/SEBClientEventAdminService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/SEBClientEventAdminService.java @@ -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); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/SEBClientEventAdminServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/SEBClientEventAdminServiceImpl.java index 759b5445..3c395bdd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/SEBClientEventAdminServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/SEBClientEventAdminServiceImpl.java @@ -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 exporter; - private final AuthorizationService authorizationService; public SEBClientEventAdminServiceImpl( final PaginationService paginationService, final ClientEventDAO clientEventDAO, final SEBClientEventExportTransactionHandler sebClientEventExportTransactionHandler, - final Collection exporter, - final AuthorizationService authorizationService) { + final Collection 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 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 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 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); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/SEBClientEventCSVExporter.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/SEBClientEventCSVExporter.java index 151b82c0..38f031a6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/SEBClientEventCSVExporter.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/SEBClientEventCSVExporter.java @@ -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(); + } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientEventController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientEventController.java index c7865d04..aab3faa7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientEventController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientEventController.java @@ -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 allRequestParams, final HttpServletRequest request, @@ -169,21 +180,31 @@ public class ClientEventController extends ReadonlyEntityControllerPlease check carefully if all SEB client logs from the list shall be deleted.

There are currently {0} logs within the list. sebserver.seblogs.delete.action.delete=Delete All Logs