Merge remote-tracking branch 'origin/SEBSERV-191' into dev-1.2
Conflicts: src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java
This commit is contained in:
		
						commit
						f1dc16b5ef
					
				
					 24 changed files with 1003 additions and 57 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 */
 | 
					/** Global Constants used in SEB Server web-service as well as in web-gui component */
 | 
				
			||||||
public final class Constants {
 | 
					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_LANG_CODE = "en";
 | 
				
			||||||
    public static final String DEFAULT_TIME_ZONE_CODE = "UTC";
 | 
					    public static final String DEFAULT_TIME_ZONE_CODE = "UTC";
 | 
				
			||||||
    public static final String TOOLTIP_TEXT_KEY_SUFFIX = ".tooltip";
 | 
					    public static final String TOOLTIP_TEXT_KEY_SUFFIX = ".tooltip";
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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_ENDPOINT = "/seb-client-event";
 | 
				
			||||||
    public static final String SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT = "/search";
 | 
					    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
 | 
					    public static final String SEB_CLIENT_EVENT_EXTENDED_PAGE_ENDPOINT = SEB_CLIENT_EVENT_ENDPOINT
 | 
				
			||||||
            + SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT;
 | 
					            + SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,6 +68,10 @@ public class ClientEvent implements Entity, IndicatorValueHolder {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public enum ExportType {
 | 
				
			||||||
 | 
					        CSV
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @JsonProperty(Domain.CLIENT_EVENT.ATTR_ID)
 | 
					    @JsonProperty(Domain.CLIENT_EVENT.ATTR_ID)
 | 
				
			||||||
    public final Long id;
 | 
					    public final Long id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
					    /** Uses a given error handler to apply an error if there is one and returning itself again
 | 
				
			||||||
     * for further processing.
 | 
					     * for further processing.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -674,4 +674,11 @@ public final class Utils {
 | 
				
			||||||
        return StringUtils.trim(sequence.toString());
 | 
					        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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package ch.ethz.seb.sebserver.gui.content;
 | 
					package ch.ethz.seb.sebserver.gui.content;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
import java.util.function.Consumer;
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
| 
						 | 
					@ -15,6 +16,8 @@ import java.util.function.Function;
 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.apache.tomcat.util.buf.StringUtils;
 | 
					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.eclipse.swt.widgets.Composite;
 | 
				
			||||||
import org.slf4j.Logger;
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
import org.slf4j.LoggerFactory;
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
| 
						 | 
					@ -24,12 +27,15 @@ import org.springframework.stereotype.Component;
 | 
				
			||||||
import org.springframework.util.MultiValueMap;
 | 
					import org.springframework.util.MultiValueMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.Constants;
 | 
					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.EntityType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
 | 
					import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
 | 
					import ch.ethz.seb.sebserver.gbl.model.Domain;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.EntityName;
 | 
					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.ClientConnection;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
 | 
					import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.ExportType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
 | 
					import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
 | 
					import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
 | 
					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.PageService.PageActionBuilder;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
 | 
					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.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.RestService;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetClientEventNames;
 | 
					import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetClientEventNames;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
 | 
					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 ResourceService resourceService;
 | 
				
			||||||
    private final RestService restService;
 | 
					    private final RestService restService;
 | 
				
			||||||
    private final I18nSupport i18nSupport;
 | 
					    private final I18nSupport i18nSupport;
 | 
				
			||||||
 | 
					    private final DownloadService downloadService;
 | 
				
			||||||
    private final SEBClientEventDetailsPopup sebClientEventDetailsPopup;
 | 
					    private final SEBClientEventDetailsPopup sebClientEventDetailsPopup;
 | 
				
			||||||
    private final SEBClientEventDeletePopup sebClientEventDeletePopup;
 | 
					    private final SEBClientEventDeletePopup sebClientEventDeletePopup;
 | 
				
			||||||
    private final int pageSize;
 | 
					    private final int pageSize;
 | 
				
			||||||
 | 
					    private final String exportFileName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public SEBClientEvents(
 | 
					    public SEBClientEvents(
 | 
				
			||||||
            final PageService pageService,
 | 
					            final PageService pageService,
 | 
				
			||||||
 | 
					            final DownloadService downloadService,
 | 
				
			||||||
            final SEBClientEventDetailsPopup sebClientEventDetailsPopup,
 | 
					            final SEBClientEventDetailsPopup sebClientEventDetailsPopup,
 | 
				
			||||||
            final SEBClientEventDeletePopup sebClientEventDeletePopup,
 | 
					            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) {
 | 
					            @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.pageService = pageService;
 | 
					        this.pageService = pageService;
 | 
				
			||||||
 | 
					        this.downloadService = downloadService;
 | 
				
			||||||
        this.resourceService = pageService.getResourceService();
 | 
					        this.resourceService = pageService.getResourceService();
 | 
				
			||||||
        this.restService = this.resourceService.getRestService();
 | 
					        this.restService = this.resourceService.getRestService();
 | 
				
			||||||
        this.i18nSupport = this.resourceService.getI18nSupport();
 | 
					        this.i18nSupport = this.resourceService.getI18nSupport();
 | 
				
			||||||
        this.sebClientEventDetailsPopup = sebClientEventDetailsPopup;
 | 
					        this.sebClientEventDetailsPopup = sebClientEventDetailsPopup;
 | 
				
			||||||
        this.sebClientEventDeletePopup = sebClientEventDeletePopup;
 | 
					        this.sebClientEventDeletePopup = sebClientEventDeletePopup;
 | 
				
			||||||
        this.pageSize = pageSize;
 | 
					        this.pageSize = pageSize;
 | 
				
			||||||
 | 
					        this.exportFileName = exportFileName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.examFilter = new TableFilterAttribute(
 | 
					        this.examFilter = new TableFilterAttribute(
 | 
				
			||||||
                CriteriaType.SINGLE_SELECTION,
 | 
					                CriteriaType.SINGLE_SELECTION,
 | 
				
			||||||
| 
						 | 
					@ -219,12 +233,52 @@ public class SEBClientEvents implements TemplateComposer {
 | 
				
			||||||
                .noEventPropagation()
 | 
					                .noEventPropagation()
 | 
				
			||||||
                .publish(false)
 | 
					                .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)
 | 
					                .newAction(ActionDefinition.LOGS_SEB_CLIENT_DELETE_ALL)
 | 
				
			||||||
                .withExec(action -> this.getOpenDelete(action, table.getFilterCriteria()))
 | 
					                .withExec(action -> this.getOpenDelete(action, table.getFilterCriteria()))
 | 
				
			||||||
                .noEventPropagation()
 | 
					                .noEventPropagation()
 | 
				
			||||||
                .publishIf(() -> writeGrant, table.hasAnyContent());
 | 
					                .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(
 | 
					    private PageAction getOpenDelete(
 | 
				
			||||||
            final PageAction pageAction,
 | 
					            final PageAction pageAction,
 | 
				
			||||||
            final MultiValueMap<String, String> filterCriteria) {
 | 
					            final MultiValueMap<String, String> filterCriteria) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -770,6 +770,10 @@ public enum ActionDefinition {
 | 
				
			||||||
            ImageIcon.DELETE,
 | 
					            ImageIcon.DELETE,
 | 
				
			||||||
            PageStateDefinitionImpl.SEB_CLIENT_LOGS,
 | 
					            PageStateDefinitionImpl.SEB_CLIENT_LOGS,
 | 
				
			||||||
            ActionCategory.LOGS_SEB_CLIENT_LIST),
 | 
					            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");
 | 
					            log.debug("download requested... trying to get needed parameter from request");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            final String modelId = request.getParameter(API.PARAM_MODEL_ID);
 | 
					            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()) {
 | 
					            if (log.isDebugEnabled()) {
 | 
				
			||||||
                log.debug(
 | 
					                log.debug("Found modelId: {} for {} download.", modelId);
 | 
				
			||||||
                        "Found modelId: {} for {} download. Trying to request webservice...",
 | 
					 | 
				
			||||||
                        modelId,
 | 
					 | 
				
			||||||
                        downloadFileName);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            final String parentModelId = request.getParameter(API.PARAM_PARENT_MODEL_ID);
 | 
					            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.setHeader(HttpHeaders.CONTENT_DISPOSITION, header);
 | 
				
			||||||
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
 | 
					            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            webserviceCall(modelId, parentModelId, response.getOutputStream());
 | 
					            webserviceCall(modelId, parentModelId, response.getOutputStream(), request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        } catch (final Exception e) {
 | 
					        } catch (final Exception e) {
 | 
				
			||||||
            log.error(
 | 
					            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;
 | 
					package ch.ethz.seb.sebserver.gui.service.remote.download;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.Constants;
 | 
					import java.util.Collection;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.API;
 | 
					import java.util.Map;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
 | 
					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.apache.commons.lang3.StringUtils;
 | 
				
			||||||
import org.eclipse.rap.rwt.RWT;
 | 
					import org.eclipse.rap.rwt.RWT;
 | 
				
			||||||
import org.eclipse.rap.rwt.service.ServiceHandler;
 | 
					import org.eclipse.rap.rwt.service.ServiceHandler;
 | 
				
			||||||
| 
						 | 
					@ -19,12 +24,10 @@ import org.slf4j.LoggerFactory;
 | 
				
			||||||
import org.springframework.context.annotation.Lazy;
 | 
					import org.springframework.context.annotation.Lazy;
 | 
				
			||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.servlet.http.HttpServletRequest;
 | 
					import ch.ethz.seb.sebserver.gbl.Constants;
 | 
				
			||||||
import javax.servlet.http.HttpServletResponse;
 | 
					import ch.ethz.seb.sebserver.gbl.api.API;
 | 
				
			||||||
import java.util.Collection;
 | 
					import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
 | 
				
			||||||
import java.util.Map;
 | 
					import ch.ethz.seb.sebserver.gbl.util.Utils;
 | 
				
			||||||
import java.util.function.Function;
 | 
					 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Implements a eclipse RAP ServiceHandler to handle downloads */
 | 
					/** Implements a eclipse RAP ServiceHandler to handle downloads */
 | 
				
			||||||
@Lazy
 | 
					@Lazy
 | 
				
			||||||
| 
						 | 
					@ -73,6 +76,33 @@ public class DownloadService implements ServiceHandler {
 | 
				
			||||||
                .processDownload(request, response);
 | 
					                .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(
 | 
					    public String createDownloadURL(
 | 
				
			||||||
            final String modelId,
 | 
					            final String modelId,
 | 
				
			||||||
            final Class<? extends DownloadServiceHandler> handlerClass,
 | 
					            final Class<? extends DownloadServiceHandler> handlerClass,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,8 @@ import java.io.IOException;
 | 
				
			||||||
import java.io.InputStream;
 | 
					import java.io.InputStream;
 | 
				
			||||||
import java.io.OutputStream;
 | 
					import java.io.OutputStream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.servlet.http.HttpServletRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.apache.commons.io.IOUtils;
 | 
					import org.apache.commons.io.IOUtils;
 | 
				
			||||||
import org.apache.commons.lang3.StringUtils;
 | 
					import org.apache.commons.lang3.StringUtils;
 | 
				
			||||||
import org.slf4j.Logger;
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
| 
						 | 
					@ -35,14 +37,16 @@ public class SEBClientConfigDownload extends AbstractDownloadServiceHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final RestService restService;
 | 
					    private final RestService restService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected SEBClientConfigDownload(
 | 
					    protected SEBClientConfigDownload(final RestService restService) {
 | 
				
			||||||
            final RestService restService) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.restService = restService;
 | 
					        this.restService = restService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @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
 | 
					        final RestCall<InputStream>.RestCallBuilder restCallBuilder = this.restService
 | 
				
			||||||
                .getBuilder(ExportClientConfig.class)
 | 
					                .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.InputStream;
 | 
				
			||||||
import java.io.OutputStream;
 | 
					import java.io.OutputStream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.servlet.http.HttpServletRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.apache.commons.io.IOUtils;
 | 
					import org.apache.commons.io.IOUtils;
 | 
				
			||||||
import org.slf4j.Logger;
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
import org.slf4j.LoggerFactory;
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
| 
						 | 
					@ -37,7 +39,11 @@ public class SEBExamConfigDownload extends AbstractDownloadServiceHandler {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @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)
 | 
					        final InputStream input = this.restService.getBuilder(ExportExamConfig.class)
 | 
				
			||||||
                .withURIVariable(API.PARAM_MODEL_ID, modelId)
 | 
					                .withURIVariable(API.PARAM_MODEL_ID, modelId)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,8 @@ import java.io.IOException;
 | 
				
			||||||
import java.io.InputStream;
 | 
					import java.io.InputStream;
 | 
				
			||||||
import java.io.OutputStream;
 | 
					import java.io.OutputStream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.servlet.http.HttpServletRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.apache.commons.io.IOUtils;
 | 
					import org.apache.commons.io.IOUtils;
 | 
				
			||||||
import org.slf4j.Logger;
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
import org.slf4j.LoggerFactory;
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
| 
						 | 
					@ -37,7 +39,11 @@ public class SEBExamConfigPlaintextDownload extends AbstractDownloadServiceHandl
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @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)
 | 
					        final InputStream input = this.restService.getBuilder(ExportPlainXML.class)
 | 
				
			||||||
                .withURIVariable(API.PARAM_MODEL_ID, modelId)
 | 
					                .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;
 | 
					        return this.name;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public String getSortColumn() {
 | 
				
			||||||
 | 
					        return this.sortColumn;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public PageSortOrder getSortOrder() {
 | 
				
			||||||
 | 
					        return this.sortOrder;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public EntityType getEntityType() {
 | 
					    public EntityType getEntityType() {
 | 
				
			||||||
        if (this.pageSupplier != null) {
 | 
					        if (this.pageSupplier != null) {
 | 
				
			||||||
            return this.pageSupplier.getEntityType();
 | 
					            return this.pageSupplier.getEntityType();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -77,6 +77,30 @@ public interface PaginationService {
 | 
				
			||||||
            final String tableName,
 | 
					            final String tableName,
 | 
				
			||||||
            final Supplier<Result<Collection<T>>> delegate);
 | 
					            final Supplier<Result<Collection<T>>> 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 */
 | 
				
			||||||
 | 
					    <T> Result<Page<T>> getPageOf(
 | 
				
			||||||
 | 
					            final Integer pageNumber,
 | 
				
			||||||
 | 
					            final Integer pageSize,
 | 
				
			||||||
 | 
					            final String sort,
 | 
				
			||||||
 | 
					            final String tableName,
 | 
				
			||||||
 | 
					            final Supplier<Result<Collection<T>>> delegate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** Use this to build a current Page from a given list of objects.
 | 
					    /** Use this to build a current Page from a given list of objects.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param <T> the Type if list entities
 | 
					     * @param <T> the Type if list entities
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -147,6 +147,29 @@ public class PaginationServiceImpl implements PaginationService {
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    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(() -> {
 | 
				
			||||||
 | 
					            //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);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String verifySortColumnName(final String sort, final String columnName) {
 | 
					    private String verifySortColumnName(final String sort, final String columnName) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (StringUtils.isBlank(sort)) {
 | 
					        if (StringUtils.isBlank(sort)) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,37 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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.webservice.servicelayer.exam;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.OutputStream;
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.springframework.scheduling.annotation.Async;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.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 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Result<EntityProcessingReport> deleteAllClientEvents(Collection<String> ids);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
 | 
				
			||||||
 | 
					    void exportSEBClientLogs(
 | 
				
			||||||
 | 
					            OutputStream output,
 | 
				
			||||||
 | 
					            FilterMap filterMap,
 | 
				
			||||||
 | 
					            String sort,
 | 
				
			||||||
 | 
					            ExportType exportType,
 | 
				
			||||||
 | 
					            boolean includeConnectionDetails,
 | 
				
			||||||
 | 
					            boolean includeExamDetails,
 | 
				
			||||||
 | 
					            final SEBServerUser currentUser);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,32 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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.webservice.servicelayer.exam;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.OutputStream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.ExportType;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public interface SEBClientEventExporter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ExportType exportType();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void streamHeader(
 | 
				
			||||||
 | 
					            OutputStream output,
 | 
				
			||||||
 | 
					            boolean includeConnectionDetails,
 | 
				
			||||||
 | 
					            boolean includeExamDetails);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void streamData(
 | 
				
			||||||
 | 
					            OutputStream output,
 | 
				
			||||||
 | 
					            ClientEventRecord eventData,
 | 
				
			||||||
 | 
					            ClientConnectionRecord connectionData,
 | 
				
			||||||
 | 
					            Exam examData);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,281 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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.webservice.servicelayer.exam.impl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.io.OutputStream;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					import java.util.EnumMap;
 | 
				
			||||||
 | 
					import java.util.EnumSet;
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.Iterator;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
 | 
					import org.springframework.context.annotation.Lazy;
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.api.APIMessage;
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.util.Result;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.util.Utils;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.exam.SEBClientEventAdminService;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.exam.SEBClientEventExporter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Lazy
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					@WebServiceProfile
 | 
				
			||||||
 | 
					public class SEBClientEventAdminServiceImpl implements SEBClientEventAdminService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final Logger log = LoggerFactory.getLogger(SEBClientEventAdminServiceImpl.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final PaginationService paginationService;
 | 
				
			||||||
 | 
					    private final ClientEventDAO clientEventDAO;
 | 
				
			||||||
 | 
					    private final SEBClientEventExportTransactionHandler sebClientEventExportTransactionHandler;
 | 
				
			||||||
 | 
					    private final EnumMap<ExportType, SEBClientEventExporter> exporter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public SEBClientEventAdminServiceImpl(
 | 
				
			||||||
 | 
					            final PaginationService paginationService,
 | 
				
			||||||
 | 
					            final ClientEventDAO clientEventDAO,
 | 
				
			||||||
 | 
					            final SEBClientEventExportTransactionHandler sebClientEventExportTransactionHandler,
 | 
				
			||||||
 | 
					            final Collection<SEBClientEventExporter> exporter) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.paginationService = paginationService;
 | 
				
			||||||
 | 
					        this.clientEventDAO = clientEventDAO;
 | 
				
			||||||
 | 
					        this.sebClientEventExportTransactionHandler = sebClientEventExportTransactionHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.exporter = new EnumMap<>(ExportType.class);
 | 
				
			||||||
 | 
					        exporter.forEach(exp -> this.exporter.putIfAbsent(exp.exportType(), exp));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public Result<EntityProcessingReport> deleteAllClientEvents(final Collection<String> ids) {
 | 
				
			||||||
 | 
					        return Result.tryCatch(() -> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (ids == null || ids.isEmpty()) {
 | 
				
			||||||
 | 
					                return EntityProcessingReport.ofEmptyError();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final Set<EntityKey> sources = ids.stream()
 | 
				
			||||||
 | 
					                    .map(id -> new EntityKey(id, EntityType.CLIENT_EVENT))
 | 
				
			||||||
 | 
					                    .collect(Collectors.toSet());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final Result<Collection<EntityKey>> delete = this.clientEventDAO.delete(sources);
 | 
				
			||||||
 | 
					            if (delete.hasError()) {
 | 
				
			||||||
 | 
					                return new EntityProcessingReport(
 | 
				
			||||||
 | 
					                        Collections.emptyList(),
 | 
				
			||||||
 | 
					                        Collections.emptyList(),
 | 
				
			||||||
 | 
					                        Arrays.asList(new ErrorEntry(null, APIMessage.ErrorMessage.UNEXPECTED.of(delete.getError()))));
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return new EntityProcessingReport(
 | 
				
			||||||
 | 
					                        sources,
 | 
				
			||||||
 | 
					                        delete.get(),
 | 
				
			||||||
 | 
					                        Collections.emptyList());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void exportSEBClientLogs(
 | 
				
			||||||
 | 
					            final OutputStream output,
 | 
				
			||||||
 | 
					            final FilterMap filterMap,
 | 
				
			||||||
 | 
					            final String sort,
 | 
				
			||||||
 | 
					            final ExportType exportType,
 | 
				
			||||||
 | 
					            final boolean includeConnectionDetails,
 | 
				
			||||||
 | 
					            final boolean includeExamDetails,
 | 
				
			||||||
 | 
					            final SEBServerUser currentUser) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private class exportRunner {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private final SEBClientEventExporter exporter;
 | 
				
			||||||
 | 
					        private final boolean includeConnectionDetails;
 | 
				
			||||||
 | 
					        private final boolean includeExamDetails;
 | 
				
			||||||
 | 
					        private final Iterator<Collection<ClientEventRecord>> pager;
 | 
				
			||||||
 | 
					        private final OutputStream output;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private final Map<Long, Exam> examCache;
 | 
				
			||||||
 | 
					        private final Map<Long, ClientConnectionRecord> connectionCache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public exportRunner(
 | 
				
			||||||
 | 
					                final SEBClientEventExporter exporter,
 | 
				
			||||||
 | 
					                final boolean includeConnectionDetails,
 | 
				
			||||||
 | 
					                final boolean includeExamDetails,
 | 
				
			||||||
 | 
					                final Iterator<Collection<ClientEventRecord>> pager,
 | 
				
			||||||
 | 
					                final OutputStream output) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.exporter = exporter;
 | 
				
			||||||
 | 
					            this.includeConnectionDetails = includeConnectionDetails;
 | 
				
			||||||
 | 
					            this.includeExamDetails = includeExamDetails;
 | 
				
			||||||
 | 
					            this.pager = pager;
 | 
				
			||||||
 | 
					            this.output = output;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.connectionCache = new HashMap<>();
 | 
				
			||||||
 | 
					            this.examCache = new HashMap<>();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void run(final SEBServerUser currentUser) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final EnumSet<UserRole> userRoles = currentUser.getUserRoles();
 | 
				
			||||||
 | 
					            final boolean isSupporterOnly = userRoles.size() == 1 && userRoles.contains(UserRole.EXAM_SUPPORTER);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // first stream header line
 | 
				
			||||||
 | 
					            this.exporter.streamHeader(this.output, this.includeConnectionDetails, this.includeExamDetails);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // then batch with the pager and stream line per line
 | 
				
			||||||
 | 
					            while (this.pager.hasNext()) {
 | 
				
			||||||
 | 
					                this.pager.next().forEach(rec -> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    final Exam exam = getExam(rec.getClientConnectionId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (!isSupporterOnly || exam.isOwner(currentUser.uuid())) {
 | 
				
			||||||
 | 
					                        this.exporter.streamData(
 | 
				
			||||||
 | 
					                                this.output,
 | 
				
			||||||
 | 
					                                rec,
 | 
				
			||||||
 | 
					                                this.includeConnectionDetails ? getConnection(rec.getClientConnectionId()) : null,
 | 
				
			||||||
 | 
					                                this.includeExamDetails ? getExam(rec.getClientConnectionId()) : null);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private ClientConnectionRecord getConnection(final Long connectionId) {
 | 
				
			||||||
 | 
					            if (!this.connectionCache.containsKey(connectionId)) {
 | 
				
			||||||
 | 
					                SEBClientEventAdminServiceImpl.this.sebClientEventExportTransactionHandler
 | 
				
			||||||
 | 
					                        .clientConnectionById(connectionId)
 | 
				
			||||||
 | 
					                        .onSuccess(rec -> this.connectionCache.put(rec.getId(), rec))
 | 
				
			||||||
 | 
					                        .onError(error -> log.error("Failed to get ClientConnectionRecord for id: {}",
 | 
				
			||||||
 | 
					                                connectionId,
 | 
				
			||||||
 | 
					                                error));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return this.connectionCache.get(connectionId);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private Exam getExam(final Long connectionId) {
 | 
				
			||||||
 | 
					            final ClientConnectionRecord connection = getConnection(connectionId);
 | 
				
			||||||
 | 
					            final Long examId = connection.getExamId();
 | 
				
			||||||
 | 
					            if (!this.examCache.containsKey(examId)) {
 | 
				
			||||||
 | 
					                SEBClientEventAdminServiceImpl.this.sebClientEventExportTransactionHandler
 | 
				
			||||||
 | 
					                        .examById(examId)
 | 
				
			||||||
 | 
					                        .onSuccess(e -> this.examCache.put(examId, e))
 | 
				
			||||||
 | 
					                        .onError(error -> log.error("Failed to get Exam for id: {}",
 | 
				
			||||||
 | 
					                                examId,
 | 
				
			||||||
 | 
					                                error));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return this.examCache.get(examId);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private class Pager implements Iterator<Collection<ClientEventRecord>> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private final FilterMap filterMap;
 | 
				
			||||||
 | 
					        private final String sort;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private int pageNumber = 1;
 | 
				
			||||||
 | 
					        private final int pageSize = 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private Collection<ClientEventRecord> nextRecords;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Pager(
 | 
				
			||||||
 | 
					                final FilterMap filterMap,
 | 
				
			||||||
 | 
					                final String sort) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.filterMap = filterMap;
 | 
				
			||||||
 | 
					            this.sort = sort;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            fetchNext();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public boolean hasNext() {
 | 
				
			||||||
 | 
					            return this.nextRecords != null && !this.nextRecords.isEmpty();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public Collection<ClientEventRecord> next() {
 | 
				
			||||||
 | 
					            final Collection<ClientEventRecord> result = this.nextRecords;
 | 
				
			||||||
 | 
					            fetchNext();
 | 
				
			||||||
 | 
					            return result;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private void fetchNext() {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                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);
 | 
				
			||||||
 | 
					                this.nextRecords = null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,132 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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.webservice.servicelayer.exam.impl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public ExportType exportType() {
 | 
				
			||||||
 | 
					        return ExportType.CSV;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void streamHeader(
 | 
				
			||||||
 | 
					            final OutputStream output,
 | 
				
			||||||
 | 
					            final boolean includeConnectionDetails,
 | 
				
			||||||
 | 
					            final boolean includeExamDetails) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final StringBuilder builder = new StringBuilder();
 | 
				
			||||||
 | 
					        builder.append("Event Type,Message,Value,Client Time (UTC),Server Time (UTC)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (includeConnectionDetails) {
 | 
				
			||||||
 | 
					            builder.append(",User Session-ID,Client Machine,Connection Status,Connection Token");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (includeExamDetails) {
 | 
				
			||||||
 | 
					            builder.append("Exam Name,Exam Description,Exam Type,Start Time (LMS),End Time (LMS)");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        builder.append(Constants.CARRIAGE_RETURN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            output.write(Utils.toByteArray(builder));
 | 
				
			||||||
 | 
					        } catch (final IOException e) {
 | 
				
			||||||
 | 
					            log.error("Failed to stream header: ", e);
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                output.flush();
 | 
				
			||||||
 | 
					            } catch (final IOException e) {
 | 
				
			||||||
 | 
					                e.printStackTrace();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void streamData(
 | 
				
			||||||
 | 
					            final OutputStream output,
 | 
				
			||||||
 | 
					            final ClientEventRecord eventData,
 | 
				
			||||||
 | 
					            final ClientConnectionRecord connectionData,
 | 
				
			||||||
 | 
					            final Exam examData) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final StringBuilder builder = new StringBuilder();
 | 
				
			||||||
 | 
					        final EventType type = EventType.byId(eventData.getType());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        builder.append(type.name());
 | 
				
			||||||
 | 
					        builder.append(Constants.COMMA);
 | 
				
			||||||
 | 
					        builder.append(Utils.toCSVString(eventData.getText()));
 | 
				
			||||||
 | 
					        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());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        builder.append(Constants.CARRIAGE_RETURN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            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();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,113 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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.webservice.servicelayer.exam.impl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.mybatis.dynamic.sql.SqlBuilder.equalTo;
 | 
				
			||||||
 | 
					import static org.mybatis.dynamic.sql.SqlBuilder.isEqualToWhenPresent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
 | 
					import java.util.function.Predicate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.mybatis.dynamic.sql.SqlBuilder;
 | 
				
			||||||
 | 
					import org.springframework.context.annotation.Lazy;
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
 | 
					import org.springframework.transaction.annotation.Transactional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
 | 
				
			||||||
 | 
					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.EventType;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.util.Result;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper;
 | 
				
			||||||
 | 
					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.dao.ExamDAO;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Lazy
 | 
				
			||||||
 | 
					@Component
 | 
				
			||||||
 | 
					@WebServiceProfile
 | 
				
			||||||
 | 
					public class SEBClientEventExportTransactionHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final ClientEventRecordMapper clientEventRecordMapper;
 | 
				
			||||||
 | 
					    private final ClientConnectionRecordMapper clientConnectionRecordMapper;
 | 
				
			||||||
 | 
					    private final ExamDAO examDAO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public SEBClientEventExportTransactionHandler(
 | 
				
			||||||
 | 
					            final ClientEventRecordMapper clientEventRecordMapper,
 | 
				
			||||||
 | 
					            final ClientConnectionRecordMapper clientConnectionRecordMapper,
 | 
				
			||||||
 | 
					            final ExamDAO examDAO) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.clientEventRecordMapper = clientEventRecordMapper;
 | 
				
			||||||
 | 
					        this.clientConnectionRecordMapper = clientConnectionRecordMapper;
 | 
				
			||||||
 | 
					        this.examDAO = examDAO;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Transactional(readOnly = true)
 | 
				
			||||||
 | 
					    public Result<Collection<ClientEventRecord>> allMatching(
 | 
				
			||||||
 | 
					            final FilterMap filterMap,
 | 
				
			||||||
 | 
					            final Predicate<ClientEvent> predicate) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Result.tryCatch(() -> this.clientEventRecordMapper
 | 
				
			||||||
 | 
					                .selectByExample()
 | 
				
			||||||
 | 
					                .leftJoin(ClientConnectionRecordDynamicSqlSupport.clientConnectionRecord)
 | 
				
			||||||
 | 
					                .on(
 | 
				
			||||||
 | 
					                        ClientConnectionRecordDynamicSqlSupport.id,
 | 
				
			||||||
 | 
					                        equalTo(ClientEventRecordDynamicSqlSupport.clientConnectionId))
 | 
				
			||||||
 | 
					                .where(
 | 
				
			||||||
 | 
					                        ClientConnectionRecordDynamicSqlSupport.institutionId,
 | 
				
			||||||
 | 
					                        isEqualToWhenPresent(filterMap.getInstitutionId()))
 | 
				
			||||||
 | 
					                .and(
 | 
				
			||||||
 | 
					                        ClientConnectionRecordDynamicSqlSupport.examId,
 | 
				
			||||||
 | 
					                        isEqualToWhenPresent(filterMap.getClientEventExamId()))
 | 
				
			||||||
 | 
					                .and(
 | 
				
			||||||
 | 
					                        ClientConnectionRecordDynamicSqlSupport.examUserSessionId,
 | 
				
			||||||
 | 
					                        SqlBuilder.isLikeWhenPresent(filterMap.getSQLWildcard(ClientConnection.FILTER_ATTR_SESSION_ID)))
 | 
				
			||||||
 | 
					                .and(
 | 
				
			||||||
 | 
					                        ClientEventRecordDynamicSqlSupport.clientConnectionId,
 | 
				
			||||||
 | 
					                        isEqualToWhenPresent(filterMap.getClientEventConnectionId()))
 | 
				
			||||||
 | 
					                .and(
 | 
				
			||||||
 | 
					                        ClientEventRecordDynamicSqlSupport.type,
 | 
				
			||||||
 | 
					                        isEqualToWhenPresent(filterMap.getClientEventTypeId()))
 | 
				
			||||||
 | 
					                .and(
 | 
				
			||||||
 | 
					                        ClientEventRecordDynamicSqlSupport.type,
 | 
				
			||||||
 | 
					                        SqlBuilder.isNotEqualTo(EventType.LAST_PING.id))
 | 
				
			||||||
 | 
					                .and(
 | 
				
			||||||
 | 
					                        ClientEventRecordDynamicSqlSupport.clientTime,
 | 
				
			||||||
 | 
					                        SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeFrom()))
 | 
				
			||||||
 | 
					                .and(
 | 
				
			||||||
 | 
					                        ClientEventRecordDynamicSqlSupport.clientTime,
 | 
				
			||||||
 | 
					                        SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeTo()))
 | 
				
			||||||
 | 
					                .and(
 | 
				
			||||||
 | 
					                        ClientEventRecordDynamicSqlSupport.serverTime,
 | 
				
			||||||
 | 
					                        SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeFrom()))
 | 
				
			||||||
 | 
					                .and(
 | 
				
			||||||
 | 
					                        ClientEventRecordDynamicSqlSupport.serverTime,
 | 
				
			||||||
 | 
					                        SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeTo()))
 | 
				
			||||||
 | 
					                .and(
 | 
				
			||||||
 | 
					                        ClientEventRecordDynamicSqlSupport.text,
 | 
				
			||||||
 | 
					                        SqlBuilder.isLikeWhenPresent(filterMap.getClientEventText()))
 | 
				
			||||||
 | 
					                .build()
 | 
				
			||||||
 | 
					                .execute());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Transactional(readOnly = true)
 | 
				
			||||||
 | 
					    public Result<ClientConnectionRecord> clientConnectionById(final Long id) {
 | 
				
			||||||
 | 
					        return Result.tryCatch(() -> this.clientConnectionRecordMapper.selectByPrimaryKey(id));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public Result<Exam> examById(final Long id) {
 | 
				
			||||||
 | 
					        return this.examDAO.byPK(id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -8,17 +8,20 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package ch.ethz.seb.sebserver.webservice.weblayer.api;
 | 
					package ch.ethz.seb.sebserver.webservice.weblayer.api;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.Arrays;
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.io.PipedInputStream;
 | 
				
			||||||
 | 
					import java.io.PipedOutputStream;
 | 
				
			||||||
import java.util.Collection;
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.Collections;
 | 
					 | 
				
			||||||
import java.util.EnumSet;
 | 
					import java.util.EnumSet;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Set;
 | 
					 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.servlet.ServletOutputStream;
 | 
				
			||||||
import javax.servlet.http.HttpServletRequest;
 | 
					import javax.servlet.http.HttpServletRequest;
 | 
				
			||||||
 | 
					import javax.servlet.http.HttpServletResponse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.apache.commons.io.IOUtils;
 | 
				
			||||||
import org.mybatis.dynamic.sql.SqlTable;
 | 
					import org.mybatis.dynamic.sql.SqlTable;
 | 
				
			||||||
 | 
					import org.springframework.http.HttpStatus;
 | 
				
			||||||
import org.springframework.http.MediaType;
 | 
					import org.springframework.http.MediaType;
 | 
				
			||||||
import org.springframework.util.MultiValueMap;
 | 
					import org.springframework.util.MultiValueMap;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
					import org.springframework.web.bind.annotation.RequestMapping;
 | 
				
			||||||
| 
						 | 
					@ -28,16 +31,14 @@ import org.springframework.web.bind.annotation.RestController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.API;
 | 
					import ch.ethz.seb.sebserver.gbl.api.API;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
 | 
					import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
 | 
					 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
					import ch.ethz.seb.sebserver.gbl.api.EntityType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
 | 
					import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.EntityDependency;
 | 
					import ch.ethz.seb.sebserver.gbl.model.EntityDependency;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
 | 
					 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
 | 
					import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport.ErrorEntry;
 | 
					 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
 | 
					import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.Page;
 | 
					import ch.ethz.seb.sebserver.gbl.model.Page;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
 | 
					import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.ExportType;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
 | 
					import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
 | 
					import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
 | 
				
			||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
 | 
					import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
 | 
				
			||||||
| 
						 | 
					@ -53,6 +54,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
 | 
				
			||||||
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.exam.SEBClientEventAdminService;
 | 
				
			||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
 | 
					import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@WebServiceProfile
 | 
					@WebServiceProfile
 | 
				
			||||||
| 
						 | 
					@ -62,6 +64,7 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final ExamDAO examDao;
 | 
					    private final ExamDAO examDao;
 | 
				
			||||||
    private final ClientEventDAO clientEventDAO;
 | 
					    private final ClientEventDAO clientEventDAO;
 | 
				
			||||||
 | 
					    private final SEBClientEventAdminService sebClientEventAdminService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected ClientEventController(
 | 
					    protected ClientEventController(
 | 
				
			||||||
            final AuthorizationService authorization,
 | 
					            final AuthorizationService authorization,
 | 
				
			||||||
| 
						 | 
					@ -70,7 +73,8 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
 | 
				
			||||||
            final UserActivityLogDAO userActivityLogDAO,
 | 
					            final UserActivityLogDAO userActivityLogDAO,
 | 
				
			||||||
            final PaginationService paginationService,
 | 
					            final PaginationService paginationService,
 | 
				
			||||||
            final BeanValidationService beanValidationService,
 | 
					            final BeanValidationService beanValidationService,
 | 
				
			||||||
            final ExamDAO examDao) {
 | 
					            final ExamDAO examDao,
 | 
				
			||||||
 | 
					            final SEBClientEventAdminService sebClientEventAdminService) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super(authorization,
 | 
					        super(authorization,
 | 
				
			||||||
                bulkActionService,
 | 
					                bulkActionService,
 | 
				
			||||||
| 
						 | 
					@ -81,6 +85,7 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.examDao = examDao;
 | 
					        this.examDao = examDao;
 | 
				
			||||||
        this.clientEventDAO = entityDAO;
 | 
					        this.clientEventDAO = entityDAO;
 | 
				
			||||||
 | 
					        this.sebClientEventAdminService = sebClientEventAdminService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @RequestMapping(
 | 
					    @RequestMapping(
 | 
				
			||||||
| 
						 | 
					@ -136,26 +141,69 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.checkWritePrivilege(institutionId);
 | 
					        this.checkWritePrivilege(institutionId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (ids == null || ids.isEmpty()) {
 | 
					        return this.sebClientEventAdminService
 | 
				
			||||||
            return EntityProcessingReport.ofEmptyError();
 | 
					                .deleteAllClientEvents(ids)
 | 
				
			||||||
        }
 | 
					                .getOrThrow();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final Set<EntityKey> sources = ids.stream()
 | 
					    @RequestMapping(
 | 
				
			||||||
                .map(id -> new EntityKey(id, EntityType.CLIENT_EVENT))
 | 
					            path = API.SEB_CLIENT_EVENT_EXPORT_PATH_SEGMENT,
 | 
				
			||||||
                .collect(Collectors.toSet());
 | 
					            method = RequestMethod.GET,
 | 
				
			||||||
 | 
					            produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
 | 
				
			||||||
 | 
					    public void exportEvents(
 | 
				
			||||||
 | 
					            @RequestParam(
 | 
				
			||||||
 | 
					                    name = API.PARAM_INSTITUTION_ID,
 | 
				
			||||||
 | 
					                    required = true,
 | 
				
			||||||
 | 
					                    defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
 | 
				
			||||||
 | 
					            @RequestParam(name = API.SEB_CLIENT_EVENT_EXPORT_TYPE, required = true) final ExportType type,
 | 
				
			||||||
 | 
					            @RequestParam(
 | 
				
			||||||
 | 
					                    name = 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,
 | 
				
			||||||
 | 
					            final HttpServletResponse response) throws IOException {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final Result<Collection<EntityKey>> delete = this.clientEventDAO.delete(sources);
 | 
					        // at least current user must have base read access for specified entity type within its own institution
 | 
				
			||||||
 | 
					        checkReadPrivilege(institutionId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (delete.hasError()) {
 | 
					        final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
 | 
				
			||||||
            return new EntityProcessingReport(
 | 
					        populateFilterMap(filterMap, institutionId, sort);
 | 
				
			||||||
                    Collections.emptyList(),
 | 
					
 | 
				
			||||||
                    Collections.emptyList(),
 | 
					        final ServletOutputStream outputStream = response.getOutputStream();
 | 
				
			||||||
                    Arrays.asList(new ErrorEntry(null, APIMessage.ErrorMessage.UNEXPECTED.of(delete.getError()))));
 | 
					        PipedOutputStream pout;
 | 
				
			||||||
        } else {
 | 
					        PipedInputStream pin;
 | 
				
			||||||
            return new EntityProcessingReport(
 | 
					        try {
 | 
				
			||||||
                    sources,
 | 
					            pout = new PipedOutputStream();
 | 
				
			||||||
                    delete.get(),
 | 
					            pin = new PipedInputStream(pout);
 | 
				
			||||||
                    Collections.emptyList());
 | 
					
 | 
				
			||||||
 | 
					            final SEBServerUser currentUser = this.authorization
 | 
				
			||||||
 | 
					                    .getUserService()
 | 
				
			||||||
 | 
					                    .getCurrentUser();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.sebClientEventAdminService.exportSEBClientLogs(
 | 
				
			||||||
 | 
					                    pout,
 | 
				
			||||||
 | 
					                    filterMap,
 | 
				
			||||||
 | 
					                    sort,
 | 
				
			||||||
 | 
					                    type,
 | 
				
			||||||
 | 
					                    includeConnectionDetails,
 | 
				
			||||||
 | 
					                    includeExamDetails,
 | 
				
			||||||
 | 
					                    currentUser);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            IOUtils.copyLarge(pin, outputStream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            response.setStatus(HttpStatus.OK.value());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            outputStream.flush();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            outputStream.flush();
 | 
				
			||||||
 | 
					            outputStream.close();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -175,12 +223,14 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    protected Result<ClientEvent> checkReadAccess(final ClientEvent entity) {
 | 
					    protected Result<ClientEvent> checkReadAccess(final ClientEvent entity) {
 | 
				
			||||||
 | 
					        final EnumSet<UserRole> userRoles = this.authorization
 | 
				
			||||||
 | 
					                .getUserService()
 | 
				
			||||||
 | 
					                .getCurrentUser()
 | 
				
			||||||
 | 
					                .getUserRoles();
 | 
				
			||||||
 | 
					        final boolean isSupporterOnly = userRoles.size() == 1 && userRoles.contains(UserRole.EXAM_SUPPORTER);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Result.tryCatch(() -> {
 | 
					        return Result.tryCatch(() -> {
 | 
				
			||||||
            final EnumSet<UserRole> userRoles = this.authorization
 | 
					
 | 
				
			||||||
                    .getUserService()
 | 
					 | 
				
			||||||
                    .getCurrentUser()
 | 
					 | 
				
			||||||
                    .getUserRoles();
 | 
					 | 
				
			||||||
            final boolean isSupporterOnly = userRoles.size() == 1 && userRoles.contains(UserRole.EXAM_SUPPORTER);
 | 
					 | 
				
			||||||
            if (isSupporterOnly) {
 | 
					            if (isSupporterOnly) {
 | 
				
			||||||
                // check owner grant be getting exam
 | 
					                // check owner grant be getting exam
 | 
				
			||||||
                return super.checkReadAccess(entity)
 | 
					                return super.checkReadAccess(entity)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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.form.column.exam.endTime.tooltip=The end date and time of the exam
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sebserver.seblogs.action.delete=Delete Logs
 | 
					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.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.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
 | 
					sebserver.seblogs.delete.action.delete=Delete All Logs
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue