SEBSERV-191 gui implementation and download streaming
This commit is contained in:
parent
32d8c8a978
commit
06ce72a76f
20 changed files with 396 additions and 74 deletions
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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),
|
||||
|
||||
;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue