From d40449847548b45b9d3881ec94079279a185ec89 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 14 Mar 2019 16:56:34 +0100 Subject: [PATCH] SEBSERV-30 preparing for Exam and Quiz gui and javadoc --- .../ch/ethz/seb/sebserver/gbl/api/API.java | 2 +- .../sebserver/gbl/model/exam/QuizData.java | 18 ++- .../seb/sebserver/gui/content/ExamForm.java | 42 +++++ .../seb/sebserver/gui/content/ExamList.java | 46 ++++++ .../sebserver/gui/content/LmsSetupForm.java | 13 +- .../gui/content/QuizDiscoveryList.java | 46 ++++++ .../gui/content/UserAccountForm.java | 12 +- .../ch/ethz/seb/sebserver/gui/form/Form.java | 14 +- .../gui/service/ResourceService.java | 30 ++++ .../gui/service/push/ServerPushService.java | 4 + .../remote/webservice/api/RestCall.java | 36 +++-- .../remote/webservice/api/RestService.java | 152 +++++++----------- .../webservice/api/RestServiceImpl.java | 136 ++++++++++++++++ .../webservice/api/exam/ActivateExam.java | 40 +++++ .../webservice/api/exam/DeactivateExam.java | 40 +++++ .../remote/webservice/api/exam/GetExam.java | 40 +++++ .../api/exam/GetExamDependencies.java | 43 +++++ .../webservice/api/exam/GetExamNames.java | 42 +++++ .../remote/webservice/api/exam/GetExams.java | 41 +++++ .../remote/webservice/api/exam/SaveExam.java | 39 +++++ .../api/lmssetup/GetLmsSetupNames.java | 42 +++++ .../webservice/api/quiz/GetQuizzes.java | 41 +++++ .../webservice/api/quiz/ImportAsExam.java | 13 ++ .../auth/AuthorizationContextHolder.java | 17 +- .../servicelayer/lms/LmsAPITemplate.java | 13 +- .../lms/impl/MockupLmsAPITemplate.java | 26 +-- .../lms/impl/OpenEdxLmsAPITemplate.java | 8 +- .../api/ActivatableEntityController.java | 5 + .../weblayer/api/EntityController.java | 34 +++- .../api/ExamAdministrationController.java | 9 +- .../weblayer/api/InstitutionController.java | 5 - .../weblayer/api/LmsSetupController.java | 5 - .../weblayer/api/QuizImportController.java | 10 +- .../weblayer/api/UserAccountController.java | 5 - .../gui/integration/RestServiceTest.java | 4 +- 35 files changed, 877 insertions(+), 196 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/QuizDiscoveryList.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestServiceImpl.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/ActivateExam.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/DeactivateExam.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExam.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamDependencies.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamNames.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExams.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveExam.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/GetLmsSetupNames.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/quiz/GetQuizzes.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/quiz/ImportAsExam.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 2cb089e8..382b41a5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -48,7 +48,7 @@ public final class API { 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"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/QuizData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/QuizData.java index cecd7f64..7b9af527 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/QuizData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/QuizData.java @@ -27,6 +27,7 @@ public final class QuizData implements Entity { 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_LMS_SETUP_ID = "lms_setup_id"; public static final String QUIZ_ATTR_NAME = "quiz_name"; public static final String QUIZ_ATTR_DESCRIPTION = "quiz_description"; 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) public final String id; + @JsonProperty(QUIZ_ATTR_LMS_SETUP_ID) + public final String lmsSetupId; + @JsonProperty(QUIZ_ATTR_NAME) public final String name; @@ -54,6 +58,7 @@ public final class QuizData implements Entity { @JsonCreator public QuizData( @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_DESCRIPTION) final String description, @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) { this.id = id; + this.lmsSetupId = lmsSetupId; this.name = name; this.description = description; this.startTime = startTime; @@ -70,6 +76,7 @@ public final class QuizData implements Entity { public QuizData( final String id, + final String lmsSetupId, final String name, final String description, final String startTime, @@ -77,6 +84,7 @@ public final class QuizData implements Entity { final String startURL) { this.id = id; + this.lmsSetupId = lmsSetupId; this.name = name; this.description = description; this.startTime = LocalDateTime @@ -106,6 +114,10 @@ public final class QuizData implements Entity { return this.id; } + public String getLmsSetupId() { + return this.lmsSetupId; + } + @Override public String getName() { return this.name; @@ -154,9 +166,9 @@ public final class QuizData implements Entity { @Override public String toString() { - return "QuizData [id=" + this.id + ", name=" + this.name + ", description=" + this.description + ", startTime=" - + this.startTime - + ", endTime=" + this.endTime + ", startURL=" + this.startURL + "]"; + return "QuizData [id=" + this.id + ", lmsSetupId=" + this.lmsSetupId + ", name=" + this.name + ", description=" + + this.description + + ", startTime=" + this.startTime + ", endTime=" + this.endTime + ", startURL=" + this.startURL + "]"; } public static Comparator getIdComparator(final boolean descending) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java new file mode 100644 index 00000000..b9397f5e --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java @@ -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 + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java new file mode 100644 index 00000000..8a34c18b --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java @@ -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 + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java index 875f5a49..ac1e1b22 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupForm.java @@ -114,7 +114,7 @@ public class LmsSetupForm implements TemplateComposer { final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(lmsSetup); final boolean writeGrant = userGrantCheck.w(); 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())) .call() .map(inst -> inst.active) @@ -177,27 +177,27 @@ public class LmsSetupForm implements TemplateComposer { formContext.clearEntityKeys() .createAction(ActionDefinition.LMS_SETUP_NEW) - .publishIf(() -> writeGrant && readonly && istitutionActive) + .publishIf(() -> writeGrant && readonly && institutionActive) .createAction(ActionDefinition.LMS_SETUP_MODIFY) .withEntityKey(entityKey) - .publishIf(() -> modifyGrant && readonly && istitutionActive) + .publishIf(() -> modifyGrant && readonly && institutionActive) .createAction(ActionDefinition.LMS_SETUP_TEST) .withEntityKey(entityKey) .withExec(action -> this.testLmsSetup(action, formHandle)) - .publishIf(() -> modifyGrant && isNotNew.getAsBoolean() && istitutionActive) + .publishIf(() -> modifyGrant && isNotNew.getAsBoolean() && institutionActive) .createAction(ActionDefinition.LMS_SETUP_DEACTIVATE) .withEntityKey(entityKey) .withExec(restService::activation) .withConfirm(PageUtils.confirmDeactivation(lmsSetup, restService)) - .publishIf(() -> writeGrant && readonly && istitutionActive && lmsSetup.isActive()) + .publishIf(() -> writeGrant && readonly && institutionActive && lmsSetup.isActive()) .createAction(ActionDefinition.LMS_SETUP_ACTIVATE) .withEntityKey(entityKey) .withExec(restService::activation) - .publishIf(() -> writeGrant && readonly && istitutionActive && !lmsSetup.isActive()) + .publishIf(() -> writeGrant && readonly && institutionActive && !lmsSetup.isActive()) .createAction(ActionDefinition.LMS_SETUP_SAVE) .withExec(formHandle::processFormSave) @@ -208,7 +208,6 @@ public class LmsSetupForm implements TemplateComposer { .withExec(Action::onEmptyEntityKeyGoToActivityHome) .withConfirm("sebserver.overall.action.modify.cancel.confirm") .publishIf(() -> !readonly); - } /** LmsSetup test action implementation */ diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizDiscoveryList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizDiscoveryList.java new file mode 100644 index 00000000..d2b03668 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizDiscoveryList.java @@ -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 + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java index 939c0acd..3f92517c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountForm.java @@ -106,7 +106,7 @@ public class UserAccountForm implements TemplateComposer { final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(userAccount); final boolean writeGrant = userGrantCheck.w(); 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())) .call() .map(inst -> inst.active) @@ -190,26 +190,26 @@ public class UserAccountForm implements TemplateComposer { formContext.clearEntityKeys() .createAction(ActionDefinition.USER_ACCOUNT_NEW) - .publishIf(() -> writeGrant && readonly && istitutionActive) + .publishIf(() -> writeGrant && readonly && institutionActive) .createAction(ActionDefinition.USER_ACCOUNT_MODIFY) .withEntityKey(entityKey) - .publishIf(() -> modifyGrant && readonly && istitutionActive) + .publishIf(() -> modifyGrant && readonly && institutionActive) .createAction(ActionDefinition.USER_ACCOUNT_CHANGE_PASSOWRD) .withEntityKey(entityKey) - .publishIf(() -> modifyGrant && readonly && istitutionActive && userAccount.isActive()) + .publishIf(() -> modifyGrant && readonly && institutionActive && userAccount.isActive()) .createAction(ActionDefinition.USER_ACCOUNT_DEACTIVATE) .withEntityKey(entityKey) .withExec(restService::activation) .withConfirm(PageUtils.confirmDeactivation(userAccount, restService)) - .publishIf(() -> writeGrant && readonly && istitutionActive && userAccount.isActive()) + .publishIf(() -> writeGrant && readonly && institutionActive && userAccount.isActive()) .createAction(ActionDefinition.USER_ACCOUNT_ACTIVATE) .withEntityKey(entityKey) .withExec(restService::activation) - .publishIf(() -> writeGrant && readonly && istitutionActive && !userAccount.isActive()) + .publishIf(() -> writeGrant && readonly && institutionActive && !userAccount.isActive()) .createAction(ActionDefinition.USER_ACCOUNT_SAVE) .withExec(action -> { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java index ff3307b7..d59e31e8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java @@ -216,6 +216,7 @@ public final class Form implements FormBinding { } } + // following are FormFieldAccessor implementations for all field types //@formatter:off private FormFieldAccessor createAccessor(final Label label, final 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); } }; } - private FormFieldAccessor createAccessor( - final Label label, - final SingleSelection singleSelection) { - + private FormFieldAccessor createAccessor(final Label label, final SingleSelection singleSelection) { return new FormFieldAccessor(label, singleSelection) { @Override public String getValue() { return singleSelection.getSelectionValue(); } @Override public void setValue(final String value) { singleSelection.select(value); } }; } - private FormFieldAccessor createAccessor( - final Label label, - final MultiSelection multiSelection) { - + private FormFieldAccessor createAccessor(final Label label,final MultiSelection multiSelection) { return new FormFieldAccessor(label, multiSelection) { @Override public String getValue() { return multiSelection.getSelectionValue(); } @Override public void setValue(final String value) { multiSelection.select(value); } - @Override - public void putJsonValue(final String key, final ObjectNode objectRoot) { + @Override public void putJsonValue(final String key, final ObjectNode objectRoot) { final String value = getValue(); if (StringUtils.isNoneBlank(value)) { final ArrayNode arrayNode = objectRoot.putArray(key); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java index 0f6a67d2..4edd080a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java @@ -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.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.lmssetup.GetLmsSetupNames; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; @Lazy @@ -115,6 +116,35 @@ public class ResourceService { }; } + public List> 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 getLmsSetupNameFunction(final Long institutionId) { + + final Map 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 * language of the current users locale. * diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushService.java index f9892627..20bd5661 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushService.java @@ -16,6 +16,10 @@ import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; 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 @Service public class ServerPushService { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java index 3ce5624a..2724dbe2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java @@ -8,6 +8,7 @@ package ch.ethz.seb.sebserver.gui.service.remote.webservice.api; +import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -25,8 +26,10 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestClientResponseException; +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; 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.EntityType; @@ -100,20 +103,7 @@ public abstract class RestCall { RestCall.this.typeKey.typeRef)); } else { - - final RestCallError restCallError = - new RestCallError("Response Entity: " + responseEntity.toString()); - - restCallError.errors.addAll(RestCall.this.jsonMapper.readValue( - responseEntity.getBody(), - new TypeReference>() { - })); - - log.debug( - "Webservice answered with well defined error- or validation-failure-response: ", - restCallError); - - return Result.ofError(restCallError); + return handleRestCallError(responseEntity); } } catch (final Throwable t) { @@ -149,6 +139,24 @@ public abstract class RestCall { return new RestCallBuilder(); } + private Result handleRestCallError(final ResponseEntity 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>() { + })); + + log.debug( + "Webservice answered with well defined error- or validation-failure-response: ", + restCallError); + + return Result.ofError(restCallError); + } + public class RestCallBuilder { private final HttpHeaders httpHeaders = new HttpHeaders(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestService.java index 7dbf14de..9ccaa26f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestService.java @@ -8,113 +8,75 @@ 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 RestService { +/** Interface to SEB Server webservice API thought RestCall's + * or thought Spring's RestTemplate on lower level. + * + * 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: + * + *
+ *  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
+ * 
+ */ +public interface RestService { - private final AuthorizationContextHolder authorizationContextHolder; - private final WebserviceURIService webserviceURIBuilderSupplier; - private final Map> calls; + /** Get Spring's RestTemplate that is used within this service. + * + * @return Spring's RestTemplate that is used within this service. */ + RestTemplate getWebserviceAPIRestTemplate(); - public RestService( - final AuthorizationContextHolder authorizationContextHolder, - final JSONMapper jsonMapper, - final Collection> calls) { + /** Get Spring's UriComponentsBuilder that is used within this service. + * + * @return Spring's UriComponentsBuilder that is used within this service. */ + UriComponentsBuilder getWebserviceURIBuilder(); - this.authorizationContextHolder = authorizationContextHolder; - this.webserviceURIBuilderSupplier = authorizationContextHolder - .getWebserviceURIService(); + /** Get a certain RestCall by Class type. + * + * @param type the Class type of the RestCall + * @return RestCall instance */ + RestCall getRestCall(Class> type); - this.calls = calls - .stream() - .collect(Collectors.toMap( - call -> call.getClass().getName(), - call -> call.init(this, jsonMapper))); - } + /** Get a certain RestCall by EntityType and CallType. + * NOTE not all RestCall can be get within this method. Only the ones that have a defined CallType + * + * @param entityType The EntityType of the RestCall + * @param callType The CallType of the RestCall (not UNDEFINDED) + * @return RestCall instance */ + RestCall getRestCall(EntityType entityType, CallType callType); - public final RestTemplate getWebserviceAPIRestTemplate() { - return this.authorizationContextHolder - .getAuthorizationContext() - .getRestTemplate(); - } + /** Get a certain RestCallBuilder by RestCall Class type. + * + * @param type the Class type of the RestCall + * @return RestCallBuilder instance to build a dedicated call and execute it */ + RestCall.RestCallBuilder getBuilder(Class> type); - public final UriComponentsBuilder getWebserviceURIBuilder() { - return this.webserviceURIBuilderSupplier.getURIBuilder(); - } + /** Get a certain RestCallBuilder by EntityType and CallType. + * + * @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 */ + RestCall.RestCallBuilder getBuilder( + EntityType entityType, + CallType callType); - @SuppressWarnings("unchecked") - public final RestCall getRestCall(final Class> type) { - return (RestCall) this.calls.get(type.getName()); - } + /** Performs an activation Action on RestCall specified within the given Action. + * The RestCall must be of CallType.ACTIVATION_ACTIVATE or CallType.ACTIVATION_DEACTIVATE + * + * @param action the Action that defines an entity activation + * @return the successfully executed Action */ + Action activation(Action action); - @SuppressWarnings("unchecked") - public final RestCall getRestCall(final EntityType entityType, final CallType callType) { - return (RestCall) this.calls.values() - .stream() - .filter(call -> call.typeKey.callType == callType && call.typeKey.entityType == entityType) - .findFirst() - .orElse(null); - } - - public final RestCall.RestCallBuilder getBuilder(final Class> type) { - @SuppressWarnings("unchecked") - final RestCall restCall = (RestCall) this.calls.get(type.getName()); - if (restCall == null) { - return null; - } - - return restCall.newBuilder(); - } - - public final RestCall.RestCallBuilder getBuilder( - final EntityType entityType, - final CallType callType) { - - final RestCall restCall = getRestCall(entityType, callType); - if (restCall == null) { - return null; - } - - return restCall.newBuilder(); - } - - public 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> restCallType = - (Class>) 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; - } - -} +} \ No newline at end of file diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestServiceImpl.java new file mode 100644 index 00000000..4bb6d678 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestServiceImpl.java @@ -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> calls; + + public RestServiceImpl( + final AuthorizationContextHolder authorizationContextHolder, + final JSONMapper jsonMapper, + final Collection> 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 RestCall getRestCall(final Class> type) { + return (RestCall) this.calls.get(type.getName()); + } + + @Override + @SuppressWarnings("unchecked") + public final RestCall getRestCall(final EntityType entityType, final CallType callType) { + + if (callType == CallType.UNDEFINED) { + throw new IllegalArgumentException("Undefined CallType not supported"); + } + + return (RestCall) this.calls.values() + .stream() + .filter(call -> call.typeKey.callType == callType && call.typeKey.entityType == entityType) + .findFirst() + .orElse(null); + } + + @Override + public final RestCall.RestCallBuilder getBuilder(final Class> type) { + @SuppressWarnings("unchecked") + final RestCall restCall = (RestCall) this.calls.get(type.getName()); + if (restCall == null) { + return null; + } + + return restCall.newBuilder(); + } + + @Override + public final RestCall.RestCallBuilder getBuilder( + final EntityType entityType, + final CallType callType) { + + if (callType == CallType.UNDEFINED) { + throw new IllegalArgumentException("Undefined CallType not supported"); + } + + final RestCall restCall = getRestCall(entityType, callType); + if (restCall == null) { + return null; + } + + return restCall.newBuilder(); + } + + @Override + public 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> restCallType = + (Class>) 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; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/ActivateExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/ActivateExam.java new file mode 100644 index 00000000..e5bcb2e4 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/ActivateExam.java @@ -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 { + + protected ActivateExam() { + super(new TypeKey<>( + CallType.ACTIVATION_ACTIVATE, + EntityType.EXAM, + new TypeReference() { + }), + HttpMethod.POST, + MediaType.APPLICATION_FORM_URLENCODED, + API.EXAM_ADMINISTRATION_ENDPOINT + API.PATH_VAR_ACTIVE); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/DeactivateExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/DeactivateExam.java new file mode 100644 index 00000000..aa5fc9c7 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/DeactivateExam.java @@ -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 { + + protected DeactivateExam() { + super(new TypeKey<>( + CallType.ACTIVATION_DEACTIVATE, + EntityType.EXAM, + new TypeReference() { + }), + HttpMethod.POST, + MediaType.APPLICATION_FORM_URLENCODED, + API.EXAM_ADMINISTRATION_ENDPOINT + API.PATH_VAR_INACTIVE); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExam.java new file mode 100644 index 00000000..234d0efa --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExam.java @@ -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 { + + protected GetExam() { + super(new TypeKey<>( + CallType.GET_SINGLE, + EntityType.EXAM, + new TypeReference() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.EXAM_ADMINISTRATION_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamDependencies.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamDependencies.java new file mode 100644 index 00000000..fab35842 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamDependencies.java @@ -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> { + + protected GetExamDependencies() { + super(new TypeKey<>( + CallType.GET_DEPENDENCIES, + EntityType.EXAM, + new TypeReference>() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.EXAM_ADMINISTRATION_ENDPOINT + + API.MODEL_ID_VAR_PATH_SEGMENT + + API.DEPENDENCY_PATH_SEGMENT); + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamNames.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamNames.java new file mode 100644 index 00000000..2f120342 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExamNames.java @@ -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> { + + protected GetExamNames() { + super(new TypeKey<>( + CallType.GET_NAMES, + EntityType.EXAM, + new TypeReference>() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.EXAM_ADMINISTRATION_ENDPOINT + API.NAMES_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExams.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExams.java new file mode 100644 index 00000000..cef36611 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/GetExams.java @@ -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> { + + protected GetExams() { + super(new TypeKey<>( + CallType.GET_PAGE, + EntityType.EXAM, + new TypeReference>() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.EXAM_ADMINISTRATION_ENDPOINT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveExam.java new file mode 100644 index 00000000..f0653542 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveExam.java @@ -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 { + + protected SaveExam() { + super(new TypeKey<>( + CallType.SAVE, + EntityType.EXAM, + new TypeReference() { + }), + HttpMethod.PUT, + MediaType.APPLICATION_JSON_UTF8, + API.EXAM_ADMINISTRATION_ENDPOINT); + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/GetLmsSetupNames.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/GetLmsSetupNames.java new file mode 100644 index 00000000..ad8427af --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/lmssetup/GetLmsSetupNames.java @@ -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> { + + protected GetLmsSetupNames() { + super(new TypeKey<>( + CallType.GET_NAMES, + EntityType.LMS_SETUP, + new TypeReference>() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.LMS_SETUP_ENDPOINT + API.NAMES_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/quiz/GetQuizzes.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/quiz/GetQuizzes.java new file mode 100644 index 00000000..f9e4b373 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/quiz/GetQuizzes.java @@ -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> { + + protected GetQuizzes() { + super(new TypeKey<>( + CallType.GET_PAGE, + EntityType.EXAM, + new TypeReference>() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.QUIZ_DISCOVERY_ENDPOINT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/quiz/ImportAsExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/quiz/ImportAsExam.java new file mode 100644 index 00000000..ba3b457d --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/quiz/ImportAsExam.java @@ -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 { + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/AuthorizationContextHolder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/AuthorizationContextHolder.java index 39f43789..1b6d9121 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/AuthorizationContextHolder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/AuthorizationContextHolder.java @@ -12,13 +12,28 @@ import javax.servlet.http.HttpSession; import org.eclipse.rap.rwt.RWT; +/** Single point of access for SEBServerAuthorizationContext */ 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); + /** Get the WebserviceURIService that is used within this AuthorizationContextHolder + * + * @return the WebserviceURIService that is used within this AuthorizationContextHolder */ 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() { return getAuthorizationContext(RWT.getUISession().getHttpSession()); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java index 2e2119b3..ab9d2df1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/LmsAPITemplate.java @@ -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.institution.LmsSetup; 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.webservice.servicelayer.client.ClientCredentials; 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 * Later a concrete LmsAPITemplate may also implement some special features regarding to the type - * of LMS */ + * of the LMS */ public interface LmsAPITemplate { /** Get the underling LMSSetup configuration for this LmsAPITemplate @@ -54,9 +53,17 @@ public interface LmsAPITemplate { * or refer to an error when happened */ Result> 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> getQuizzes(Set ids); - Result 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 getExamineeAccountDetails(String examineeUserId); default List attributeValidation(final ClientCredentials credentials) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java index 7a14268d..8ef0dc04 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/MockupLmsAPITemplate.java @@ -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.institution.LmsSetup; 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.webservice.servicelayer.client.ClientCredentialService; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials; @@ -50,27 +49,28 @@ final class MockupLmsAPITemplate implements LmsAPITemplate { this.clientCredentialService = clientCredentialService; this.credentials = credentials; + final String lmsSetupId = lmsSetup.getModelId(); this.mockups = new ArrayList<>(); 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/")); 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/")); 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/")); 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/")); 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/")); 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/")); 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/")); } @@ -124,16 +124,6 @@ final class MockupLmsAPITemplate implements LmsAPITemplate { .collect(Collectors.toList()); } - @Override - public Result 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() { try { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxLmsAPITemplate.java index 7e51f556..ec83675c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/OpenEdxLmsAPITemplate.java @@ -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.institution.LmsSetup; 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.SupplierWithCircuitBreaker; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService; @@ -136,12 +135,6 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate { return null; } - @Override - public Result getExamineeAccountDetails(final String examineeUserId) { - // TODO Auto-generated method stub - return null; - } - private Result initRestTemplateAndRequestAccessToken() { 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; return new QuizData( courseData.id, + lmsSetup.getModelId(), courseData.name, courseData.short_description, courseData.start, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java index 16805146..defc6307 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ActivatableEntityController.java @@ -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.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 The concrete Entity domain-model type used on all GET, PUT + * @param The concrete Entity domain-model type used for POST methods (new) */ public abstract class ActivatableEntityController extends EntityController { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java index a62eafac..eebff1d2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java @@ -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.validation.BeanValidationService; +/** Abstract Entity-Controller that defines generic Entity rest API endpoints that are supported + * by all entity types. + * + * @param The concrete Entity domain-model type used on all GET, PUT + * @param The concrete Entity domain-model type used for POST methods (new) */ public abstract class EntityController { protected final AuthorizationService authorization; @@ -75,6 +80,11 @@ public abstract class EntityController getAll( + public Page getPage( @RequestParam( name = API.PARAM_INSTITUTION_ID, required = true, @@ -363,8 +391,6 @@ public abstract class EntityController modifiedDataType(); - protected abstract SqlTable getSQLTableOfEntity(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java index 45e6f833..7eb175b4 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java @@ -87,11 +87,6 @@ public class ExamAdministrationController extends ActivatableEntityController modifiedDataType() { - return Exam.class; - } - @Override protected SqlTable getSQLTableOfEntity() { return ExamRecordDynamicSqlSupport.examRecord; @@ -102,7 +97,7 @@ public class ExamAdministrationController extends ActivatableEntityController getAll( + public Page getPage( @RequestParam( name = API.PARAM_INSTITUTION_ID, required = true, @@ -120,7 +115,7 @@ public class ExamAdministrationController extends ActivatableEntityController modifiedDataType() { - return Institution.class; - } - @Override protected SqlTable getSQLTableOfEntity() { return InstitutionRecordDynamicSqlSupport.institutionRecord; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsSetupController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsSetupController.java index cda9ed11..dee8a346 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsSetupController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/LmsSetupController.java @@ -58,11 +58,6 @@ public class LmsSetupController extends ActivatableEntityController modifiedDataType() { - return LmsSetup.class; - } - @Override protected SqlTable getSQLTableOfEntity() { return LmsSetupRecordDynamicSqlSupport.lmsSetupRecord; diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/QuizImportController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/QuizImportController.java index 8d3134a5..399e96ef 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/QuizImportController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/QuizImportController.java @@ -9,6 +9,7 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -29,7 +30,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; @WebServiceProfile @RestController -@RequestMapping("/${sebserver.webservice.api.admin.endpoint}" + API.QUIZ_IMPORT_ENDPOINT) +@RequestMapping("/${sebserver.webservice.api.admin.endpoint}" + API.QUIZ_DISCOVERY_ENDPOINT) public class QuizImportController { private final int defaultPageSize; @@ -50,8 +51,11 @@ public class QuizImportController { this.authorization = authorization; } - @RequestMapping(method = RequestMethod.GET) - public Page search( + @RequestMapping( + method = RequestMethod.GET, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public Page getQuizPage( @RequestParam( name = Entity.FILTER_ATTR_INSTITUTION, required = true, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java index dabda0bd..0eb4bc9c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/UserAccountController.java @@ -90,11 +90,6 @@ public class UserAccountController extends ActivatableEntityController modifiedDataType() { - return UserMod.class; - } - @Override protected SqlTable getSQLTableOfEntity() { return UserRecordDynamicSqlSupport.userRecord; diff --git a/src/test/java/ch/ethz/seb/sebserver/gui/integration/RestServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/gui/integration/RestServiceTest.java index 72a6ca8e..6eb1078a 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gui/integration/RestServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/gui/integration/RestServiceTest.java @@ -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.util.Result; 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; public class RestServiceTest extends GuiIntegrationTest { @@ -36,7 +36,7 @@ public class RestServiceTest extends GuiIntegrationTest { final Collection> calls = new ArrayList<>(); 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 call = restService.getBuilder(RestServiceTest.GetInstitution.class) .withURIVariable(API.PARAM_MODEL_ID, "2")