SEBSERV-62 SEBSERV-63 exam-api changes and GUI implementation
This commit is contained in:
parent
5f9a2c6fe0
commit
42ef5a04aa
28 changed files with 1281 additions and 51 deletions
|
@ -36,10 +36,13 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
@ -102,12 +105,28 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E
|
|||
web
|
||||
.ignoring()
|
||||
.antMatchers("/error")
|
||||
.antMatchers(this.examAPIDiscoveryEndpoint);
|
||||
.antMatchers(this.examAPIDiscoveryEndpoint)
|
||||
.and();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(final HttpSecurity http) throws Exception {
|
||||
http
|
||||
.sessionManagement()
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
.antMatcher("/**")
|
||||
.authorizeRequests()
|
||||
.anyRequest()
|
||||
.permitAll()
|
||||
.and()
|
||||
.exceptionHandling()
|
||||
.accessDeniedHandler(new OAuth2AccessDeniedHandler());
|
||||
}
|
||||
|
||||
@RequestMapping("/error")
|
||||
public void handleError(final HttpServletResponse response) throws IOException {
|
||||
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
|
||||
//response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
|
||||
response.setHeader(HttpHeaders.LOCATION, this.unauthorizedRedirect);
|
||||
response.flushBuffer();
|
||||
}
|
||||
|
|
|
@ -16,15 +16,15 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
|
||||
public class ClientConnectionData {
|
||||
|
||||
@JsonProperty
|
||||
@JsonProperty("clientConnection")
|
||||
public final ClientConnection clientConnection;
|
||||
@JsonProperty
|
||||
@JsonProperty("indicatorValues")
|
||||
public final Collection<? extends IndicatorValue> indicatorValues;
|
||||
|
||||
@JsonCreator
|
||||
protected ClientConnectionData(
|
||||
@JsonProperty final ClientConnection clientConnection,
|
||||
@JsonProperty final Collection<? extends IndicatorValue> indicatorValues) {
|
||||
@JsonProperty("clientConnection") final ClientConnection clientConnection,
|
||||
@JsonProperty("indicatorValues") final Collection<? extends IndicatorValue> indicatorValues) {
|
||||
|
||||
this.clientConnection = clientConnection;
|
||||
this.indicatorValues = indicatorValues;
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.LinkedHashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -38,6 +39,9 @@ import ch.ethz.seb.sebserver.gbl.Constants;
|
|||
|
||||
public final class Utils {
|
||||
|
||||
public static final Predicate<?> TRUE_PREDICATE = v -> true;
|
||||
public static final Predicate<?> FALSE_PREDICATE = v -> false;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Utils.class);
|
||||
|
||||
/** This Collector can be used within stream collect to get one expected singleton element from
|
||||
|
@ -339,4 +343,14 @@ public final class Utils {
|
|||
return (text == null) ? null : Constants.PERCENTAGE + text + Constants.PERCENTAGE;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Predicate<T> truePredicate() {
|
||||
return (Predicate<T>) TRUE_PREDICATE;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Predicate<T> falsePredicate() {
|
||||
return (Predicate<T>) FALSE_PREDICATE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamCo
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteIndicator;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingsPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.ImportAsExam;
|
||||
|
@ -337,7 +337,7 @@ public class ExamForm implements TemplateComposer {
|
|||
INDICATOR_LIST_TITLE_KEY);
|
||||
|
||||
final EntityTable<Indicator> indicatorTable =
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetIndicators.class))
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetIndicatorPage.class))
|
||||
.withRestCallAdapter(builder -> builder.withQueryParam(
|
||||
Indicator.FILTER_ATTR_EXAM_ID,
|
||||
entityKey.modelId))
|
||||
|
|
|
@ -28,6 +28,7 @@ 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;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
|
@ -256,7 +257,7 @@ public class LmsSetupForm implements TemplateComposer {
|
|||
|
||||
// reset previous errors
|
||||
formHandle.process(
|
||||
name -> true,
|
||||
Utils.truePredicate(),
|
||||
fieldAccessor -> fieldAccessor.resetError());
|
||||
|
||||
// first test the connection on ad hoc object
|
||||
|
|
|
@ -8,26 +8,129 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.content;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetConnectionData;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionPoll;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class MonitoringRunningExam implements TemplateComposer {
|
||||
|
||||
public MonitoringRunningExam() {
|
||||
// TODO Auto-generated constructor stub
|
||||
private static final Logger log = LoggerFactory.getLogger(MonitoringRunningExam.class);
|
||||
|
||||
private final ServerPushService serverPushService;
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final long pollInterval;
|
||||
|
||||
protected MonitoringRunningExam(
|
||||
final ServerPushService serverPushService,
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
@Value("${sebserver.gui.webservice.poll-interval:500}") final long pollInterval) {
|
||||
|
||||
this.serverPushService = serverPushService;
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.pollInterval = pollInterval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
// TODO Auto-generated method stub
|
||||
//final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
|
||||
final Exam exam = restService.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final Collection<Indicator> indicators = restService.getBuilder(GetIndicators.class)
|
||||
.withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final Composite content = this.pageService.getWidgetFactory().defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
new LocTextKey("sebserver.monitoring.exam", exam.name));
|
||||
|
||||
final Composite tablePane = new Composite(content, SWT.NONE);
|
||||
tablePane.setLayout(new GridLayout());
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
gridData.heightHint = 100;
|
||||
tablePane.setLayoutData(gridData);
|
||||
|
||||
final ClientConnectionTable clientTable = new ClientConnectionTable(
|
||||
widgetFactory,
|
||||
tablePane,
|
||||
exam,
|
||||
indicators);
|
||||
|
||||
final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCall =
|
||||
restService.getBuilder(GetConnectionData.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId());
|
||||
|
||||
final ClientConnectionPoll clientConnectionPoll = new ClientConnectionPoll(
|
||||
restCall,
|
||||
clientTable,
|
||||
this.pollInterval);
|
||||
|
||||
this.serverPushService.runServerPush(
|
||||
new ServerPushContext(content, Utils.truePredicate()),
|
||||
clientConnectionPoll,
|
||||
updateTable(clientTable));
|
||||
|
||||
}
|
||||
|
||||
private final Consumer<ServerPushContext> updateTable(final ClientConnectionTable clientTable) {
|
||||
return context -> {
|
||||
if (!context.isDisposed()) {
|
||||
try {
|
||||
clientTable.updateGUI();
|
||||
context.layout();
|
||||
} catch (final Exception e) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("Unexpected error while trying to update GUI: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -163,7 +163,7 @@ public final class Form implements FormBinding {
|
|||
|
||||
public void allVisible() {
|
||||
process(
|
||||
name -> true,
|
||||
Utils.truePredicate(),
|
||||
ffa -> ffa.setVisible(true));
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.slf4j.LoggerFactory;
|
|||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form.FormFieldAccessor;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
|
@ -77,7 +78,7 @@ public class FormHandle<T extends Entity> {
|
|||
public Result<T> doAPIPost() {
|
||||
// reset all errors that may still be displayed
|
||||
this.form.process(
|
||||
name -> true,
|
||||
Utils.truePredicate(),
|
||||
fieldAccessor -> fieldAccessor.resetError());
|
||||
|
||||
// post
|
||||
|
|
|
@ -29,8 +29,8 @@ public abstract class AbstractExportCall extends RestCall<byte[]> {
|
|||
@Override
|
||||
protected Result<byte[]> exchange(final RestCallBuilder builder) {
|
||||
try {
|
||||
final ResponseEntity<byte[]> responseEntity = this.restService
|
||||
.getWebserviceAPIRestTemplate()
|
||||
final ResponseEntity<byte[]> responseEntity = builder
|
||||
.getRestTemplate()
|
||||
.exchange(
|
||||
builder.buildURI(),
|
||||
this.httpMethod,
|
||||
|
|
|
@ -27,6 +27,8 @@ import org.springframework.http.ResponseEntity;
|
|||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestClientResponseException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
@ -81,7 +83,10 @@ public abstract class RestCall<T> {
|
|||
|
||||
}
|
||||
|
||||
protected RestCall<T> init(final RestService restService, final JSONMapper jsonMapper) {
|
||||
protected RestCall<T> init(
|
||||
final RestService restService,
|
||||
final JSONMapper jsonMapper) {
|
||||
|
||||
this.restService = restService;
|
||||
this.jsonMapper = jsonMapper;
|
||||
return this;
|
||||
|
@ -92,8 +97,7 @@ public abstract class RestCall<T> {
|
|||
log.debug("Call webservice API on {} for {}", this.path, builder);
|
||||
|
||||
try {
|
||||
final ResponseEntity<String> responseEntity = this.restService
|
||||
.getWebserviceAPIRestTemplate()
|
||||
final ResponseEntity<String> responseEntity = builder.restTemplate
|
||||
.exchange(
|
||||
builder.buildURI(),
|
||||
this.httpMethod,
|
||||
|
@ -141,7 +145,9 @@ public abstract class RestCall<T> {
|
|||
}
|
||||
|
||||
public RestCallBuilder newBuilder() {
|
||||
return new RestCallBuilder();
|
||||
return new RestCallBuilder(
|
||||
this.restService.getWebserviceAPIRestTemplate(),
|
||||
this.restService.getWebserviceURIBuilder());
|
||||
}
|
||||
|
||||
public RestCall<T>.RestCallBuilder newBuilder(final RestCall<?>.RestCallBuilder builder) {
|
||||
|
@ -168,12 +174,16 @@ public abstract class RestCall<T> {
|
|||
|
||||
public class RestCallBuilder {
|
||||
|
||||
private RestTemplate restTemplate;
|
||||
private UriComponentsBuilder uriComponentsBuilder;
|
||||
private final HttpHeaders httpHeaders;
|
||||
private String body = null;
|
||||
private final MultiValueMap<String, String> queryParams;
|
||||
private final Map<String, String> uriVariables;
|
||||
|
||||
protected RestCallBuilder() {
|
||||
protected RestCallBuilder(final RestTemplate restTemplate, final UriComponentsBuilder uriComponentsBuilder) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.uriComponentsBuilder = uriComponentsBuilder;
|
||||
this.httpHeaders = new HttpHeaders();
|
||||
this.queryParams = new LinkedMultiValueMap<>();
|
||||
this.uriVariables = new HashMap<>();
|
||||
|
@ -183,12 +193,28 @@ public abstract class RestCall<T> {
|
|||
}
|
||||
|
||||
public RestCallBuilder(final RestCall<?>.RestCallBuilder builder) {
|
||||
this.restTemplate = builder.restTemplate;
|
||||
this.uriComponentsBuilder = builder.uriComponentsBuilder;
|
||||
this.httpHeaders = builder.httpHeaders;
|
||||
this.body = builder.body;
|
||||
this.queryParams = new LinkedMultiValueMap<>(builder.queryParams);
|
||||
this.uriVariables = new HashMap<>(builder.uriVariables);
|
||||
}
|
||||
|
||||
public RestTemplate getRestTemplate() {
|
||||
return this.restTemplate;
|
||||
}
|
||||
|
||||
public RestCallBuilder withRestTemplate(final RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestCallBuilder withUriComponentsBuilder(final UriComponentsBuilder uriComponentsBuilder) {
|
||||
this.uriComponentsBuilder = uriComponentsBuilder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestCallBuilder withHeaders(final HttpHeaders headers) {
|
||||
this.httpHeaders.addAll(headers);
|
||||
return this;
|
||||
|
@ -284,7 +310,8 @@ public abstract class RestCall<T> {
|
|||
}
|
||||
|
||||
public String buildURI() {
|
||||
return RestCall.this.restService.getWebserviceURIBuilder()
|
||||
return this.uriComponentsBuilder
|
||||
.cloneBuilder()
|
||||
.path(RestCall.this.path)
|
||||
.queryParams(this.queryParams)
|
||||
.toUriString();
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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 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.model.Page;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class GetIndicatorPage extends RestCall<Page<Indicator>> {
|
||||
|
||||
public GetIndicatorPage() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_PAGE,
|
||||
EntityType.INDICATOR,
|
||||
new TypeReference<Page<Indicator>>() {
|
||||
}),
|
||||
HttpMethod.GET,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.EXAM_INDICATOR_ENDPOINT);
|
||||
}
|
||||
|
||||
}
|
|
@ -8,34 +8,30 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.model.Page;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.PageToListCallAdapter;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class GetIndicators extends RestCall<Page<Indicator>> {
|
||||
public class GetIndicators extends PageToListCallAdapter<Indicator> {
|
||||
|
||||
public GetIndicators() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_PAGE,
|
||||
super(
|
||||
GetIndicatorPage.class,
|
||||
EntityType.INDICATOR,
|
||||
new TypeReference<Page<Indicator>>() {
|
||||
}),
|
||||
HttpMethod.GET,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
new TypeReference<List<Indicator>>() {
|
||||
},
|
||||
API.EXAM_INDICATOR_ENDPOINT);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.session;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
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.model.session.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class GetConnectionData extends RestCall<Collection<ClientConnectionData>> {
|
||||
|
||||
public GetConnectionData() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_LIST,
|
||||
EntityType.CLIENT_CONNECTION,
|
||||
new TypeReference<Collection<ClientConnectionData>>() {
|
||||
}),
|
||||
HttpMethod.GET,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.EXAM_MONITORING_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.auth;
|
||||
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
public class WebserviceConnectionData {
|
||||
|
||||
final String id;
|
||||
final String webserviceProtocol;
|
||||
final String webserviceServerAdress;
|
||||
final String webserviceServerPort;
|
||||
final String webserviceAPIPath;
|
||||
final String webserviceServerAddress;
|
||||
|
||||
private final UriComponentsBuilder webserviceURIBuilder;
|
||||
|
||||
protected WebserviceConnectionData(
|
||||
final String id,
|
||||
final String webserviceProtocol,
|
||||
final String webserviceServerAdress,
|
||||
final String webserviceServerPort,
|
||||
final String webserviceAPIPath) {
|
||||
|
||||
this.id = id;
|
||||
this.webserviceProtocol = webserviceProtocol;
|
||||
this.webserviceServerAdress = webserviceServerAdress;
|
||||
this.webserviceServerPort = webserviceServerPort;
|
||||
this.webserviceAPIPath = webserviceAPIPath;
|
||||
|
||||
this.webserviceServerAddress = webserviceProtocol + "://" + webserviceServerAdress + ":" + webserviceServerPort;
|
||||
this.webserviceURIBuilder = UriComponentsBuilder
|
||||
.fromHttpUrl(webserviceProtocol + "://" + webserviceServerAdress)
|
||||
.port(webserviceServerPort)
|
||||
.path(webserviceAPIPath);
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getWebserviceProtocol() {
|
||||
return this.webserviceProtocol;
|
||||
}
|
||||
|
||||
public String getWebserviceServerAdress() {
|
||||
return this.webserviceServerAdress;
|
||||
}
|
||||
|
||||
public String getWebserviceServerPort() {
|
||||
return this.webserviceServerPort;
|
||||
}
|
||||
|
||||
public String getWebserviceAPIPath() {
|
||||
return this.webserviceAPIPath;
|
||||
}
|
||||
|
||||
public String getWebserviceServerAddress() {
|
||||
return this.webserviceServerAddress;
|
||||
}
|
||||
|
||||
public UriComponentsBuilder getWebserviceURIBuilder() {
|
||||
return this.webserviceURIBuilder.cloneBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final WebserviceConnectionData other = (WebserviceConnectionData) obj;
|
||||
if (this.id == null) {
|
||||
if (other.id != null)
|
||||
return false;
|
||||
} else if (!this.id.equals(other.id))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("WebserviceConnectionData [id=");
|
||||
builder.append(this.id);
|
||||
builder.append(", webserviceProtocol=");
|
||||
builder.append(this.webserviceProtocol);
|
||||
builder.append(", webserviceServerAdress=");
|
||||
builder.append(this.webserviceServerAdress);
|
||||
builder.append(", webserviceServerPort=");
|
||||
builder.append(this.webserviceServerPort);
|
||||
builder.append(", webserviceAPIPath=");
|
||||
builder.append(this.webserviceAPIPath);
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.session;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
public class ClientConnectionPoll implements Consumer<ServerPushContext> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ClientConnectionPoll.class);
|
||||
|
||||
private final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder;
|
||||
private final ClientConnectionTable clientConnectionTable;
|
||||
private final long pollInterval;
|
||||
|
||||
public ClientConnectionPoll(
|
||||
final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder,
|
||||
final ClientConnectionTable clientConnectionTable,
|
||||
final long pollInterval) {
|
||||
|
||||
this.restCallBuilder = restCallBuilder;
|
||||
this.clientConnectionTable = clientConnectionTable;
|
||||
this.pollInterval = pollInterval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(final ServerPushContext pushContext) {
|
||||
try {
|
||||
Thread.sleep(this.pollInterval);
|
||||
} catch (final Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("unexpected error while sleep: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
this.clientConnectionTable.updateValues(this.restCallBuilder
|
||||
.call()
|
||||
.get(t -> {
|
||||
log.error("Error poll connection data: ", t);
|
||||
return Collections.emptyList();
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,289 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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.session;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.graphics.Color;
|
||||
import org.eclipse.swt.graphics.RGB;
|
||||
import org.eclipse.swt.graphics.Rectangle;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.swt.widgets.Table;
|
||||
import org.eclipse.swt.widgets.TableColumn;
|
||||
import org.eclipse.swt.widgets.TableItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
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.exam.Indicator.Threshold;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
public final class ClientConnectionTable {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ClientConnectionTable.class);
|
||||
|
||||
private final static LocTextKey CONNECTION_ID_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.connection.list.column.id");
|
||||
private final static LocTextKey CONNECTION_ADDRESS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.connection.list.column.address");
|
||||
private final static LocTextKey CONNECTION_STATUS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.connection.list.column.status");
|
||||
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final Exam exam;
|
||||
private final EnumMap<IndicatorType, IndicatorData> indicatorMapping;
|
||||
private final Table table;
|
||||
|
||||
private final Color color1;
|
||||
private final Color color2;
|
||||
private final Color color3;
|
||||
private int tableWidth;
|
||||
|
||||
private final Map<Long, UpdatableTableItem> tableMapping;
|
||||
|
||||
public ClientConnectionTable(
|
||||
final WidgetFactory widgetFactory,
|
||||
final Composite tableRoot,
|
||||
final Exam exam,
|
||||
final Collection<Indicator> indicators) {
|
||||
|
||||
this.widgetFactory = widgetFactory;
|
||||
this.exam = exam;
|
||||
|
||||
final Display display = tableRoot.getDisplay();
|
||||
|
||||
this.indicatorMapping = new EnumMap<>(IndicatorType.class);
|
||||
int i = 3;
|
||||
for (final Indicator indicator : indicators) {
|
||||
this.indicatorMapping.put(indicator.type, new IndicatorData(indicator, i, display));
|
||||
i++;
|
||||
}
|
||||
|
||||
this.table = widgetFactory.tableLocalized(tableRoot);
|
||||
this.table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
this.table.setLayout(new GridLayout());
|
||||
|
||||
widgetFactory.tableColumnLocalized(
|
||||
this.table,
|
||||
CONNECTION_ID_TEXT_KEY);
|
||||
widgetFactory.tableColumnLocalized(
|
||||
this.table,
|
||||
CONNECTION_ADDRESS_TEXT_KEY);
|
||||
widgetFactory.tableColumnLocalized(
|
||||
this.table,
|
||||
CONNECTION_STATUS_TEXT_KEY);
|
||||
for (final Indicator indDef : indicators) {
|
||||
final TableColumn tc = new TableColumn(this.table, SWT.NONE);
|
||||
tc.setText(indDef.name);
|
||||
}
|
||||
|
||||
this.table.setHeaderVisible(true);
|
||||
this.table.setLinesVisible(true);
|
||||
|
||||
this.color1 = new Color(display, new RGB(0, 255, 0), 100);
|
||||
this.color2 = new Color(display, new RGB(249, 166, 2), 100);
|
||||
this.color3 = new Color(display, new RGB(255, 0, 0), 100);
|
||||
|
||||
this.tableMapping = new HashMap<>();
|
||||
this.table.layout();
|
||||
}
|
||||
|
||||
public WidgetFactory getWidgetFactory() {
|
||||
return this.widgetFactory;
|
||||
}
|
||||
|
||||
public Exam getExam() {
|
||||
return this.exam;
|
||||
}
|
||||
|
||||
public void updateValues(final Collection<ClientConnectionData> connectionInfo) {
|
||||
for (final ClientConnectionData data : connectionInfo) {
|
||||
final UpdatableTableItem tableItem = this.tableMapping.computeIfAbsent(
|
||||
data.getConnectionId(),
|
||||
userIdentifier -> new UpdatableTableItem(this.table, data.getConnectionId()));
|
||||
tableItem.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateGUI() {
|
||||
for (final UpdatableTableItem uti : this.tableMapping.values()) {
|
||||
if (uti.tableItem == null) {
|
||||
createTableItem(uti);
|
||||
updateIndicatorValues(uti);
|
||||
updateConnectionStatusColor(uti);
|
||||
} else {
|
||||
if (!uti.connectionData.clientConnection.status
|
||||
.equals(uti.previous_connectionData.clientConnection.status)) {
|
||||
uti.tableItem.setText(0, uti.getConnectionIdentifer());
|
||||
uti.tableItem.setText(1, uti.getStatusName());
|
||||
updateConnectionStatusColor(uti);
|
||||
}
|
||||
if (uti.hasStatus(ConnectionStatus.ESTABLISHED)) {
|
||||
updateIndicatorValues(uti);
|
||||
}
|
||||
}
|
||||
uti.tableItem.getDisplay();
|
||||
}
|
||||
|
||||
adaptTableWidth();
|
||||
}
|
||||
|
||||
private void createTableItem(final UpdatableTableItem uti) {
|
||||
uti.tableItem = new TableItem(this.table, SWT.NONE);
|
||||
uti.tableItem.setText(0, uti.getConnectionIdentifer());
|
||||
uti.tableItem.setText(1, uti.getConnectionAddress());
|
||||
uti.tableItem.setText(2, uti.getStatusName());
|
||||
}
|
||||
|
||||
private void adaptTableWidth() {
|
||||
final Rectangle area = this.table.getParent().getClientArea();
|
||||
if (this.tableWidth != area.width) {
|
||||
final int columnWidth = area.width / this.table.getColumnCount();
|
||||
for (final TableColumn column : this.table.getColumns()) {
|
||||
column.setWidth(columnWidth);
|
||||
}
|
||||
this.table.layout(true, true);
|
||||
//this.table.pack();
|
||||
this.tableWidth = area.width;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIndicatorValues(final UpdatableTableItem uti) {
|
||||
|
||||
for (final IndicatorValue iv : uti.connectionData.indicatorValues) {
|
||||
final IndicatorData indicatorData = this.indicatorMapping.get(iv.getType());
|
||||
if (indicatorData != null) {
|
||||
uti.tableItem.setText(indicatorData.index, String.valueOf(iv.getValue()));
|
||||
uti.tableItem.setBackground(
|
||||
indicatorData.index,
|
||||
this.getColorForValue(indicatorData, iv.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateConnectionStatusColor(final UpdatableTableItem uti) {
|
||||
switch (uti.connectionData.clientConnection.status) {
|
||||
case ESTABLISHED: {
|
||||
uti.tableItem.setBackground(1, this.color1);
|
||||
break;
|
||||
}
|
||||
case ABORTED: {
|
||||
uti.tableItem.setBackground(1, this.color3);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
uti.tableItem.setBackground(1, this.color2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Color getColorForValue(final IndicatorData indicatorData, final double value) {
|
||||
|
||||
for (int i = 0; i < indicatorData.thresholdColor.length; i++) {
|
||||
if (value >= indicatorData.thresholdColor[i].value) {
|
||||
return indicatorData.thresholdColor[i].color;
|
||||
}
|
||||
}
|
||||
|
||||
return this.color1;
|
||||
}
|
||||
|
||||
private static final class UpdatableTableItem {
|
||||
|
||||
final Long connectionId;
|
||||
TableItem tableItem;
|
||||
ClientConnectionData previous_connectionData;
|
||||
ClientConnectionData connectionData;
|
||||
|
||||
private UpdatableTableItem(final Table parent, final Long connectionId) {
|
||||
this.tableItem = null;
|
||||
this.connectionId = connectionId;
|
||||
}
|
||||
|
||||
public String getStatusName() {
|
||||
if (this.connectionData != null && this.connectionData.clientConnection.status != null) {
|
||||
return this.connectionData.clientConnection.status.name();
|
||||
}
|
||||
return ConnectionStatus.UNDEFINED.name();
|
||||
}
|
||||
|
||||
public String getConnectionAddress() {
|
||||
if (this.connectionData != null && this.connectionData.clientConnection.clientAddress != null) {
|
||||
return this.connectionData.clientConnection.clientAddress;
|
||||
}
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
public String getConnectionIdentifer() {
|
||||
if (this.connectionData != null && this.connectionData.clientConnection.userSessionId != null) {
|
||||
return this.connectionData.clientConnection.userSessionId;
|
||||
}
|
||||
|
||||
return "- " + this.connectionId + " -";
|
||||
}
|
||||
|
||||
public boolean hasStatus(final ConnectionStatus status) {
|
||||
if (this.connectionData != null && this.connectionData.clientConnection != null) {
|
||||
return status == this.connectionData.clientConnection.status;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void push(final ClientConnectionData connectionData) {
|
||||
this.previous_connectionData = this.connectionData;
|
||||
this.connectionData = connectionData;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class IndicatorData {
|
||||
final int index;
|
||||
final Indicator indicator;
|
||||
final ThresholdColor[] thresholdColor;
|
||||
|
||||
protected IndicatorData(final Indicator indicator, final int index, final Display display) {
|
||||
this.indicator = indicator;
|
||||
this.index = index;
|
||||
this.thresholdColor = new ThresholdColor[indicator.thresholds.size()];
|
||||
for (int i = 0; i < indicator.thresholds.size(); i++) {
|
||||
this.thresholdColor[i] = new ThresholdColor(indicator.thresholds.get(i), display);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class ThresholdColor {
|
||||
final double value;
|
||||
final Color color;
|
||||
|
||||
protected ThresholdColor(final Threshold threshold, final Display display) {
|
||||
this.value = threshold.value;
|
||||
final RGB rgb = new RGB(
|
||||
Integer.parseInt(threshold.color.substring(0, 2), 16),
|
||||
Integer.parseInt(threshold.color.substring(2, 4), 16),
|
||||
Integer.parseInt(threshold.color.substring(4, 6), 16));
|
||||
this.color = new Color(display, rgb, 100);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -370,6 +370,13 @@ public class WidgetFactory {
|
|||
return table;
|
||||
}
|
||||
|
||||
public TableColumn tableColumnLocalized(
|
||||
final Table table,
|
||||
final LocTextKey locTextKey) {
|
||||
|
||||
return tableColumnLocalized(table, locTextKey, null);
|
||||
}
|
||||
|
||||
public TableColumn tableColumnLocalized(
|
||||
final Table table,
|
||||
final LocTextKey locTextKey,
|
||||
|
|
|
@ -26,6 +26,7 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
|||
import ch.ethz.seb.sebserver.gbl.model.EntityName;
|
||||
import ch.ethz.seb.sebserver.gbl.model.ModelIdAware;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
||||
/** Defines generic interface for all Entity based Data Access Objects
|
||||
*
|
||||
|
@ -129,7 +130,7 @@ public interface EntityDAO<T extends Entity, M extends ModelIdAware> {
|
|||
* @return Result referring to collection of all matching entities or an error if happened */
|
||||
@Transactional(readOnly = true)
|
||||
default Result<Collection<T>> allMatching(final FilterMap filterMap) {
|
||||
return allMatching(filterMap, e -> true);
|
||||
return allMatching(filterMap, Utils.truePredicate());
|
||||
}
|
||||
|
||||
/** Get a (unordered) collection of all Entities that matches a given filter criteria
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
@ -69,6 +70,16 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
|
|||
return new ResponseEntity<>(valErrors, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ExceptionHandler(OAuth2Exception.class)
|
||||
public ResponseEntity<Object> handleBeanValidationException(
|
||||
final OAuth2Exception ex,
|
||||
final WebRequest request) {
|
||||
|
||||
log.error("OAuth2Exception: ", ex);
|
||||
final APIMessage message = APIMessage.ErrorMessage.UNAUTHORIZED.of(ex.getMessage());
|
||||
return new ResponseEntity<>(message, HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@ExceptionHandler(BeanValidationException.class)
|
||||
public ResponseEntity<Object> handleBeanValidationException(
|
||||
final BeanValidationException ex,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -77,16 +78,16 @@ public class ExamAPI_V1_Controller {
|
|||
|
||||
@RequestMapping(
|
||||
path = API.EXAM_API_HANDSHAKE_ENDPOINT,
|
||||
method = RequestMethod.GET,
|
||||
method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public Collection<RunningExam> handshakeCreate(
|
||||
@RequestParam(name = API.PARAM_INSTITUTION_ID, required = false) final Long instIdRequestParam,
|
||||
@RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = false) final Long examIdRequestParam,
|
||||
@RequestBody final MultiValueMap<String, String> formParams,
|
||||
@RequestBody(required = false) final MultiValueMap<String, String> formParams,
|
||||
final Principal principal,
|
||||
final HttpServletRequest request,
|
||||
final HttpServletResponse response) {
|
||||
final HttpServletResponse response) throws IOException {
|
||||
|
||||
final POSTMapper mapper = new POSTMapper(formParams);
|
||||
|
||||
|
|
|
@ -9,12 +9,14 @@
|
|||
package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
@ -25,8 +27,10 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
|||
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
|
||||
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.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException;
|
||||
|
@ -122,7 +126,7 @@ public class ExamMonitoringController {
|
|||
}
|
||||
|
||||
final List<Exam> exams = new ArrayList<>(this.examSessionService
|
||||
.getFilteredRunningExams(filterMap, exam -> true)
|
||||
.getFilteredRunningExams(filterMap, Utils.truePredicate())
|
||||
.getOrThrow());
|
||||
|
||||
return ExamAdministrationController.buildSortedExamPage(
|
||||
|
@ -132,4 +136,29 @@ public class ExamMonitoringController {
|
|||
exams);
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.MODEL_ID_VAR_PATH_SEGMENT,
|
||||
method = RequestMethod.GET,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public Collection<ClientConnectionData> getConnectionData(
|
||||
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long examId) {
|
||||
|
||||
// check if user has EXAM_SUPPORTER privilege.
|
||||
final SEBServerUser currentUser = this.authorization
|
||||
.getUserService()
|
||||
.getCurrentUser();
|
||||
|
||||
if (!currentUser.getUserRoles().contains(UserRole.EXAM_SUPPORTER)) {
|
||||
throw new PermissionDeniedException(
|
||||
EntityType.EXAM,
|
||||
PrivilegeType.READ,
|
||||
currentUser.getUserInfo().uuid);
|
||||
}
|
||||
|
||||
return this.examSessionService
|
||||
.getConnectionData(examId)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -101,7 +101,8 @@ public class UserActivityLogController {
|
|||
Utils.toMilliSeconds(to),
|
||||
activityTypes,
|
||||
entityTypes,
|
||||
log -> true).getOrThrow();
|
||||
Utils.truePredicate())
|
||||
.getOrThrow();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -10,17 +10,16 @@ package ch.ethz.seb.sebserver.webservice.weblayer.oauth;
|
|||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
import org.springframework.security.oauth2.provider.ClientDetailsService;
|
||||
import org.springframework.security.oauth2.provider.ClientRegistrationException;
|
||||
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.WebSecurityConfig;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebClientConfigService;
|
||||
|
@ -35,13 +34,11 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebClientConfigSe
|
|||
@Component
|
||||
public class WebClientDetailsService implements ClientDetailsService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WebClientDetailsService.class);
|
||||
|
||||
private final SebClientConfigService sebClientConfigService;
|
||||
private final AdminAPIClientDetails adminClientDetails;
|
||||
@Autowired
|
||||
@Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME)
|
||||
private PasswordEncoder clientPasswordEncoder;
|
||||
|
||||
// TODO inject a collection of BaseClientDetails here to allow multiple admin client configurations
|
||||
public WebClientDetailsService(
|
||||
final AdminAPIClientDetails adminClientDetails,
|
||||
final SebClientConfigService sebClientConfigService) {
|
||||
|
@ -72,10 +69,18 @@ public class WebClientDetailsService implements ClientDetailsService {
|
|||
}
|
||||
|
||||
return getForExamClientAPI(clientId)
|
||||
.getOrThrow();
|
||||
.get(t -> {
|
||||
log.error("Client not found: ", t);
|
||||
throw new AccessDeniedException(t.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
protected Result<ClientDetails> getForExamClientAPI(final String clientId) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Trying to get ClientDetails for client: {}", clientId);
|
||||
}
|
||||
|
||||
return this.sebClientConfigService.getEncodedClientSecret(clientId)
|
||||
.map(pwd -> {
|
||||
final BaseClientDetails baseClientDetails = new BaseClientDetails(
|
||||
|
|
|
@ -10,6 +10,8 @@ sebserver.gui.webservice.protocol=http
|
|||
sebserver.gui.webservice.address=localhost
|
||||
sebserver.gui.webservice.port=8080
|
||||
sebserver.gui.webservice.apipath=/admin-api/v1
|
||||
# defines the polling interval that is used to poll the webservice for client connection data on a monitored exam page
|
||||
sebserver.gui.webservice.poll-interval=200
|
||||
|
||||
|
||||
sebserver.gui.theme=css/sebserver.css
|
||||
|
|
|
@ -878,3 +878,9 @@ sebserver.monitoring.exam.list.column.type=Type
|
|||
sebserver.monitoring.exam.list.column.startTime=Start Time
|
||||
sebserver.monitoring.exam.list.column.endTime=End Time
|
||||
|
||||
sebserver.monitoring.exam=Monitoring Exam: {0}
|
||||
|
||||
sebserver.monitoring.connection.list.column.id=Identifier
|
||||
sebserver.monitoring.connection.list.column.address=IP Address
|
||||
sebserver.monitoring.connection.list.column.status=Status
|
||||
|
||||
|
|
462
src/test/java/ch/ethz/seb/sebserver/HTTPClientBot.java
Normal file
462
src/test/java/ch/ethz/seb/sebserver/HTTPClientBot.java
Normal file
|
@ -0,0 +1,462 @@
|
|||
/*
|
||||
* Copyright (c) 2019 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;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorHandler;
|
||||
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.RunningExam;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
||||
public class HTTPClientBot {
|
||||
|
||||
private static final long ONE_SECOND = 1000; // milliseconds
|
||||
private static final long TEN_SECONDS = 10 * ONE_SECOND;
|
||||
private static final long ONE_MINUTE = 60 * ONE_SECOND;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(HTTPClientBot.class);
|
||||
|
||||
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
|
||||
|
||||
private final List<String> scopes = Arrays.asList("read", "write");
|
||||
|
||||
private final String webserviceAddress;
|
||||
private final String accessTokenEndpoint;
|
||||
private final String clientId;
|
||||
private final String clientSecret;
|
||||
private final String apiPath;
|
||||
private final String apiVersion;
|
||||
private final String examId;
|
||||
private final String institutionId;
|
||||
|
||||
private final int numberOfConnections;
|
||||
|
||||
private final long pingInterval;
|
||||
private final long errorInterval;
|
||||
private final long runtime;
|
||||
private final int connectionAttempts;
|
||||
|
||||
public HTTPClientBot(final Map<String, String> args) {
|
||||
this.webserviceAddress = args.getOrDefault("webserviceAddress", "http://localhost:8080");
|
||||
this.accessTokenEndpoint = args.getOrDefault("accessTokenEndpoint", "/oauth/token");
|
||||
this.clientId = args.getOrDefault("clientId", "TO_SET");
|
||||
this.clientSecret = args.getOrDefault("clientSecret", "TO_SET");
|
||||
this.apiPath = args.getOrDefault("apiPath", "/exam-api");
|
||||
this.apiVersion = args.getOrDefault("apiVersion", "v1");
|
||||
this.examId = args.getOrDefault("examId", "2");
|
||||
this.institutionId = args.getOrDefault("institutionId", "1");
|
||||
this.numberOfConnections = Integer.parseInt(args.getOrDefault("numberOfConnections", "1"));
|
||||
this.pingInterval = Long.parseLong(args.getOrDefault("pingInterval", "200"));
|
||||
this.errorInterval = Long.parseLong(args.getOrDefault("errorInterval", String.valueOf(TEN_SECONDS)));
|
||||
this.runtime = Long.parseLong(args.getOrDefault("runtime", String.valueOf(ONE_MINUTE)));
|
||||
this.connectionAttempts = Integer.parseInt(args.getOrDefault("connectionAttempts", "3"));
|
||||
|
||||
for (int i = 0; i < this.numberOfConnections; i++) {
|
||||
this.executorService.execute(new ConnectionBot("connection_" + i));
|
||||
}
|
||||
|
||||
this.executorService.shutdown();
|
||||
}
|
||||
|
||||
public static void main(final String[] args) {
|
||||
final Map<String, String> argsMap = new HashMap<>();
|
||||
if (args.length > 0) {
|
||||
for (final String arg : StringUtils.split(args[0], Constants.LIST_SEPARATOR)) {
|
||||
final String[] nameValue = StringUtils.split(arg, Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR);
|
||||
argsMap.put(nameValue[0], nameValue[1]);
|
||||
}
|
||||
}
|
||||
new HTTPClientBot(argsMap);
|
||||
}
|
||||
|
||||
private final class ConnectionBot implements Runnable {
|
||||
|
||||
private final String name;
|
||||
private final OAuth2RestTemplate restTemplate = createRestTemplate();
|
||||
|
||||
private final String handshakeURI = HTTPClientBot.this.webserviceAddress +
|
||||
HTTPClientBot.this.apiPath + "/" +
|
||||
HTTPClientBot.this.apiVersion + "/handshake";
|
||||
private final String configurartionURI = HTTPClientBot.this.webserviceAddress +
|
||||
HTTPClientBot.this.apiPath + "/" +
|
||||
HTTPClientBot.this.apiVersion + "/configuration";
|
||||
private final String pingURI = HTTPClientBot.this.webserviceAddress +
|
||||
HTTPClientBot.this.apiPath + "/" +
|
||||
HTTPClientBot.this.apiVersion + "/sebping";
|
||||
private final String eventURI = HTTPClientBot.this.webserviceAddress +
|
||||
HTTPClientBot.this.apiPath + "/" +
|
||||
HTTPClientBot.this.apiVersion + "/seblog";
|
||||
|
||||
private final HttpEntity<?> connectBody;
|
||||
|
||||
protected ConnectionBot(final String name) {
|
||||
this.name = name;
|
||||
final MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
|
||||
headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
this.connectBody = new HttpEntity<>(API.PARAM_INSTITUTION_ID +
|
||||
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
||||
HTTPClientBot.this.institutionId +
|
||||
Constants.FORM_URL_ENCODED_SEPARATOR +
|
||||
API.EXAM_API_PARAM_EXAM_ID +
|
||||
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
||||
HTTPClientBot.this.examId,
|
||||
headers);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
log.info("ConnectionBot {} : Client-Connection-Bot started: {}\n"
|
||||
+ "webserviceAddress: {}\n"
|
||||
+ "accessTokenEndpoint: {}\n"
|
||||
+ "clientId: {}\n"
|
||||
+ "clientSecret: {}\n"
|
||||
+ "apiPath: {}\n"
|
||||
+ "apiVersion: {}\n"
|
||||
+ "examId: {}\n"
|
||||
+ "institutionId: {}\n"
|
||||
+ "pingInterval: {}\n"
|
||||
+ "errorInterval: {}\n"
|
||||
+ "runtime: {}\n", this.name,
|
||||
HTTPClientBot.this.webserviceAddress,
|
||||
HTTPClientBot.this.accessTokenEndpoint,
|
||||
HTTPClientBot.this.clientId,
|
||||
HTTPClientBot.this.clientSecret,
|
||||
HTTPClientBot.this.apiPath,
|
||||
HTTPClientBot.this.apiVersion,
|
||||
HTTPClientBot.this.examId,
|
||||
HTTPClientBot.this.institutionId,
|
||||
HTTPClientBot.this.pingInterval,
|
||||
HTTPClientBot.this.errorInterval);
|
||||
|
||||
int attempt = 0;
|
||||
|
||||
while (attempt < HTTPClientBot.this.connectionAttempts) {
|
||||
attempt++;
|
||||
log.info("ConnectionBot {} : Try to request access-token; attempt: {}", this.name, attempt);
|
||||
try {
|
||||
|
||||
this.restTemplate.getAccessToken();
|
||||
|
||||
final String connectionToken = createConnection();
|
||||
if (connectionToken != null) {
|
||||
if (getConfig(connectionToken) && establishConnection(connectionToken)) {
|
||||
|
||||
final PingEntity pingHeader = new PingEntity(connectionToken);
|
||||
final EventEntity eventHeader = new EventEntity(connectionToken);
|
||||
|
||||
try {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final long endTime = startTime + HTTPClientBot.this.runtime;
|
||||
long currentTime = startTime;
|
||||
long lastPingTime = startTime;
|
||||
long lastErrorTime = startTime;
|
||||
|
||||
while (currentTime < endTime) {
|
||||
if (currentTime - lastPingTime >= HTTPClientBot.this.pingInterval) {
|
||||
pingHeader.next();
|
||||
sendPing(pingHeader);
|
||||
lastPingTime = currentTime;
|
||||
}
|
||||
if (currentTime - lastErrorTime >= HTTPClientBot.this.errorInterval) {
|
||||
eventHeader.next();
|
||||
sendErrorEvent(eventHeader);
|
||||
lastErrorTime = currentTime;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (final Exception e) {
|
||||
}
|
||||
currentTime = System.currentTimeMillis();
|
||||
}
|
||||
} catch (final Throwable t) {
|
||||
log.error("ConnectionBot {} : Error sending events: ", this.name, t);
|
||||
} finally {
|
||||
disconnect(connectionToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("ConnectionBot {} : Failed to request access-token: ", this.name, e);
|
||||
if (attempt >= HTTPClientBot.this.connectionAttempts) {
|
||||
log.error("ConnectionBot {} : Gave up afer {} connection attempts: ", this.name, attempt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String createConnection() {
|
||||
log.info("ConnectionBot {} : init connection", this.name);
|
||||
|
||||
try {
|
||||
final ResponseEntity<Collection<RunningExam>> exchange = this.restTemplate.exchange(
|
||||
this.handshakeURI,
|
||||
HttpMethod.POST,
|
||||
this.connectBody,
|
||||
new ParameterizedTypeReference<Collection<RunningExam>>() {
|
||||
});
|
||||
|
||||
final HttpStatus statusCode = exchange.getStatusCode();
|
||||
if (statusCode.isError()) {
|
||||
throw new RuntimeException("Webservice answered with error: " + exchange.getBody());
|
||||
}
|
||||
|
||||
final Collection<RunningExam> body = exchange.getBody();
|
||||
final String token = exchange.getHeaders().getFirst(API.EXAM_API_SEB_CONNECTION_TOKEN);
|
||||
|
||||
log.info("ConnectionBot {} : successfully created connection, token: {} body: {} ", token, body);
|
||||
|
||||
return token;
|
||||
} catch (final Exception e) {
|
||||
log.error("ConnectionBot {} : Failed to init connection", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getConfig(final String connectionToken) {
|
||||
final HttpEntity<?> configHeader = new HttpEntity<>(
|
||||
API.EXAM_API_PARAM_EXAM_ID +
|
||||
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
||||
HTTPClientBot.this.examId);
|
||||
configHeader.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
configHeader.getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
||||
|
||||
log.info("ConnectionBot {} : get SEB Configuration", this.name);
|
||||
|
||||
try {
|
||||
final ResponseEntity<byte[]> exchange = this.restTemplate.exchange(
|
||||
this.configurartionURI,
|
||||
HttpMethod.GET,
|
||||
configHeader,
|
||||
new ParameterizedTypeReference<byte[]>() {
|
||||
});
|
||||
|
||||
final HttpStatus statusCode = exchange.getStatusCode();
|
||||
if (statusCode.isError()) {
|
||||
throw new RuntimeException("Webservice answered with error: " + exchange.getBody());
|
||||
}
|
||||
|
||||
final byte[] config = exchange.getBody();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("ConnectionBot {} : successfully requested exam config: " + Utils.toString(config));
|
||||
} else {
|
||||
log.info("ConnectionBot {} : successfully requested exam config");
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (final Exception e) {
|
||||
log.error("ConnectionBot {} : Failed get SEB Configuration", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean establishConnection(final String connectionToken) {
|
||||
final HttpEntity<?> configHeader = new HttpEntity<>(
|
||||
API.EXAM_API_USER_SESSION_ID +
|
||||
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
||||
this.name);
|
||||
configHeader.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
configHeader.getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
||||
|
||||
log.info("ConnectionBot {} : Trying to establish SEB client connection", this.name);
|
||||
|
||||
try {
|
||||
|
||||
final ResponseEntity<Object> exchange = this.restTemplate.exchange(
|
||||
this.handshakeURI,
|
||||
HttpMethod.PUT,
|
||||
configHeader,
|
||||
new ParameterizedTypeReference<>() {
|
||||
});
|
||||
|
||||
final HttpStatus statusCode = exchange.getStatusCode();
|
||||
if (statusCode.isError()) {
|
||||
throw new RuntimeException("Webservice answered with error: " + exchange.getBody());
|
||||
}
|
||||
|
||||
log.info("ConnectionBot {} : successfully established SEB client connection");
|
||||
|
||||
return true;
|
||||
} catch (final Exception e) {
|
||||
log.error("ConnectionBot {} : Failed get established SEB client connection", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean sendPing(final HttpEntity<String> pingHeader) {
|
||||
try {
|
||||
|
||||
this.restTemplate.exchange(
|
||||
this.pingURI,
|
||||
HttpMethod.POST,
|
||||
pingHeader,
|
||||
new ParameterizedTypeReference<>() {
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (final Exception e) {
|
||||
log.error("ConnectionBot {} : Failed send ping", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean sendErrorEvent(final HttpEntity<String> eventHeader) {
|
||||
try {
|
||||
|
||||
this.restTemplate.exchange(
|
||||
this.eventURI,
|
||||
HttpMethod.POST,
|
||||
eventHeader,
|
||||
new ParameterizedTypeReference<>() {
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (final Exception e) {
|
||||
log.error("ConnectionBot {} : Failed send ping", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean disconnect(final String connectionToken) {
|
||||
final HttpEntity<?> configHeader = new HttpEntity<>(null);
|
||||
configHeader.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
configHeader.getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
||||
|
||||
log.info("ConnectionBot {} : Trying to delete SEB client connection", this.name);
|
||||
|
||||
try {
|
||||
|
||||
final ResponseEntity<Object> exchange = this.restTemplate.exchange(
|
||||
this.handshakeURI,
|
||||
HttpMethod.DELETE,
|
||||
configHeader,
|
||||
new ParameterizedTypeReference<>() {
|
||||
});
|
||||
|
||||
final HttpStatus statusCode = exchange.getStatusCode();
|
||||
if (statusCode.isError()) {
|
||||
throw new RuntimeException("Webservice answered with error: " + exchange.getBody());
|
||||
}
|
||||
|
||||
log.info("ConnectionBot {} : successfully deleted SEB client connection");
|
||||
|
||||
return true;
|
||||
} catch (final Exception e) {
|
||||
log.error("ConnectionBot {} : Failed get deleted SEB client connection", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private OAuth2RestTemplate createRestTemplate() {
|
||||
final ClientCredentialsResourceDetails clientCredentialsResourceDetails =
|
||||
new ClientCredentialsResourceDetails();
|
||||
clientCredentialsResourceDetails.setAccessTokenUri(this.webserviceAddress + this.accessTokenEndpoint);
|
||||
clientCredentialsResourceDetails.setClientId(this.clientId);
|
||||
clientCredentialsResourceDetails.setClientSecret(this.clientSecret);
|
||||
clientCredentialsResourceDetails.setScope(this.scopes);
|
||||
|
||||
final OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(clientCredentialsResourceDetails);
|
||||
restTemplate.setErrorHandler(new OAuth2ErrorHandler(clientCredentialsResourceDetails));
|
||||
restTemplate
|
||||
.getMessageConverters()
|
||||
.add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
|
||||
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
private static class PingEntity extends HttpEntity<String> {
|
||||
private final String pingBodyTemplate = API.EXAM_API_PING_TIMESTAMP +
|
||||
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
||||
"{}" +
|
||||
Constants.FORM_URL_ENCODED_SEPARATOR +
|
||||
API.EXAM_API_PING_NUMBER +
|
||||
Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR +
|
||||
"{}";
|
||||
|
||||
private long timestamp = 0;
|
||||
private int count = 0;
|
||||
|
||||
protected PingEntity(final String connectionToken) {
|
||||
super();
|
||||
getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
||||
}
|
||||
|
||||
void next() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
this.count++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody() {
|
||||
return String.format(this.pingBodyTemplate, this.timestamp, this.count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasBody() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class EventEntity extends HttpEntity<String> {
|
||||
private final String eventBodyTemplate =
|
||||
"{ \"type\": \"ERROR_LOG\", \"timestamp\": {}, \"text\": \"some error\" }";
|
||||
|
||||
private long timestamp = 0;
|
||||
|
||||
protected EventEntity(final String connectionToken) {
|
||||
super();
|
||||
getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
getHeaders().set(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken);
|
||||
}
|
||||
|
||||
void next() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody() {
|
||||
return String.format(this.eventBodyTemplate, this.timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasBody() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -136,7 +136,7 @@ public abstract class ExamAPIIntegrationTester {
|
|||
final Long institutionId,
|
||||
final Long examId) throws Exception {
|
||||
|
||||
final MockHttpServletRequestBuilder builder = get(this.endpoint + "/handshake")
|
||||
final MockHttpServletRequestBuilder builder = post(this.endpoint + "/handshake")
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.header("Authorization", "Bearer " + accessToken)
|
||||
.accept(MediaType.APPLICATION_JSON_UTF8_VALUE);
|
||||
|
|
|
@ -168,7 +168,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester {
|
|||
new TypeReference<Collection<APIMessage>>() {
|
||||
});
|
||||
final APIMessage error = errorMessage.iterator().next();
|
||||
assertEquals(ErrorMessage.GENERIC.messageCode, error.messageCode);
|
||||
assertEquals(ErrorMessage.ILLEGAL_API_ARGUMENT.messageCode, error.messageCode);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in a new issue