SEBSERV-30 preparing for Exam and Quiz gui and javadoc

This commit is contained in:
anhefti 2019-03-14 16:56:34 +01:00
parent 7b2f7228af
commit d404498475
35 changed files with 877 additions and 196 deletions

View file

@ -48,7 +48,7 @@ public final class API {
public static final String USER_ACCOUNT_ENDPOINT = "/useraccount"; public static final String USER_ACCOUNT_ENDPOINT = "/useraccount";
public static final String QUIZ_IMPORT_ENDPOINT = "/quiz"; public static final String QUIZ_DISCOVERY_ENDPOINT = "/quiz";
public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam"; public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam";

View file

@ -27,6 +27,7 @@ public final class QuizData implements Entity {
public static final String FILTER_ATTR_START_TIME = "start_timestamp"; public static final String FILTER_ATTR_START_TIME = "start_timestamp";
public static final String QUIZ_ATTR_ID = "quiz_id"; public static final String QUIZ_ATTR_ID = "quiz_id";
public static final String QUIZ_ATTR_LMS_SETUP_ID = "lms_setup_id";
public static final String QUIZ_ATTR_NAME = "quiz_name"; public static final String QUIZ_ATTR_NAME = "quiz_name";
public static final String QUIZ_ATTR_DESCRIPTION = "quiz_description"; public static final String QUIZ_ATTR_DESCRIPTION = "quiz_description";
public static final String QUIZ_ATTR_START_TIME = "quiz_start_time"; public static final String QUIZ_ATTR_START_TIME = "quiz_start_time";
@ -36,6 +37,9 @@ public final class QuizData implements Entity {
@JsonProperty(QUIZ_ATTR_ID) @JsonProperty(QUIZ_ATTR_ID)
public final String id; public final String id;
@JsonProperty(QUIZ_ATTR_LMS_SETUP_ID)
public final String lmsSetupId;
@JsonProperty(QUIZ_ATTR_NAME) @JsonProperty(QUIZ_ATTR_NAME)
public final String name; public final String name;
@ -54,6 +58,7 @@ public final class QuizData implements Entity {
@JsonCreator @JsonCreator
public QuizData( public QuizData(
@JsonProperty(QUIZ_ATTR_ID) final String id, @JsonProperty(QUIZ_ATTR_ID) final String id,
@JsonProperty(QUIZ_ATTR_LMS_SETUP_ID) final String lmsSetupId,
@JsonProperty(QUIZ_ATTR_NAME) final String name, @JsonProperty(QUIZ_ATTR_NAME) final String name,
@JsonProperty(QUIZ_ATTR_DESCRIPTION) final String description, @JsonProperty(QUIZ_ATTR_DESCRIPTION) final String description,
@JsonProperty(QUIZ_ATTR_START_TIME) final DateTime startTime, @JsonProperty(QUIZ_ATTR_START_TIME) final DateTime startTime,
@ -61,6 +66,7 @@ public final class QuizData implements Entity {
@JsonProperty(QUIZ_ATTR_START_URL) final String startURL) { @JsonProperty(QUIZ_ATTR_START_URL) final String startURL) {
this.id = id; this.id = id;
this.lmsSetupId = lmsSetupId;
this.name = name; this.name = name;
this.description = description; this.description = description;
this.startTime = startTime; this.startTime = startTime;
@ -70,6 +76,7 @@ public final class QuizData implements Entity {
public QuizData( public QuizData(
final String id, final String id,
final String lmsSetupId,
final String name, final String name,
final String description, final String description,
final String startTime, final String startTime,
@ -77,6 +84,7 @@ public final class QuizData implements Entity {
final String startURL) { final String startURL) {
this.id = id; this.id = id;
this.lmsSetupId = lmsSetupId;
this.name = name; this.name = name;
this.description = description; this.description = description;
this.startTime = LocalDateTime this.startTime = LocalDateTime
@ -106,6 +114,10 @@ public final class QuizData implements Entity {
return this.id; return this.id;
} }
public String getLmsSetupId() {
return this.lmsSetupId;
}
@Override @Override
public String getName() { public String getName() {
return this.name; return this.name;
@ -154,9 +166,9 @@ public final class QuizData implements Entity {
@Override @Override
public String toString() { public String toString() {
return "QuizData [id=" + this.id + ", name=" + this.name + ", description=" + this.description + ", startTime=" return "QuizData [id=" + this.id + ", lmsSetupId=" + this.lmsSetupId + ", name=" + this.name + ", description="
+ this.startTime + this.description
+ ", endTime=" + this.endTime + ", startURL=" + this.startURL + "]"; + ", startTime=" + this.startTime + ", endTime=" + this.endTime + ", startURL=" + this.startURL + "]";
} }
public static Comparator<QuizData> getIdComparator(final boolean descending) { public static Comparator<QuizData> getIdComparator(final boolean descending) {

View file

@ -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.content;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.form.PageFormService;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
@Lazy
@Component
@GuiProfile
public class ExamForm implements TemplateComposer {
private final PageFormService pageFormService;
private final ResourceService resourceService;
protected ExamForm(
final PageFormService pageFormService,
final ResourceService resourceService) {
this.pageFormService = pageFormService;
this.resourceService = resourceService;
}
@Override
public void compose(final PageContext pageContext) {
// TODO Auto-generated method stub
}
}

View file

@ -0,0 +1,46 @@
/*
* 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.content;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@Component
@GuiProfile
public class ExamList implements TemplateComposer {
private final WidgetFactory widgetFactory;
private final ResourceService resourceService;
private final int pageSize;
protected ExamList(
final WidgetFactory widgetFactory,
final ResourceService resourceService,
@Value("${sebserver.gui.list.page.size}") final Integer pageSize) {
this.widgetFactory = widgetFactory;
this.resourceService = resourceService;
this.pageSize = (pageSize != null) ? pageSize : 20;
}
@Override
public void compose(final PageContext pageContext) {
// TODO Auto-generated method stub
}
}

View file

@ -114,7 +114,7 @@ public class LmsSetupForm implements TemplateComposer {
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(lmsSetup); final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(lmsSetup);
final boolean writeGrant = userGrantCheck.w(); final boolean writeGrant = userGrantCheck.w();
final boolean modifyGrant = userGrantCheck.m(); final boolean modifyGrant = userGrantCheck.m();
final boolean istitutionActive = restService.getBuilder(GetInstitution.class) final boolean institutionActive = restService.getBuilder(GetInstitution.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(lmsSetup.getInstitutionId())) .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(lmsSetup.getInstitutionId()))
.call() .call()
.map(inst -> inst.active) .map(inst -> inst.active)
@ -177,27 +177,27 @@ public class LmsSetupForm implements TemplateComposer {
formContext.clearEntityKeys() formContext.clearEntityKeys()
.createAction(ActionDefinition.LMS_SETUP_NEW) .createAction(ActionDefinition.LMS_SETUP_NEW)
.publishIf(() -> writeGrant && readonly && istitutionActive) .publishIf(() -> writeGrant && readonly && institutionActive)
.createAction(ActionDefinition.LMS_SETUP_MODIFY) .createAction(ActionDefinition.LMS_SETUP_MODIFY)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.publishIf(() -> modifyGrant && readonly && istitutionActive) .publishIf(() -> modifyGrant && readonly && institutionActive)
.createAction(ActionDefinition.LMS_SETUP_TEST) .createAction(ActionDefinition.LMS_SETUP_TEST)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(action -> this.testLmsSetup(action, formHandle)) .withExec(action -> this.testLmsSetup(action, formHandle))
.publishIf(() -> modifyGrant && isNotNew.getAsBoolean() && istitutionActive) .publishIf(() -> modifyGrant && isNotNew.getAsBoolean() && institutionActive)
.createAction(ActionDefinition.LMS_SETUP_DEACTIVATE) .createAction(ActionDefinition.LMS_SETUP_DEACTIVATE)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(restService::activation) .withExec(restService::activation)
.withConfirm(PageUtils.confirmDeactivation(lmsSetup, restService)) .withConfirm(PageUtils.confirmDeactivation(lmsSetup, restService))
.publishIf(() -> writeGrant && readonly && istitutionActive && lmsSetup.isActive()) .publishIf(() -> writeGrant && readonly && institutionActive && lmsSetup.isActive())
.createAction(ActionDefinition.LMS_SETUP_ACTIVATE) .createAction(ActionDefinition.LMS_SETUP_ACTIVATE)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(restService::activation) .withExec(restService::activation)
.publishIf(() -> writeGrant && readonly && istitutionActive && !lmsSetup.isActive()) .publishIf(() -> writeGrant && readonly && institutionActive && !lmsSetup.isActive())
.createAction(ActionDefinition.LMS_SETUP_SAVE) .createAction(ActionDefinition.LMS_SETUP_SAVE)
.withExec(formHandle::processFormSave) .withExec(formHandle::processFormSave)
@ -208,7 +208,6 @@ public class LmsSetupForm implements TemplateComposer {
.withExec(Action::onEmptyEntityKeyGoToActivityHome) .withExec(Action::onEmptyEntityKeyGoToActivityHome)
.withConfirm("sebserver.overall.action.modify.cancel.confirm") .withConfirm("sebserver.overall.action.modify.cancel.confirm")
.publishIf(() -> !readonly); .publishIf(() -> !readonly);
} }
/** LmsSetup test action implementation */ /** LmsSetup test action implementation */

View file

@ -0,0 +1,46 @@
/*
* 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.content;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@Component
@GuiProfile
public class QuizDiscoveryList implements TemplateComposer {
private final WidgetFactory widgetFactory;
private final ResourceService resourceService;
private final int pageSize;
protected QuizDiscoveryList(
final WidgetFactory widgetFactory,
final ResourceService resourceService,
@Value("${sebserver.gui.list.page.size}") final Integer pageSize) {
this.widgetFactory = widgetFactory;
this.resourceService = resourceService;
this.pageSize = (pageSize != null) ? pageSize : 20;
}
@Override
public void compose(final PageContext pageContext) {
// TODO Auto-generated method stub
}
}

View file

@ -106,7 +106,7 @@ public class UserAccountForm implements TemplateComposer {
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(userAccount); final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(userAccount);
final boolean writeGrant = userGrantCheck.w(); final boolean writeGrant = userGrantCheck.w();
final boolean modifyGrant = userGrantCheck.m(); final boolean modifyGrant = userGrantCheck.m();
final boolean istitutionActive = restService.getBuilder(GetInstitution.class) final boolean institutionActive = restService.getBuilder(GetInstitution.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(userAccount.getInstitutionId())) .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(userAccount.getInstitutionId()))
.call() .call()
.map(inst -> inst.active) .map(inst -> inst.active)
@ -190,26 +190,26 @@ public class UserAccountForm implements TemplateComposer {
formContext.clearEntityKeys() formContext.clearEntityKeys()
.createAction(ActionDefinition.USER_ACCOUNT_NEW) .createAction(ActionDefinition.USER_ACCOUNT_NEW)
.publishIf(() -> writeGrant && readonly && istitutionActive) .publishIf(() -> writeGrant && readonly && institutionActive)
.createAction(ActionDefinition.USER_ACCOUNT_MODIFY) .createAction(ActionDefinition.USER_ACCOUNT_MODIFY)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.publishIf(() -> modifyGrant && readonly && istitutionActive) .publishIf(() -> modifyGrant && readonly && institutionActive)
.createAction(ActionDefinition.USER_ACCOUNT_CHANGE_PASSOWRD) .createAction(ActionDefinition.USER_ACCOUNT_CHANGE_PASSOWRD)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.publishIf(() -> modifyGrant && readonly && istitutionActive && userAccount.isActive()) .publishIf(() -> modifyGrant && readonly && institutionActive && userAccount.isActive())
.createAction(ActionDefinition.USER_ACCOUNT_DEACTIVATE) .createAction(ActionDefinition.USER_ACCOUNT_DEACTIVATE)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(restService::activation) .withExec(restService::activation)
.withConfirm(PageUtils.confirmDeactivation(userAccount, restService)) .withConfirm(PageUtils.confirmDeactivation(userAccount, restService))
.publishIf(() -> writeGrant && readonly && istitutionActive && userAccount.isActive()) .publishIf(() -> writeGrant && readonly && institutionActive && userAccount.isActive())
.createAction(ActionDefinition.USER_ACCOUNT_ACTIVATE) .createAction(ActionDefinition.USER_ACCOUNT_ACTIVATE)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(restService::activation) .withExec(restService::activation)
.publishIf(() -> writeGrant && readonly && istitutionActive && !userAccount.isActive()) .publishIf(() -> writeGrant && readonly && institutionActive && !userAccount.isActive())
.createAction(ActionDefinition.USER_ACCOUNT_SAVE) .createAction(ActionDefinition.USER_ACCOUNT_SAVE)
.withExec(action -> { .withExec(action -> {

View file

@ -216,6 +216,7 @@ public final class Form implements FormBinding {
} }
} }
// following are FormFieldAccessor implementations for all field types
//@formatter:off //@formatter:off
private FormFieldAccessor createAccessor(final Label label, final Label field) { private FormFieldAccessor createAccessor(final Label label, final Label field) {
return new FormFieldAccessor(label, field) { return new FormFieldAccessor(label, field) {
@ -229,24 +230,17 @@ public final class Form implements FormBinding {
@Override public void setValue(final String value) { text.setText(value); } @Override public void setValue(final String value) { text.setText(value); }
}; };
} }
private FormFieldAccessor createAccessor( private FormFieldAccessor createAccessor(final Label label, final SingleSelection singleSelection) {
final Label label,
final SingleSelection singleSelection) {
return new FormFieldAccessor(label, singleSelection) { return new FormFieldAccessor(label, singleSelection) {
@Override public String getValue() { return singleSelection.getSelectionValue(); } @Override public String getValue() { return singleSelection.getSelectionValue(); }
@Override public void setValue(final String value) { singleSelection.select(value); } @Override public void setValue(final String value) { singleSelection.select(value); }
}; };
} }
private FormFieldAccessor createAccessor( private FormFieldAccessor createAccessor(final Label label,final MultiSelection multiSelection) {
final Label label,
final MultiSelection multiSelection) {
return new FormFieldAccessor(label, multiSelection) { return new FormFieldAccessor(label, multiSelection) {
@Override public String getValue() { return multiSelection.getSelectionValue(); } @Override public String getValue() { return multiSelection.getSelectionValue(); }
@Override public void setValue(final String value) { multiSelection.select(value); } @Override public void setValue(final String value) { multiSelection.select(value); }
@Override @Override public void putJsonValue(final String key, final ObjectNode objectRoot) {
public void putJsonValue(final String key, final ObjectNode objectRoot) {
final String value = getValue(); final String value = getValue();
if (StringUtils.isNoneBlank(value)) { if (StringUtils.isNoneBlank(value)) {
final ArrayNode arrayNode = objectRoot.putArray(key); final ArrayNode arrayNode = objectRoot.putArray(key);

View file

@ -31,6 +31,7 @@ import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
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.institution.GetInstitutionNames; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetupNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
@Lazy @Lazy
@ -115,6 +116,35 @@ public class ResourceService {
}; };
} }
public List<Tuple<String>> lmsSetupResource(final Long institutionId) {
return this.restService.getBuilder(GetLmsSetupNames.class)
.withQueryParam(Domain.LMS_SETUP.ATTR_INSTITUTION_ID, String.valueOf(institutionId))
.withQueryParam(Domain.LMS_SETUP.ATTR_ACTIVE, "true")
.call()
.getOr(Collections.emptyList())
.stream()
.map(entityName -> new Tuple<>(entityName.modelId, entityName.name))
.collect(Collectors.toList());
}
public Function<String, String> getLmsSetupNameFunction(final Long institutionId) {
final Map<String, String> idNameMap = this.restService.getBuilder(GetLmsSetupNames.class)
.withQueryParam(Domain.LMS_SETUP.ATTR_INSTITUTION_ID, String.valueOf(institutionId))
.withQueryParam(Domain.INSTITUTION.ATTR_ACTIVE, "true")
.call()
.getOr(Collections.emptyList())
.stream()
.collect(Collectors.toMap(e -> e.modelId, e -> e.name));
return id -> {
if (!idNameMap.containsKey(id)) {
return Constants.EMPTY_NOTE;
}
return idNameMap.get(id);
};
}
/** Get a list of language key/name tuples for all supported languages in the /** Get a list of language key/name tuples for all supported languages in the
* language of the current users locale. * language of the current users locale.
* *

View file

@ -16,6 +16,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;
/** Puts RAP's server-push functionality in a well defined service by using a context
* as state holder and the possibility to split the server-push process into two
* separated processes, a business-process to get and update business data and the
* an update-process to update the UI after according to updated data */
@Lazy @Lazy
@Service @Service
public class ServerPushService { public class ServerPushService {

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api; package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -25,8 +26,10 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientResponseException; import org.springframework.web.client.RestClientResponseException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
@ -100,20 +103,7 @@ public abstract class RestCall<T> {
RestCall.this.typeKey.typeRef)); RestCall.this.typeKey.typeRef));
} else { } else {
return handleRestCallError(responseEntity);
final RestCallError restCallError =
new RestCallError("Response Entity: " + responseEntity.toString());
restCallError.errors.addAll(RestCall.this.jsonMapper.readValue(
responseEntity.getBody(),
new TypeReference<List<APIMessage>>() {
}));
log.debug(
"Webservice answered with well defined error- or validation-failure-response: ",
restCallError);
return Result.ofError(restCallError);
} }
} catch (final Throwable t) { } catch (final Throwable t) {
@ -149,6 +139,24 @@ public abstract class RestCall<T> {
return new RestCallBuilder(); return new RestCallBuilder();
} }
private Result<T> handleRestCallError(final ResponseEntity<String> responseEntity)
throws IOException, JsonParseException, JsonMappingException {
final RestCallError restCallError =
new RestCallError("Response Entity: " + responseEntity.toString());
restCallError.errors.addAll(RestCall.this.jsonMapper.readValue(
responseEntity.getBody(),
new TypeReference<List<APIMessage>>() {
}));
log.debug(
"Webservice answered with well defined error- or validation-failure-response: ",
restCallError);
return Result.ofError(restCallError);
}
public class RestCallBuilder { public class RestCallBuilder {
private final HttpHeaders httpHeaders = new HttpHeaders(); private final HttpHeaders httpHeaders = new HttpHeaders();

View file

@ -8,113 +8,75 @@
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api; package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
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.JSONMapper;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
import ch.ethz.seb.sebserver.gui.service.page.action.Action; import ch.ethz.seb.sebserver.gui.service.page.action.Action;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall.CallType; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall.CallType;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService;
@Lazy /** Interface to SEB Server webservice API thought RestCall's
@Service * or thought Spring's RestTemplate on lower level.
@GuiProfile *
public class RestService { * A RestCall's can be used to call a specified SEB Server webservice API endpoint with given request parameter.
* This service collects all the available RestCalls and map them by Class type or EntityType and CallType.
*
* For Example if one want to get a certain User-Account by API request on SEB Server webservice API:
*
* <pre>
* UserInfo userInfo = RestService.getBuilder(GetUserAccount.class)
* .withURIVariable(API.PARAM_MODEL_ID, user-account-id) adds an URI path variable
* .withQueryParam(API.PARAM_INSTITUTION_ID, institutionId) adds a URI query parameter
* .call() executes the API request call
* .get(pageContext::notifyError) gets the result or notify an error to the user if happened
* </pre>
*/
public interface RestService {
private final AuthorizationContextHolder authorizationContextHolder; /** Get Spring's RestTemplate that is used within this service.
private final WebserviceURIService webserviceURIBuilderSupplier; *
private final Map<String, RestCall<?>> calls; * @return Spring's RestTemplate that is used within this service. */
RestTemplate getWebserviceAPIRestTemplate();
public RestService( /** Get Spring's UriComponentsBuilder that is used within this service.
final AuthorizationContextHolder authorizationContextHolder, *
final JSONMapper jsonMapper, * @return Spring's UriComponentsBuilder that is used within this service. */
final Collection<RestCall<?>> calls) { UriComponentsBuilder getWebserviceURIBuilder();
this.authorizationContextHolder = authorizationContextHolder; /** Get a certain RestCall by Class type.
this.webserviceURIBuilderSupplier = authorizationContextHolder *
.getWebserviceURIService(); * @param type the Class type of the RestCall
* @return RestCall instance */
<T> RestCall<T> getRestCall(Class<? extends RestCall<T>> type);
this.calls = calls /** Get a certain RestCall by EntityType and CallType.
.stream() * NOTE not all RestCall can be get within this method. Only the ones that have a defined CallType
.collect(Collectors.toMap( *
call -> call.getClass().getName(), * @param entityType The EntityType of the RestCall
call -> call.init(this, jsonMapper))); * @param callType The CallType of the RestCall (not UNDEFINDED)
} * @return RestCall instance */
<T> RestCall<T> getRestCall(EntityType entityType, CallType callType);
public final RestTemplate getWebserviceAPIRestTemplate() { /** Get a certain RestCallBuilder by RestCall Class type.
return this.authorizationContextHolder *
.getAuthorizationContext() * @param type the Class type of the RestCall
.getRestTemplate(); * @return RestCallBuilder instance to build a dedicated call and execute it */
} <T> RestCall<T>.RestCallBuilder getBuilder(Class<? extends RestCall<T>> type);
public final UriComponentsBuilder getWebserviceURIBuilder() { /** Get a certain RestCallBuilder by EntityType and CallType.
return this.webserviceURIBuilderSupplier.getURIBuilder(); *
} * @param entityType The EntityType of the RestCall to get a builder for
* @param callType The CallType of the RestCall to get a builder for (not UNDEFINDED)
* @return RestCallBuilder instance to build a dedicated call and execute it */
<T> RestCall<T>.RestCallBuilder getBuilder(
EntityType entityType,
CallType callType);
@SuppressWarnings("unchecked") /** Performs an activation Action on RestCall specified within the given Action.
public final <T> RestCall<T> getRestCall(final Class<? extends RestCall<T>> type) { * The RestCall must be of CallType.ACTIVATION_ACTIVATE or CallType.ACTIVATION_DEACTIVATE
return (RestCall<T>) this.calls.get(type.getName()); *
} * @param action the Action that defines an entity activation
* @return the successfully executed Action */
<T> Action activation(Action action);
@SuppressWarnings("unchecked") }
public final <T> RestCall<T> getRestCall(final EntityType entityType, final CallType callType) {
return (RestCall<T>) this.calls.values()
.stream()
.filter(call -> call.typeKey.callType == callType && call.typeKey.entityType == entityType)
.findFirst()
.orElse(null);
}
public final <T> RestCall<T>.RestCallBuilder getBuilder(final Class<? extends RestCall<T>> type) {
@SuppressWarnings("unchecked")
final RestCall<T> restCall = (RestCall<T>) this.calls.get(type.getName());
if (restCall == null) {
return null;
}
return restCall.newBuilder();
}
public final <T> RestCall<T>.RestCallBuilder getBuilder(
final EntityType entityType,
final CallType callType) {
final RestCall<T> restCall = getRestCall(entityType, callType);
if (restCall == null) {
return null;
}
return restCall.newBuilder();
}
public <T> Action activation(final Action action) {
if (action.definition.restCallType == null) {
throw new IllegalArgumentException("ActionDefinition needs to define a restCallType to use this action");
}
@SuppressWarnings("unchecked")
final Class<? extends RestCall<T>> restCallType =
(Class<? extends RestCall<T>>) action.definition.restCallType;
this.getBuilder(restCallType)
.withURIVariable(
API.PARAM_MODEL_ID,
action.pageContext().getAttribute(AttributeKeys.ENTITY_ID))
.call()
.onErrorDo(t -> action.pageContext().notifyError(t));
return action;
}
}

View file

@ -0,0 +1,136 @@
/*
* 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;
import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
import ch.ethz.seb.sebserver.gui.service.page.action.Action;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall.CallType;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.WebserviceURIService;
@Lazy
@Service
@GuiProfile
public class RestServiceImpl implements RestService {
private final AuthorizationContextHolder authorizationContextHolder;
private final WebserviceURIService webserviceURIBuilderSupplier;
private final Map<String, RestCall<?>> calls;
public RestServiceImpl(
final AuthorizationContextHolder authorizationContextHolder,
final JSONMapper jsonMapper,
final Collection<RestCall<?>> calls) {
this.authorizationContextHolder = authorizationContextHolder;
this.webserviceURIBuilderSupplier = authorizationContextHolder
.getWebserviceURIService();
this.calls = calls
.stream()
.collect(Collectors.toMap(
call -> call.getClass().getName(),
call -> call.init(this, jsonMapper)));
}
@Override
public final RestTemplate getWebserviceAPIRestTemplate() {
return this.authorizationContextHolder
.getAuthorizationContext()
.getRestTemplate();
}
@Override
public final UriComponentsBuilder getWebserviceURIBuilder() {
return this.webserviceURIBuilderSupplier.getURIBuilder();
}
@Override
@SuppressWarnings("unchecked")
public final <T> RestCall<T> getRestCall(final Class<? extends RestCall<T>> type) {
return (RestCall<T>) this.calls.get(type.getName());
}
@Override
@SuppressWarnings("unchecked")
public final <T> RestCall<T> getRestCall(final EntityType entityType, final CallType callType) {
if (callType == CallType.UNDEFINED) {
throw new IllegalArgumentException("Undefined CallType not supported");
}
return (RestCall<T>) this.calls.values()
.stream()
.filter(call -> call.typeKey.callType == callType && call.typeKey.entityType == entityType)
.findFirst()
.orElse(null);
}
@Override
public final <T> RestCall<T>.RestCallBuilder getBuilder(final Class<? extends RestCall<T>> type) {
@SuppressWarnings("unchecked")
final RestCall<T> restCall = (RestCall<T>) this.calls.get(type.getName());
if (restCall == null) {
return null;
}
return restCall.newBuilder();
}
@Override
public final <T> RestCall<T>.RestCallBuilder getBuilder(
final EntityType entityType,
final CallType callType) {
if (callType == CallType.UNDEFINED) {
throw new IllegalArgumentException("Undefined CallType not supported");
}
final RestCall<T> restCall = getRestCall(entityType, callType);
if (restCall == null) {
return null;
}
return restCall.newBuilder();
}
@Override
public <T> Action activation(final Action action) {
if (action.definition.restCallType == null) {
throw new IllegalArgumentException("ActionDefinition needs to define a restCallType to use this action");
}
@SuppressWarnings("unchecked")
final Class<? extends RestCall<T>> restCallType =
(Class<? extends RestCall<T>>) action.definition.restCallType;
this.getBuilder(restCallType)
.withURIVariable(
API.PARAM_MODEL_ID,
action.pageContext().getAttribute(AttributeKeys.ENTITY_ID))
.call()
.onErrorDo(t -> action.pageContext().notifyError(t));
return action;
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class ActivateExam extends RestCall<EntityProcessingReport> {
protected ActivateExam() {
super(new TypeKey<>(
CallType.ACTIVATION_ACTIVATE,
EntityType.EXAM,
new TypeReference<EntityProcessingReport>() {
}),
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_ADMINISTRATION_ENDPOINT + API.PATH_VAR_ACTIVE);
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class DeactivateExam extends RestCall<EntityProcessingReport> {
protected DeactivateExam() {
super(new TypeKey<>(
CallType.ACTIVATION_DEACTIVATE,
EntityType.EXAM,
new TypeReference<EntityProcessingReport>() {
}),
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_ADMINISTRATION_ENDPOINT + API.PATH_VAR_INACTIVE);
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.exam.Exam;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetExam extends RestCall<Exam> {
protected GetExam() {
super(new TypeKey<>(
CallType.GET_SINGLE,
EntityType.EXAM,
new TypeReference<Exam>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_ADMINISTRATION_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT);
}
}

View file

@ -0,0 +1,43 @@
/*
* 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 java.util.Set;
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.EntityKey;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetExamDependencies extends RestCall<Set<EntityKey>> {
protected GetExamDependencies() {
super(new TypeKey<>(
CallType.GET_DEPENDENCIES,
EntityType.EXAM,
new TypeReference<Set<EntityKey>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_ADMINISTRATION_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.DEPENDENCY_PATH_SEGMENT);
}
}

View file

@ -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.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.EntityName;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetExamNames extends RestCall<List<EntityName>> {
protected GetExamNames() {
super(new TypeKey<>(
CallType.GET_NAMES,
EntityType.EXAM,
new TypeReference<List<EntityName>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_ADMINISTRATION_ENDPOINT + API.NAMES_PATH_SEGMENT);
}
}

View file

@ -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.Exam;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetExams extends RestCall<Page<Exam>> {
protected GetExams() {
super(new TypeKey<>(
CallType.GET_PAGE,
EntityType.EXAM,
new TypeReference<Page<Exam>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_ADMINISTRATION_ENDPOINT);
}
}

View file

@ -0,0 +1,39 @@
/*
* 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.exam.Exam;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class SaveExam extends RestCall<Exam> {
protected SaveExam() {
super(new TypeKey<>(
CallType.SAVE,
EntityType.EXAM,
new TypeReference<Exam>() {
}),
HttpMethod.PUT,
MediaType.APPLICATION_JSON_UTF8,
API.EXAM_ADMINISTRATION_ENDPOINT);
}
}

View file

@ -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.lmssetup;
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.EntityName;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetLmsSetupNames extends RestCall<List<EntityName>> {
protected GetLmsSetupNames() {
super(new TypeKey<>(
CallType.GET_NAMES,
EntityType.LMS_SETUP,
new TypeReference<List<EntityName>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.LMS_SETUP_ENDPOINT + API.NAMES_PATH_SEGMENT);
}
}

View file

@ -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.quiz;
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.QuizData;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetQuizzes extends RestCall<Page<QuizData>> {
protected GetQuizzes() {
super(new TypeKey<>(
CallType.GET_PAGE,
EntityType.EXAM,
new TypeReference<Page<QuizData>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.QUIZ_DISCOVERY_ENDPOINT);
}
}

View file

@ -0,0 +1,13 @@
/*
* 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.quiz;
public class ImportAsExam {
}

View file

@ -12,13 +12,28 @@ import javax.servlet.http.HttpSession;
import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.RWT;
/** Single point of access for SEBServerAuthorizationContext */
public interface AuthorizationContextHolder { public interface AuthorizationContextHolder {
/** Get the SEBServerAuthorizationContext that is bound the the given HttpSession.
* If there is no AuthorizationContext or an invalid one within the given HttpSession
* a new one is created for the given HttpSession
*
* @param session HttpSession instance
* @return SEBServerAuthorizationContext instance */
SEBServerAuthorizationContext getAuthorizationContext(HttpSession session); SEBServerAuthorizationContext getAuthorizationContext(HttpSession session);
/** Get the WebserviceURIService that is used within this AuthorizationContextHolder
*
* @return the WebserviceURIService that is used within this AuthorizationContextHolder */
WebserviceURIService getWebserviceURIService(); WebserviceURIService getWebserviceURIService();
// TODO error handling!? /** Get the SEBServerAuthorizationContext that is bound the HttpSession of the current
* RWT UISession.
* NOTE: This may throw an exception if RWT.getUISession().getHttpSession() throws one
* This is the case if there is no RWT UISession within the current Thread
*
* @return SEBServerAuthorizationContext instance */
default SEBServerAuthorizationContext getAuthorizationContext() { default SEBServerAuthorizationContext getAuthorizationContext() {
return getAuthorizationContext(RWT.getUISession().getHttpSession()); return getAuthorizationContext(RWT.getUISession().getHttpSession());
} }

View file

@ -20,7 +20,6 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.LMS_SETUP;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
@ -31,7 +30,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
* *
* A LmsAPITemplate defines at least the core API access to query courses and quizzes from the LMS * A LmsAPITemplate defines at least the core API access to query courses and quizzes from the LMS
* Later a concrete LmsAPITemplate may also implement some special features regarding to the type * Later a concrete LmsAPITemplate may also implement some special features regarding to the type
* of LMS */ * of the LMS */
public interface LmsAPITemplate { public interface LmsAPITemplate {
/** Get the underling LMSSetup configuration for this LmsAPITemplate /** Get the underling LMSSetup configuration for this LmsAPITemplate
@ -54,9 +53,17 @@ public interface LmsAPITemplate {
* or refer to an error when happened */ * or refer to an error when happened */
Result<List<QuizData>> getQuizzes(FilterMap filterMap); Result<List<QuizData>> getQuizzes(FilterMap filterMap);
/** Get all QuizData for the set of QuizData identifiers from LMS API in a collection
* of Result. If particular Quiz cannot be loaded because of errors or deletion,
* the Result will have an error reference.
*
* @param ids the Set of Quiz identifiers to get the QuizData for
* @return Collection of all QuizData from the given id set */
Collection<Result<QuizData>> getQuizzes(Set<String> ids); Collection<Result<QuizData>> getQuizzes(Set<String> ids);
Result<ExamineeAccountDetails> getExamineeAccountDetails(String examineeUserId); // TODO this can be used in a future release to resolve examinee's account detail information by an
// examinee identifier received by on SEB-Client connection.
//Result<ExamineeAccountDetails> getExamineeAccountDetails(String examineeUserId);
default List<APIMessage> attributeValidation(final ClientCredentials credentials) { default List<APIMessage> attributeValidation(final ClientCredentials credentials) {

View file

@ -21,7 +21,6 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
@ -50,27 +49,28 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
this.clientCredentialService = clientCredentialService; this.clientCredentialService = clientCredentialService;
this.credentials = credentials; this.credentials = credentials;
final String lmsSetupId = lmsSetup.getModelId();
this.mockups = new ArrayList<>(); this.mockups = new ArrayList<>();
this.mockups.add(new QuizData( this.mockups.add(new QuizData(
"quiz1", "Demo Quiz 1", "Demo Quit Mockup", "quiz1", lmsSetupId, "Demo Quiz 1", "Demo Quit Mockup",
"2020-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/")); "2020-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData( this.mockups.add(new QuizData(
"quiz2", "Demo Quiz 2", "Demo Quit Mockup", "quiz2", lmsSetupId, "Demo Quiz 2", "Demo Quit Mockup",
"2020-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/")); "2020-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData( this.mockups.add(new QuizData(
"quiz3", "Demo Quiz 3", "Demo Quit Mockup", "quiz3", lmsSetupId, "Demo Quiz 3", "Demo Quit Mockup",
"2018-07-30 09:00:00", "2018-08-01 00:00:00", "http://lms.mockup.com/api/")); "2018-07-30 09:00:00", "2018-08-01 00:00:00", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData( this.mockups.add(new QuizData(
"quiz4", "Demo Quiz 4", "Demo Quit Mockup", "quiz4", lmsSetupId, "Demo Quiz 4", "Demo Quit Mockup",
"2018-01-01 00:00:00", "2019-01-01 00:00:00", "http://lms.mockup.com/api/")); "2018-01-01 00:00:00", "2019-01-01 00:00:00", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData( this.mockups.add(new QuizData(
"quiz5", "Demo Quiz 5", "Demo Quit Mockup", "quiz5", lmsSetupId, "Demo Quiz 5", "Demo Quit Mockup",
"2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/")); "2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData( this.mockups.add(new QuizData(
"quiz6", "Demo Quiz 6", "Demo Quit Mockup", "quiz6", lmsSetupId, "Demo Quiz 6", "Demo Quit Mockup",
"2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/")); "2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
this.mockups.add(new QuizData( this.mockups.add(new QuizData(
"quiz7", "Demo Quiz 7", "Demo Quit Mockup", "quiz7", lmsSetupId, "Demo Quiz 7", "Demo Quit Mockup",
"2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/")); "2018-01-01 09:00:00", "2021-01-01 09:00:00", "http://lms.mockup.com/api/"));
} }
@ -124,16 +124,6 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@Override
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeUserId) {
authenticate();
if (this.credentials == null) {
throw new IllegalArgumentException("Wrong clientId or secret");
}
return Result.of(new ExamineeAccountDetails(examineeUserId, "mockup", "mockup", "mockup"));
}
private boolean authenticate() { private boolean authenticate() {
try { try {

View file

@ -41,7 +41,6 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.SupplierWithCircuitBreaker; import ch.ethz.seb.sebserver.gbl.util.SupplierWithCircuitBreaker;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
@ -136,12 +135,6 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
return null; return null;
} }
@Override
public Result<ExamineeAccountDetails> getExamineeAccountDetails(final String examineeUserId) {
// TODO Auto-generated method stub
return null;
}
private Result<LmsSetup> initRestTemplateAndRequestAccessToken() { private Result<LmsSetup> initRestTemplateAndRequestAccessToken() {
log.info("Initialize Rest Template for OpenEdX API access. LmsSetup: {}", this.lmsSetup); log.info("Initialize Rest Template for OpenEdX API access. LmsSetup: {}", this.lmsSetup);
@ -247,6 +240,7 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate {
final String startURI = lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_START_URL_PREFIX + courseData.id; final String startURI = lmsSetup.lmsApiUrl + OPEN_EDX_DEFAULT_COURSE_START_URL_PREFIX + courseData.id;
return new QuizData( return new QuizData(
courseData.id, courseData.id,
lmsSetup.getModelId(),
courseData.name, courseData.name,
courseData.short_description, courseData.short_description,
courseData.start, courseData.start,

View file

@ -34,6 +34,11 @@ 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.validation.BeanValidationService; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
/** Abstract Entity-Controller that defines generic Entity rest API endpoints that are supported
* by all entity types that has activation feature and can be activated or deactivated.
*
* @param <T> The concrete Entity domain-model type used on all GET, PUT
* @param <M> The concrete Entity domain-model type used for POST methods (new) */
public abstract class ActivatableEntityController<T extends GrantEntity, M extends GrantEntity> public abstract class ActivatableEntityController<T extends GrantEntity, M extends GrantEntity>
extends EntityController<T, M> { extends EntityController<T, M> {

View file

@ -50,6 +50,11 @@ 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.validation.BeanValidationService; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
/** Abstract Entity-Controller that defines generic Entity rest API endpoints that are supported
* by all entity types.
*
* @param <T> The concrete Entity domain-model type used on all GET, PUT
* @param <M> The concrete Entity domain-model type used for POST methods (new) */
public abstract class EntityController<T extends GrantEntity, M extends GrantEntity> { public abstract class EntityController<T extends GrantEntity, M extends GrantEntity> {
protected final AuthorizationService authorization; protected final AuthorizationService authorization;
@ -75,6 +80,11 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
this.beanValidationService = beanValidationService; this.beanValidationService = beanValidationService;
} }
/** This is called by Spring to initialize the WebDataBinder and is used here to
* initialize the default value binding for the institutionId request-parameter
* that has the current users insitutionId as default.
*
* See also UserService.addUsersInstitutionDefaultPropertySupport */
@InitBinder @InitBinder
public void initBinder(final WebDataBinder binder) throws Exception { public void initBinder(final WebDataBinder binder) throws Exception {
this.authorization this.authorization
@ -83,14 +93,32 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
} }
// ****************** // ******************
// * GET (all) // * GET (getAll)
// ****************** // ******************
/** The get-all or get-page rest endpoint for all types of Entity and returns a Page of
* entities of specific type.
*
* GET /{api}/{entity-type-endpoint-name}
*
* 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
*
* @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 */
@RequestMapping( @RequestMapping(
method = RequestMethod.GET, method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE) produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Page<T> getAll( public Page<T> getPage(
@RequestParam( @RequestParam(
name = API.PARAM_INSTITUTION_ID, name = API.PARAM_INSTITUTION_ID,
required = true, required = true,
@ -363,8 +391,6 @@ public abstract class EntityController<T extends GrantEntity, M extends GrantEnt
protected abstract M createNew(POSTMapper postParams); protected abstract M createNew(POSTMapper postParams);
protected abstract Class<M> modifiedDataType();
protected abstract SqlTable getSQLTableOfEntity(); protected abstract SqlTable getSQLTableOfEntity();
} }

View file

@ -87,11 +87,6 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
this.lmsAPIService = lmsAPIService; this.lmsAPIService = lmsAPIService;
} }
@Override
protected Class<Exam> modifiedDataType() {
return Exam.class;
}
@Override @Override
protected SqlTable getSQLTableOfEntity() { protected SqlTable getSQLTableOfEntity() {
return ExamRecordDynamicSqlSupport.examRecord; return ExamRecordDynamicSqlSupport.examRecord;
@ -102,7 +97,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@Override @Override
public Page<Exam> getAll( public Page<Exam> getPage(
@RequestParam( @RequestParam(
name = API.PARAM_INSTITUTION_ID, name = API.PARAM_INSTITUTION_ID,
required = true, required = true,
@ -120,7 +115,7 @@ public class ExamAdministrationController extends ActivatableEntityController<Ex
if (StringUtils.isBlank(sort) || if (StringUtils.isBlank(sort) ||
this.paginationService.isNativeSortingSupported(ExamRecordDynamicSqlSupport.examRecord, sort)) { this.paginationService.isNativeSortingSupported(ExamRecordDynamicSqlSupport.examRecord, sort)) {
return super.getAll(institutionId, pageNumber, pageSize, sort, allRequestParams); return super.getPage(institutionId, pageNumber, pageSize, sort, allRequestParams);
} else { } else {

View file

@ -65,11 +65,6 @@ public class InstitutionController extends ActivatableEntityController<Instituti
this.sebClientConfigService = sebClientConfigService; this.sebClientConfigService = sebClientConfigService;
} }
@Override
protected Class<Institution> modifiedDataType() {
return Institution.class;
}
@Override @Override
protected SqlTable getSQLTableOfEntity() { protected SqlTable getSQLTableOfEntity() {
return InstitutionRecordDynamicSqlSupport.institutionRecord; return InstitutionRecordDynamicSqlSupport.institutionRecord;

View file

@ -58,11 +58,6 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
this.lmsAPIService = lmsAPIService; this.lmsAPIService = lmsAPIService;
} }
@Override
protected Class<LmsSetup> modifiedDataType() {
return LmsSetup.class;
}
@Override @Override
protected SqlTable getSQLTableOfEntity() { protected SqlTable getSQLTableOfEntity() {
return LmsSetupRecordDynamicSqlSupport.lmsSetupRecord; return LmsSetupRecordDynamicSqlSupport.lmsSetupRecord;

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; package ch.ethz.seb.sebserver.webservice.weblayer.api;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
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;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
@ -29,7 +30,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
@WebServiceProfile @WebServiceProfile
@RestController @RestController
@RequestMapping("/${sebserver.webservice.api.admin.endpoint}" + API.QUIZ_IMPORT_ENDPOINT) @RequestMapping("/${sebserver.webservice.api.admin.endpoint}" + API.QUIZ_DISCOVERY_ENDPOINT)
public class QuizImportController { public class QuizImportController {
private final int defaultPageSize; private final int defaultPageSize;
@ -50,8 +51,11 @@ public class QuizImportController {
this.authorization = authorization; this.authorization = authorization;
} }
@RequestMapping(method = RequestMethod.GET) @RequestMapping(
public Page<QuizData> search( method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Page<QuizData> getQuizPage(
@RequestParam( @RequestParam(
name = Entity.FILTER_ATTR_INSTITUTION, name = Entity.FILTER_ATTR_INSTITUTION,
required = true, required = true,

View file

@ -90,11 +90,6 @@ public class UserAccountController extends ActivatableEntityController<UserInfo,
.getUserInfo(); .getUserInfo();
} }
@Override
protected Class<UserMod> modifiedDataType() {
return UserMod.class;
}
@Override @Override
protected SqlTable getSQLTableOfEntity() { protected SqlTable getSQLTableOfEntity() {
return UserRecordDynamicSqlSupport.userRecord; return UserRecordDynamicSqlSupport.userRecord;

View file

@ -25,7 +25,7 @@ import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.institution.Institution; import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; 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.RestServiceImpl;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.OAuth2AuthorizationContextHolder; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.OAuth2AuthorizationContextHolder;
public class RestServiceTest extends GuiIntegrationTest { public class RestServiceTest extends GuiIntegrationTest {
@ -36,7 +36,7 @@ public class RestServiceTest extends GuiIntegrationTest {
final Collection<RestCall<?>> calls = new ArrayList<>(); final Collection<RestCall<?>> calls = new ArrayList<>();
calls.add(new RestServiceTest.GetInstitution()); calls.add(new RestServiceTest.GetInstitution());
final RestService restService = new RestService(authorizationContextHolder, new JSONMapper(), calls); final RestServiceImpl restService = new RestServiceImpl(authorizationContextHolder, new JSONMapper(), calls);
final Result<Institution> call = restService.getBuilder(RestServiceTest.GetInstitution.class) final Result<Institution> call = restService.getBuilder(RestServiceTest.GetInstitution.class)
.withURIVariable(API.PARAM_MODEL_ID, "2") .withURIVariable(API.PARAM_MODEL_ID, "2")