diff --git a/pom.xml b/pom.xml
index 82f3e9d6..1f6e7337 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
jar
- 1.2.3
+ 1.2.4
${sebserver-version}
${sebserver-version}
UTF-8
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 e523be9a..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
@@ -197,6 +197,10 @@ public final class API {
public static final String SEB_CLIENT_EVENT_ENDPOINT = "/seb-client-event";
public static final String SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT = "/search";
+ public static final String SEB_CLIENT_EVENT_EXPORT_PATH_SEGMENT = "/export";
+ public static final String SEB_CLIENT_EVENT_EXPORT_TYPE = "exportType";
+ public static final String SEB_CLIENT_EVENT_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/model/exam/Indicator.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java
index 6446140a..de2ce9bd 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/Indicator.java
@@ -128,9 +128,9 @@ public final class Indicator implements Entity {
this.thresholds = Utils.immutableListOf(thresholds);
}
- public Indicator(final Exam exam, final POSTMapper postParams) {
+ public Indicator(final Long examId, final POSTMapper postParams) {
this.id = null;
- this.examId = exam.id;
+ this.examId = examId;
this.name = postParams.getString(Domain.INDICATOR.ATTR_NAME);
this.type = postParams.getEnum(Domain.INDICATOR.ATTR_TYPE, IndicatorType.class);
this.defaultColor = postParams.getString(Domain.INDICATOR.ATTR_COLOR);
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientEvent.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientEvent.java
index 535c07c2..886bd5ed 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientEvent.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientEvent.java
@@ -68,6 +68,10 @@ public class ClientEvent implements Entity, IndicatorValueHolder {
}
}
+ public enum ExportType {
+ CSV
+ }
+
@JsonProperty(Domain.CLIENT_EVENT.ATTR_ID)
public final Long id;
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/gbl/util/Utils.java b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java
index 8d79797b..4d0c4671 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java
@@ -666,4 +666,19 @@ public final class Utils {
}
}
+ public static CharSequence trim(final CharSequence sequence) {
+ if (sequence == null) {
+ return null;
+ }
+
+ return StringUtils.trim(sequence.toString());
+ }
+
+ public static String toCSVString(final String text) {
+ if (StringUtils.isBlank(text)) {
+ return StringUtils.EMPTY;
+ }
+ return Constants.DOUBLE_QUOTE + text.replace("\"", "\"\"") + Constants.DOUBLE_QUOTE;
+ }
+
}
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ProctorRoomConnectionsPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ProctorRoomConnectionsPopup.java
index 87a367eb..46139f15 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ProctorRoomConnectionsPopup.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ProctorRoomConnectionsPopup.java
@@ -75,23 +75,25 @@ public class ProctorRoomConnectionsPopup {
.call()
.getOrThrow());
- this.pageService.staticListTableBuilder(connections, EntityType.CLIENT_CONNECTION)
+ final EntityTable compose =
+ this.pageService.staticListTableBuilder(connections, EntityType.CLIENT_CONNECTION)
- .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
- .withPaging(10)
+ .withEmptyMessage(EMPTY_LIST_TEXT_KEY)
+ .withPaging(10)
- .withColumn(new ColumnDefinition<>(
- Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
- TABLE_COLUMN_NAME,
- ClientConnection::getUserSessionId))
+ .withColumn(new ColumnDefinition<>(
+ Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
+ TABLE_COLUMN_NAME,
+ ClientConnection::getUserSessionId))
- .withDefaultAction(t -> actionBuilder
- .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
- .withParentEntityKey(parentEntityKey)
- .withExec(action -> showClientConnection(action, dialog, t))
- .create())
+ .withDefaultAction(t -> actionBuilder
+ .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
+ .withParentEntityKey(parentEntityKey)
+ .withExec(action -> showClientConnection(action, dialog, t))
+ .create())
- .compose(pageContext);
+ .compose(pageContext);
+ compose.reset();
}
private PageAction showClientConnection(
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 extends DownloadServiceHandler> 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 extends DownloadServiceHandler> 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/gui/table/StaticListPageSupplier.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/StaticListPageSupplier.java
index 145426a9..b7ace63f 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/table/StaticListPageSupplier.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/StaticListPageSupplier.java
@@ -106,8 +106,17 @@ public class StaticListPageSupplier implements PageSupplier {
if (numOfPages <= 0) {
return new Page<>(1, 1, this.column, this.list);
}
- final List subList = this.list.subList(this.pageNumber * this.pageSize,
- this.pageNumber * this.pageSize + this.pageSize);
+
+ int from = (this.pageNumber - 1) * this.pageSize;
+ if (from < 0) {
+ from = 0;
+ }
+ int to = (this.pageNumber - 1) * this.pageSize + this.pageSize;
+ if (to >= this.list.size()) {
+ to = this.list.size();
+ }
+
+ final List subList = this.list.subList(from, to);
return new Page<>(numOfPages, this.pageNumber, this.column, subList);
});
}
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 e01725aa..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
@@ -77,6 +77,30 @@ public interface PaginationService {
final String tableName,
final Supplier>> delegate);
+ /** Fetches a paged batch of objects
+ *
+ * NOTE: Paging always depends on SQL level. It depends on the collection given by the SQL select statement
+ * that is executed within MyBatis by using the MyBatis page service.
+ * Be aware that if the delegate that is given here applies an additional filter to the filtering done
+ * on SQL level, this will lead to paging with not fully filled pages or even to empty pages if the filter
+ * filters a lot of the entries given by the SQL statement away.
+ * So we recommend to apply as much of the filtering as possible on the SQL level and only if necessary and
+ * not avoidable, apply a additional filter on software-level that eventually filter one or two entities
+ * for a page.
+ *
+ * @param pageNumber the current page number
+ * @param pageSize the (full) size of the page
+ * @param sort the name of the sort column with a leading '-' for descending sort order
+ * @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> getPageOf(
+ final Integer pageNumber,
+ final Integer pageSize,
+ final String sort,
+ final String tableName,
+ final Supplier>> delegate);
+
/** Use this to build a current Page from a given list of objects.
*
* @param the Type if list entities
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 8da775b7..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
@@ -147,6 +147,29 @@ public class PaginationServiceImpl implements PaginationService {
});
}
+ @Override
+ public Result> getPageOf(
+ final Integer pageNumber,
+ final Integer pageSize,
+ final String sort,
+ final String tableName,
+ final Supplier>> delegate) {
+
+ return Result.tryCatch(() -> {
+ //final SqlTable table = SqlTable.of(tableName);
+ final com.github.pagehelper.Page