From 085ec45fb168cf2a6e9d0505219a840dc170291c Mon Sep 17 00:00:00 2001
From: anhefti 
Date: Mon, 28 Mar 2022 17:29:26 +0200
Subject: [PATCH] separated clientConnection and clientConnectionData page
 filter and sort
---
 .../model/session/ClientConnectionData.java   |  22 +++
 .../gui/content/monitoring/FinishedExam.java  |  16 +-
 .../api/ClientConnectionController.java       | 147 ++++++++++++++++--
 3 files changed, 153 insertions(+), 32 deletions(-)
diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java
index 9fa6a567..a610f4c5 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java
@@ -17,8 +17,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import ch.ethz.seb.sebserver.gbl.Constants;
 import ch.ethz.seb.sebserver.gbl.api.EntityType;
 import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
+import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
 import ch.ethz.seb.sebserver.gbl.util.Utils;
 
 @JsonIgnoreProperties(ignoreUnknown = true)
@@ -100,6 +102,26 @@ public class ClientConnectionData implements GrantEntity {
         return this.missingPing || this.pendingNotification;
     }
 
+    @JsonIgnore
+    public Double getIndicatorValue(final Long indicatorId) {
+        return this.indicatorValues
+                .stream()
+                .filter(indicatorValue -> indicatorValue.getIndicatorId().equals(indicatorId))
+                .findFirst()
+                .map(iv -> iv.getValue())
+                .orElse(Double.NaN);
+    }
+
+    @JsonIgnore
+    public String getIndicatorDisplayValue(final Indicator indicator) {
+        return this.indicatorValues
+                .stream()
+                .filter(indicatorValue -> indicatorValue.getIndicatorId().equals(indicator.id))
+                .findFirst()
+                .map(iv -> IndicatorValue.getDisplayValue(iv, indicator.type))
+                .orElse(Constants.EMPTY_NOTE);
+    }
+
     public ClientConnection getClientConnection() {
         return this.clientConnection;
     }
diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
index dbe2ddfc..d25a1aae 100644
--- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
+++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/FinishedExam.java
@@ -10,14 +10,12 @@ package ch.ethz.seb.sebserver.gui.content.monitoring;
 
 import java.util.Collection;
 import java.util.function.BooleanSupplier;
-import java.util.function.Function;
 
 import org.eclipse.swt.widgets.Composite;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Component;
 
-import ch.ethz.seb.sebserver.gbl.Constants;
 import ch.ethz.seb.sebserver.gbl.api.API;
 import ch.ethz.seb.sebserver.gbl.model.Domain;
 import ch.ethz.seb.sebserver.gbl.model.EntityKey;
@@ -26,7 +24,6 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
 import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
 import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
 import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
-import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
 import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
 import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
 import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
@@ -162,7 +159,7 @@ public class FinishedExam implements TemplateComposer {
             tableBuilder.withColumn(new ColumnDefinition<>(
                     indicator.name,
                     new LocTextKey(indicator.name),
-                    indicatorValueFunction(indicator)));
+                    cc -> cc.getIndicatorDisplayValue(indicator)));
         });
 
         final EntityTable table = tableBuilder.compose(pageContext.copyOf(content));
@@ -175,15 +172,4 @@ public class FinishedExam implements TemplateComposer {
                 .publishIf(isExamSupporter, false);
     }
 
-    public Function indicatorValueFunction(final Indicator indicator) {
-        return clientConnectionData -> {
-            return clientConnectionData.indicatorValues
-                    .stream()
-                    .filter(indicatorValue -> indicatorValue.getIndicatorId().equals(indicator.id))
-                    .findFirst()
-                    .map(iv -> IndicatorValue.getDisplayValue(iv, indicator.type))
-                    .orElse(Constants.EMPTY_NOTE);
-        };
-    }
-
 }
diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientConnectionController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientConnectionController.java
index f4458849..daef8596 100644
--- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientConnectionController.java
+++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ClientConnectionController.java
@@ -82,6 +82,73 @@ public class ClientConnectionController extends ReadonlyEntityController
+     * GET /{api}/{domain-entity-name}
+     * 
+     * For example for the "exam" domain-entity
+     * GET /admin-api/v1/exam
+     * GET /admin-api/v1/exam?page_number=2&page_size=10&sort=-name
+     * GET /admin-api/v1/exam?name=seb&active=true
+     * 
+     * Sorting: the sort parameter to sort the list of entities before paging
+     * the sort parameter is the name of the entity-model attribute to sort with a leading '-' sign for
+     * descending sort order. Note that not all entity-model attribute are suited for sorting while the most
+     * are.
+     * 
+     * Filter: The filter attributes accepted by this API depend on the actual entity model (domain object)
+     * and are of the form [domain-attribute-name]=[filter-value]. E.g.: name=abc or type=EXAM. Usually
+     * filter attributes of text type are treated as SQL wildcard with %[text]% to filter all text containing
+     * a given text-snippet.
+     *
+     * @param institutionId The institution identifier of the request.
+     *            Default is the institution identifier of the institution of the current user
+     * @param pageNumber the number of the page that is requested
+     * @param pageSize the size of the page that is requested
+     * @param sort the sort parameter to sort the list of entities before paging
+     *            the sort parameter is the name of the entity-model attribute to sort with a leading '-' sign for
+     *            descending sort order.
+     * @param allRequestParams a MultiValueMap of all request parameter that is used for filtering.
+     * @return Page of domain-model-entities of specified type */
+    @Override
+    @RequestMapping(
+            method = RequestMethod.GET,
+            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    public Page getPage(
+            @RequestParam(
+                    name = API.PARAM_INSTITUTION_ID,
+                    required = true,
+                    defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
+            @RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
+            @RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
+            @RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
+            @RequestParam final MultiValueMap allRequestParams,
+            final HttpServletRequest request) {
+
+        // at least current user must have read access for specified entity type within its own institution
+        checkReadPrivilege(institutionId);
+
+        final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
+        populateFilterMap(filterMap, institutionId, sort);
+
+        if (StringUtils.isNotBlank(sort) || filterMap.containsAny(EXT_FILTER)) {
+
+            final Collection allConnections = getAll(filterMap)
+                    .getOrThrow();
+
+            return this.paginationService.buildPageFromList(
+                    pageNumber,
+                    pageSize,
+                    sort,
+                    allConnections,
+                    pageClientConnectionFunction(filterMap, sort));
+
+        } else {
+            return super.getPage(institutionId, pageNumber, pageSize, sort, allRequestParams, request);
+        }
+    }
+
     @RequestMapping(
             path = API.SEB_CLIENT_CONNECTION_DATA_ENDPOINT,
             method = RequestMethod.GET,
@@ -114,7 +181,7 @@ public class ClientConnectionController extends ReadonlyEntityController> getAll(final FilterMap filterMap) {
-//        final String infoFilter = filterMap.getString(ClientConnection.FILTER_ATTR_INFO);
-//        if (StringUtils.isNotBlank(infoFilter)) {
-//            return super.getAll(filterMap)
-//                    .map(all -> all.stream().filter(c -> c.getInfo() == null || c.getInfo().contains(infoFilter))
-//                            .collect(Collectors.toList()));
-//        }
-//
-//        return super.getAll(filterMap);
-//    }
-
     @Override
     public Collection getDependencies(
             final String modelId,
@@ -198,15 +253,35 @@ public class ClientConnectionController extends ReadonlyEntityController, List> pageFunction(
+    private Function, List> pageClientConnectionFunction(
             final FilterMap filterMap,
             final String sort) {
 
         return connections -> {
 
-            final List filtered = connections.stream()
-                    .filter(getFilter(filterMap))
+            final List filtered = connections
+                    .stream()
+                    .filter(getClientConnectionFilter(filterMap))
                     .collect(Collectors.toList());
+
+            if (StringUtils.isNotBlank(sort)) {
+                filtered.sort(new ClientConnectionComparator(sort));
+            }
+            return filtered;
+        };
+    }
+
+    private Function, List> pageClientConnectionDataFunction(
+            final FilterMap filterMap,
+            final String sort) {
+
+        return connections -> {
+
+            final List filtered = connections
+                    .stream()
+                    .filter(getClientConnectionDataFilter(filterMap))
+                    .collect(Collectors.toList());
+
             if (StringUtils.isNotBlank(sort)) {
                 filtered.sort(new ClientConnectionDataComparator(sort));
             }
@@ -214,7 +289,16 @@ public class ClientConnectionController extends ReadonlyEntityController getFilter(final FilterMap filterMap) {
+    private Predicate getClientConnectionFilter(final FilterMap filterMap) {
+        final String infoFilter = filterMap.getString(ClientConnection.FILTER_ATTR_INFO);
+        Predicate filter = Utils.truePredicate();
+        if (StringUtils.isNotBlank(infoFilter)) {
+            filter = c -> c.getInfo() == null || c.getInfo().contains(infoFilter);
+        }
+        return filter;
+    }
+
+    private Predicate getClientConnectionDataFilter(final FilterMap filterMap) {
         final String infoFilter = filterMap.getString(ClientConnection.FILTER_ATTR_INFO);
         Predicate filter = Utils.truePredicate();
         if (StringUtils.isNotBlank(infoFilter)) {
@@ -223,6 +307,35 @@ public class ClientConnectionController extends ReadonlyEntityController {
+
+        final String sortColumn;
+        final boolean descending;
+
+        ClientConnectionComparator(final String sort) {
+            this.sortColumn = PageSortOrder.decode(sort);
+            this.descending = PageSortOrder.getSortOrder(sort) == PageSortOrder.DESCENDING;
+        }
+
+        @Override
+        public int compare(final ClientConnection cc1, final ClientConnection cc2) {
+            int result = 0;
+            if (Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID.equals(this.sortColumn)) {
+                result = cc1.userSessionId
+                        .compareTo(cc2.userSessionId);
+            } else if (ClientConnection.ATTR_INFO.equals(this.sortColumn)) {
+                result = cc1.getInfo().compareTo(cc2.getInfo());
+            } else if (Domain.CLIENT_CONNECTION.ATTR_STATUS.equals(this.sortColumn)) {
+                result = cc1.getStatus()
+                        .compareTo(cc2.getStatus());
+            } else {
+                result = cc1.userSessionId
+                        .compareTo(cc2.userSessionId);
+            }
+            return (this.descending) ? -result : result;
+        }
+    }
+
     private static final class ClientConnectionDataComparator implements Comparator {
 
         final String sortColumn;