diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/APIMessage.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/APIMessage.java index be0762e8..c38ff7ff 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/APIMessage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/APIMessage.java @@ -30,6 +30,7 @@ public class APIMessage implements Serializable { GENERIC("0", HttpStatus.INTERNAL_SERVER_ERROR, "Generic error message"), UNAUTHORIZED("1000", HttpStatus.UNAUTHORIZED, "UNAUTHORIZED"), FORBIDDEN("1001", HttpStatus.FORBIDDEN, "FORBIDDEN"), + RESOURCE_NOT_FOUND("1002", HttpStatus.NOT_FOUND, "resource not found"), ILLEGAL_API_ARGUMENT("1010", HttpStatus.BAD_REQUEST, "Illegal API request argument"), UNEXPECTED("1100", HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected intenral server-side error"), FIELD_VALIDATION("1200", HttpStatus.BAD_REQUEST, "Field validation error"), diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityProcessingReport.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityProcessingReport.java index a7569f25..b0bc079f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityProcessingReport.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/EntityProcessingReport.java @@ -52,4 +52,11 @@ public class EntityProcessingReport { } + @Override + public String toString() { + return "EntityProcessingReport [source=" + this.source + ", dependencies=" + this.dependencies + ", errors=" + + this.errors + + "]"; + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ResourceNotFoundException.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ResourceNotFoundException.java index 637a204c..455319dc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ResourceNotFoundException.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ResourceNotFoundException.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityType; @ResponseStatus(HttpStatus.NOT_FOUND) @@ -18,19 +19,16 @@ public final class ResourceNotFoundException extends RuntimeException { private static final long serialVersionUID = 8319235723086949618L; - public final EntityType entityType; - public final String entityId; + public final EntityKey entityKey; - public ResourceNotFoundException(final EntityType entityType, final String entityId) { - super("Resource " + entityType + " with ID: " + entityId + " not found"); - this.entityType = entityType; - this.entityId = entityId; + public ResourceNotFoundException(final EntityType entityType, final String modelId) { + super("Resource " + entityType + " with ID: " + modelId + " not found"); + this.entityKey = new EntityKey(modelId, entityType); } - public ResourceNotFoundException(final EntityType entityType, final String entityId, final Throwable cause) { - super("Resource " + entityType + " with ID: " + entityId + " not found", cause); - this.entityType = entityType; - this.entityId = entityId; + public ResourceNotFoundException(final EntityType entityType, final String modelId, final Throwable cause) { + super("Resource " + entityType + " with ID: " + modelId + " not found", cause); + this.entityKey = new EntityKey(modelId, entityType); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java index fb03ed72..21a0b9b0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ExamDAOImpl.java @@ -159,8 +159,10 @@ public class ExamDAOImpl implements ExamDAO { @Transactional public Result save(final String modelId, final Exam exam) { return Result.tryCatch(() -> { + + final Long pk = Long.parseLong(modelId); final ExamRecord examRecord = new ExamRecord( - exam.id, + pk, null, null, null, null, (exam.supporter != null) ? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR) @@ -170,7 +172,7 @@ public class ExamDAOImpl implements ExamDAO { BooleanUtils.toIntegerObject(exam.active)); this.examRecordMapper.updateByPrimaryKeySelective(examRecord); - return this.examRecordMapper.selectByPrimaryKey(exam.id); + return this.examRecordMapper.selectByPrimaryKey(pk); }) .flatMap(this::toDomainModel) .onErrorDo(TransactionHandler::rollback); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/IndicatorDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/IndicatorDAOImpl.java index ae9f67b3..6978d549 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/IndicatorDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/IndicatorDAOImpl.java @@ -146,8 +146,9 @@ public class IndicatorDAOImpl implements IndicatorDAO { public Result save(final String modelId, final Indicator modified) { return Result.tryCatch(() -> { + final Long pk = Long.parseLong(modelId); final IndicatorRecord newRecord = new IndicatorRecord( - modified.id, + pk, null, modified.type.name(), modified.name, @@ -157,7 +158,7 @@ public class IndicatorDAOImpl implements IndicatorDAO { // update also the thresholds this.thresholdRecordMapper.deleteByExample() - .where(ThresholdRecordDynamicSqlSupport.indicatorId, isEqualTo(modified.id)) + .where(ThresholdRecordDynamicSqlSupport.indicatorId, isEqualTo(pk)) .build() .execute(); @@ -165,12 +166,12 @@ public class IndicatorDAOImpl implements IndicatorDAO { .stream() .map(threshold -> new ThresholdRecord( null, - modified.id, + pk, new BigDecimal(threshold.value), threshold.color)) .forEach(this.thresholdRecordMapper::insert); - return this.indicatorRecordMapper.selectByPrimaryKey(modified.id); + return this.indicatorRecordMapper.selectByPrimaryKey(pk); }) .flatMap(this::toDomainModel) .onErrorDo(TransactionHandler::rollback); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/InstitutionDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/InstitutionDAOImpl.java index e70a0d38..40c99672 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/InstitutionDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/InstitutionDAOImpl.java @@ -131,15 +131,16 @@ public class InstitutionDAOImpl implements InstitutionDAO { public Result save(final String modelId, final Institution institution) { return Result.tryCatch(() -> { + final Long pk = Long.parseLong(modelId); final InstitutionRecord newRecord = new InstitutionRecord( - institution.id, + pk, institution.name, institution.urlSuffix, null, institution.logoImage); this.institutionRecordMapper.updateByPrimaryKeySelective(newRecord); - return this.institutionRecordMapper.selectByPrimaryKey(institution.id); + return this.institutionRecordMapper.selectByPrimaryKey(pk); }) .flatMap(InstitutionDAOImpl::toDomainModel) .onErrorDo(TransactionHandler::rollback); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java index 0137e9fe..64bba81f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/LmsSetupDAOImpl.java @@ -124,8 +124,9 @@ public class LmsSetupDAOImpl implements LmsSetupDAO { public Result save(final String modelId, final LmsSetup lmsSetup) { return Result.tryCatch(() -> { + final Long pk = Long.parseLong(modelId); final LmsSetupRecord newRecord = new LmsSetupRecord( - lmsSetup.id, + pk, lmsSetup.institutionId, lmsSetup.name, (lmsSetup.lmsType != null) ? lmsSetup.lmsType.name() : null, @@ -138,7 +139,7 @@ public class LmsSetupDAOImpl implements LmsSetupDAO { null); this.lmsSetupRecordMapper.updateByPrimaryKeySelective(newRecord); - return this.lmsSetupRecordMapper.selectByPrimaryKey(lmsSetup.id); + return this.lmsSetupRecordMapper.selectByPrimaryKey(pk); }) .flatMap(LmsSetupDAOImpl::toDomainModel) .onErrorDo(TransactionHandler::rollback); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java index 69fd632f..96fcf64a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java @@ -29,6 +29,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep import ch.ethz.seb.sebserver.gbl.model.APIMessage; import ch.ethz.seb.sebserver.gbl.model.APIMessage.APIMessageException; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationException; @Order(Ordered.HIGHEST_PRECEDENCE) @@ -79,6 +80,15 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler { return new ResponseEntity<>(valErrors, HttpStatus.BAD_REQUEST); } + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleResourceNotFoundException( + final ResourceNotFoundException ex, + final WebRequest request) { + + return APIMessage.ErrorMessage.RESOURCE_NOT_FOUND + .createErrorResponse(ex.getMessage()); + } + @ExceptionHandler(UsernameNotFoundException.class) public ResponseEntity handleUserNotFound( final UsernameNotFoundException ex, 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 88d26bc4..9391673b 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 @@ -102,7 +102,7 @@ public abstract class ActivatableEntityController() { -// }); -// } + @Test + public void createActivateModifyDeactivateAndDeleteInstitution() throws Exception { + // create new institution with seb-admin + final String sebAdminAccess = getSebAdminAccess(); + Institution institution = new RestAPITestHelper() + .withAccessToken(sebAdminAccess) + .withPath(RestAPI.ENDPOINT_INSTITUTION) + .withMethod(HttpMethod.POST) + .withAttribute("name", "testInstitution") + .withExpectedStatus(HttpStatus.OK) + .getAsObject(new TypeReference() { + }); + + assertNotNull(institution); + assertEquals("testInstitution", institution.name); + assertFalse(institution.active); + + // get + institution = new RestAPITestHelper() + .withAccessToken(sebAdminAccess) + .withPath(RestAPI.ENDPOINT_INSTITUTION).withPath("/").withPath(String.valueOf(institution.id)) + .withMethod(HttpMethod.GET) + .withExpectedStatus(HttpStatus.OK) + .getAsObject(new TypeReference() { + }); + + assertNotNull(institution); + assertEquals("testInstitution", institution.name); + assertEquals(null, institution.urlSuffix); + assertFalse(institution.active); + + // modify + institution = new RestAPITestHelper() + .withAccessToken(sebAdminAccess) + .withPath(RestAPI.ENDPOINT_INSTITUTION).withPath("/").withPath(String.valueOf(institution.id)) + .withMethod(HttpMethod.PUT) + .withBodyJson(new Institution(null, "testInstitution", "testSuffix", null, null)) + .withExpectedStatus(HttpStatus.OK) + .getAsObject(new TypeReference() { + }); + + assertNotNull(institution); + assertEquals("testInstitution", institution.name); + assertEquals("testSuffix", institution.urlSuffix); + assertFalse(institution.active); + + // activate + EntityProcessingReport report = new RestAPITestHelper() + .withAccessToken(sebAdminAccess) + .withPath(RestAPI.ENDPOINT_INSTITUTION) + .withPath("/").withPath(String.valueOf(institution.id)).withPath("/active") + .withMethod(HttpMethod.POST) + .withExpectedStatus(HttpStatus.OK) + .getAsObject(new TypeReference() { + }); + + assertNotNull(report); + assertEquals("EntityProcessingReport " + + "[source=[EntityKey [modelId=4, entityType=INSTITUTION]], " + + "dependencies=[], " + + "errors=[]]", + report.toString()); + // get + institution = new RestAPITestHelper() + .withAccessToken(sebAdminAccess) + .withPath(RestAPI.ENDPOINT_INSTITUTION).withPath("/").withPath(String.valueOf(institution.id)) + .withMethod(HttpMethod.GET) + .withExpectedStatus(HttpStatus.OK) + .getAsObject(new TypeReference() { + }); + + assertNotNull(institution); + assertTrue(institution.active); + + // deactivate + report = new RestAPITestHelper() + .withAccessToken(sebAdminAccess) + .withPath(RestAPI.ENDPOINT_INSTITUTION) + .withPath("/").withPath(String.valueOf(institution.id)).withPath("/inactive") + .withMethod(HttpMethod.POST) + .withExpectedStatus(HttpStatus.OK) + .getAsObject(new TypeReference() { + }); + + assertNotNull(report); + assertEquals("EntityProcessingReport " + + "[source=[EntityKey [modelId=4, entityType=INSTITUTION]], " + + "dependencies=[], " + + "errors=[]]", + report.toString()); + // get + institution = new RestAPITestHelper() + .withAccessToken(sebAdminAccess) + .withPath(RestAPI.ENDPOINT_INSTITUTION).withPath("/").withPath(String.valueOf(institution.id)) + .withMethod(HttpMethod.GET) + .withExpectedStatus(HttpStatus.OK) + .getAsObject(new TypeReference() { + }); + + assertNotNull(institution); + assertFalse(institution.active); + + // delete + report = new RestAPITestHelper() + .withAccessToken(sebAdminAccess) + .withPath(RestAPI.ENDPOINT_INSTITUTION) + .withPath("/").withPath(String.valueOf(institution.id)) + .withMethod(HttpMethod.DELETE) + .withExpectedStatus(HttpStatus.OK) + .getAsObject(new TypeReference() { + }); + + assertNotNull(report); + assertEquals("EntityProcessingReport " + + "[source=[EntityKey [modelId=4, entityType=INSTITUTION]], " + + "dependencies=[], " + + "errors=[]]", + report.toString()); + + // get + final APIMessage error = new RestAPITestHelper() + .withAccessToken(sebAdminAccess) + .withPath(RestAPI.ENDPOINT_INSTITUTION).withPath("/").withPath(String.valueOf(institution.id)) + .withMethod(HttpMethod.GET) + .withExpectedStatus(HttpStatus.NOT_FOUND) + .getAsObject(new TypeReference() { + }); + + assertNotNull(error); + assertEquals("Resource INSTITUTION with ID: 4 not found", error.details); + } static void assertContainsInstitution(final String name, final Collection institutions) { assert institutions != null; diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java index a8d6d34f..2e764e4f 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/UserAPITest.java @@ -843,7 +843,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { final String timeNow = DateTime.now(DateTimeZone.UTC).toString(Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS); // only a SEB Administrator or an Institutional administrator should be able to deactivate a user-account final String examAdminToken = getExamAdmin1(); - this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user4/deactivate") + this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user4/inactive") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .header("Authorization", "Bearer " + examAdminToken)) .andExpect(status().isForbidden()); @@ -851,7 +851,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { // With SEB Administrator it should work final String sebAdminToken = getSebAdminAccess(); final EntityProcessingReport report = this.jsonMapper.readValue( - this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user4/deactivate") + this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user4/inactive") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .header("Authorization", "Bearer " + sebAdminToken)) .andExpect(status().isOk()) @@ -903,7 +903,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { final String timeNow = DateTime.now(DateTimeZone.UTC).toString(Constants.DATE_TIME_PATTERN_UTC_NO_MILLIS); // only a SEB Administrator or an Institutional administrator should be able to deactivate a user-account final String examAdminToken = getExamAdmin1(); - this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user6/activate") + this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user6/active") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .header("Authorization", "Bearer " + examAdminToken)) .andExpect(status().isForbidden()); @@ -911,7 +911,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester { // With SEB Administrator it should work final String sebAdminToken = getSebAdminAccess(); final EntityProcessingReport report = this.jsonMapper.readValue( - this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user6/activate") + this.mockMvc.perform(post(this.endpoint + RestAPI.ENDPOINT_USER_ACCOUNT + "/user6/active") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .header("Authorization", "Bearer " + sebAdminToken)) .andExpect(status().isOk())