diff --git a/pom.xml b/pom.xml index ce2bb3a89ee2ae3d643e5057aef220b7ed3c9210..1155820d8d3dbcc7244bbb6756078ae27767153b 100644 --- a/pom.xml +++ b/pom.xml @@ -213,6 +213,19 @@ <groupId>org.springframework.security</groupId> <artifactId>spring-security-ldap</artifactId> </dependency> + <dependency> + <groupId>org.hibernate.validator</groupId> + <artifactId>hibernate-validator</artifactId> + </dependency> + <dependency> + <groupId>org.hibernate.validator</groupId> + <artifactId>hibernate-validator-annotation-processor</artifactId> + </dependency> + <!-- A javax.el (Unified Expression Language) implementation is needed for bean validation. --> + <dependency> + <groupId>org.mortbay.jasper</groupId> + <artifactId>apache-el</artifactId> + </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> diff --git a/src/main/java/de/thm/arsnova/controller/ControllerExceptionHandler.java b/src/main/java/de/thm/arsnova/controller/ControllerExceptionHandler.java index 0df8cd7805a533886e302ce10ae096fc908479a4..5e795ff41847a7c7f112513cdd253e213d2351d9 100644 --- a/src/main/java/de/thm/arsnova/controller/ControllerExceptionHandler.java +++ b/src/main/java/de/thm/arsnova/controller/ControllerExceptionHandler.java @@ -39,6 +39,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import de.thm.arsnova.model.EntityValidationException; import de.thm.arsnova.web.exceptions.BadRequestException; import de.thm.arsnova.web.exceptions.ForbiddenException; import de.thm.arsnova.web.exceptions.NoContentException; @@ -120,6 +121,14 @@ public class ControllerExceptionHandler extends ResponseEntityExceptionHandler { return helper.handleException(e, Level.DEBUG); } + @ExceptionHandler(EntityValidationException.class) + @ResponseBody + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Map<String, Object> handleEntityValidationException( + final EntityValidationException e, final HttpServletRequest request) { + return helper.handleException(e, Level.DEBUG); + } + @ExceptionHandler(PreconditionFailedException.class) @ResponseBody @ResponseStatus(HttpStatus.PRECONDITION_FAILED) diff --git a/src/main/java/de/thm/arsnova/model/Answer.java b/src/main/java/de/thm/arsnova/model/Answer.java index 755054ce0740ada0feec2e88830c15f22b88860b..a6e26ecca2b70e87f3a72aae376e5cb288dd76f0 100644 --- a/src/main/java/de/thm/arsnova/model/Answer.java +++ b/src/main/java/de/thm/arsnova/model/Answer.java @@ -23,6 +23,9 @@ import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; import java.util.Map; import java.util.Objects; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; import org.springframework.core.style.ToStringCreator; import de.thm.arsnova.model.serialization.FormatAnswerTypeIdResolver; @@ -36,11 +39,21 @@ import de.thm.arsnova.model.serialization.View; ) @JsonTypeIdResolver(FormatAnswerTypeIdResolver.class) public class Answer extends Entity { + @NotEmpty private String contentId; + + @NotEmpty private String roomId; + + @NotEmpty private String creatorId; + + @NotNull private Content.Format format; + + @Positive private int round; + private Map<String, Map<String, ?>> extensions; @JsonView({View.Persistence.class, View.Public.class}) diff --git a/src/main/java/de/thm/arsnova/model/ChoiceAnswer.java b/src/main/java/de/thm/arsnova/model/ChoiceAnswer.java index 25fb49606c448175b236e8eb2d2af946d05beb85..1c1d21e8aeaae85467e1dde72198775f391f5d28 100644 --- a/src/main/java/de/thm/arsnova/model/ChoiceAnswer.java +++ b/src/main/java/de/thm/arsnova/model/ChoiceAnswer.java @@ -20,12 +20,13 @@ package de.thm.arsnova.model; import com.fasterxml.jackson.annotation.JsonView; import java.util.List; +import javax.validation.constraints.PositiveOrZero; import org.springframework.core.style.ToStringCreator; import de.thm.arsnova.model.serialization.View; public class ChoiceAnswer extends Answer { - private List<Integer> selectedChoiceIndexes; + private List<@PositiveOrZero Integer> selectedChoiceIndexes; @JsonView({View.Persistence.class, View.Public.class}) public List<Integer> getSelectedChoiceIndexes() { diff --git a/src/main/java/de/thm/arsnova/model/ChoiceQuestionContent.java b/src/main/java/de/thm/arsnova/model/ChoiceQuestionContent.java index 39fd66f894d27c0aefb0c9bebf70c3177941fbe1..868d1c896129b662c4e99d40060e56095c62ee33 100644 --- a/src/main/java/de/thm/arsnova/model/ChoiceQuestionContent.java +++ b/src/main/java/de/thm/arsnova/model/ChoiceQuestionContent.java @@ -22,13 +22,16 @@ import com.fasterxml.jackson.annotation.JsonView; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import javax.validation.constraints.NotBlank; import org.springframework.core.style.ToStringCreator; import de.thm.arsnova.model.serialization.View; public class ChoiceQuestionContent extends Content { public static class AnswerOption { + @NotBlank private String label; + private int points; @JsonView({View.Persistence.class, View.Public.class}) diff --git a/src/main/java/de/thm/arsnova/model/Comment.java b/src/main/java/de/thm/arsnova/model/Comment.java index 4ae7708afc25c8ff3f7fc290f2242dec5201a0e7..c116f4e7239535fca0a74d1f8cf9a42cfd554d71 100644 --- a/src/main/java/de/thm/arsnova/model/Comment.java +++ b/src/main/java/de/thm/arsnova/model/Comment.java @@ -22,16 +22,30 @@ import com.fasterxml.jackson.annotation.JsonView; import java.util.Date; import java.util.Map; import java.util.Objects; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + import org.springframework.core.style.ToStringCreator; import de.thm.arsnova.model.serialization.View; public class Comment extends Entity { + @NotEmpty private String roomId; + + @NotEmpty private String creatorId; + + @NotBlank private String subject; + + @NotBlank private String body; + + @NotNull private Date timestamp; + private boolean read; private Map<String, Map<String, ?>> extensions; diff --git a/src/main/java/de/thm/arsnova/model/Content.java b/src/main/java/de/thm/arsnova/model/Content.java index d0e45af149272d30d4920c7c6381db280005ec54..b23e416152202d9cf0da63a583e11b2d7081844c 100644 --- a/src/main/java/de/thm/arsnova/model/Content.java +++ b/src/main/java/de/thm/arsnova/model/Content.java @@ -26,6 +26,10 @@ import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Positive; import org.springframework.core.style.ToStringCreator; import de.thm.arsnova.model.serialization.FormatContentTypeIdResolver; @@ -49,7 +53,9 @@ public class Content extends Entity { } public static class State { + @Positive private int round = 1; + private Date roundEndTimestamp; private boolean visible = true; private boolean solutionVisible = false; @@ -152,10 +158,18 @@ public class Content extends Entity { } } + @NotEmpty private String roomId; + + @NotBlank private String subject; + + @NotBlank private String body; + + @NotNull private Format format; + private Set<String> groups; private boolean abstentionsAllowed; private State state; diff --git a/src/main/java/de/thm/arsnova/model/EntityValidationException.java b/src/main/java/de/thm/arsnova/model/EntityValidationException.java new file mode 100644 index 0000000000000000000000000000000000000000..15eab9507ad80df31be595ead3dafec4812c577a --- /dev/null +++ b/src/main/java/de/thm/arsnova/model/EntityValidationException.java @@ -0,0 +1,41 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2019 The ARSnova Team and Contributors + * + * ARSnova Backend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ARSnova Backend is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.thm.arsnova.model; + +import javax.validation.ValidationException; +import org.springframework.validation.Errors; + +public class EntityValidationException extends ValidationException { + private Errors errors; + private Entity entity; + + public EntityValidationException(final Errors errors, final Entity entity) { + super(errors.getAllErrors().toString()); + this.errors = errors; + this.entity = entity; + } + + public Errors getErrors() { + return errors; + } + + public Entity getEntity() { + return entity; + } +} diff --git a/src/main/java/de/thm/arsnova/model/Motd.java b/src/main/java/de/thm/arsnova/model/Motd.java index 30f1f6c7b1786e76026e1b14db95415e6acf4f43..08ba9bd7f53df9acacdec00da6c07604120c4e57 100644 --- a/src/main/java/de/thm/arsnova/model/Motd.java +++ b/src/main/java/de/thm/arsnova/model/Motd.java @@ -21,6 +21,9 @@ package de.thm.arsnova.model; import com.fasterxml.jackson.annotation.JsonView; import java.util.Date; import java.util.Objects; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; import org.springframework.core.style.ToStringCreator; import de.thm.arsnova.model.serialization.View; @@ -34,11 +37,19 @@ public class Motd extends Entity { ROOM } + @NotEmpty private String roomId; + private Date startDate; private Date endDate; + + @NotBlank private String title; + + @NotBlank private String body; + + @NotNull private Audience audience; @Override diff --git a/src/main/java/de/thm/arsnova/model/Room.java b/src/main/java/de/thm/arsnova/model/Room.java index 00145bd286959b60040f4d6bb2d026d0653357dd..689eae0a866629bf4ae1dc3fef4a2ee7373baaa3 100644 --- a/src/main/java/de/thm/arsnova/model/Room.java +++ b/src/main/java/de/thm/arsnova/model/Room.java @@ -25,13 +25,17 @@ import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; import org.springframework.core.style.ToStringCreator; import de.thm.arsnova.model.serialization.View; public class Room extends Entity { public static class ContentGroup { + @NotBlank private String name; + private Set<String> contentIds; private boolean autoSort; @@ -105,7 +109,9 @@ public class Room extends Entity { EXECUTIVE_MODERATOR } + @NotEmpty private String userId; + private Set<Role> roles; @JsonView({View.Persistence.class, View.Public.class}) @@ -448,10 +454,18 @@ public class Room extends Entity { } } + @NotEmpty private String shortId; + + @NotEmpty private String ownerId; + + @NotBlank private String name; + + @NotBlank private String abbreviation; + private String description; private boolean closed; private Set<ContentGroup> contentGroups; diff --git a/src/main/java/de/thm/arsnova/model/TextAnswer.java b/src/main/java/de/thm/arsnova/model/TextAnswer.java index f4a2069970bc705e162eb403c481423d1f873a06..d6ed249e507153a412990861973034ab3ef340d3 100644 --- a/src/main/java/de/thm/arsnova/model/TextAnswer.java +++ b/src/main/java/de/thm/arsnova/model/TextAnswer.java @@ -20,13 +20,19 @@ package de.thm.arsnova.model; import com.fasterxml.jackson.annotation.JsonView; import java.util.Date; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; import org.springframework.core.style.ToStringCreator; import de.thm.arsnova.model.serialization.View; public class TextAnswer extends Answer { + @NotBlank private String subject; + + @NotBlank private String body; + private boolean read; @JsonView({View.Persistence.class, View.Public.class}) diff --git a/src/main/java/de/thm/arsnova/model/UserProfile.java b/src/main/java/de/thm/arsnova/model/UserProfile.java index 80b18b5dd38af86ef4bb239945c2b18b6d973735..314b3dc6f676130d00fce3443af211d7c712500f 100644 --- a/src/main/java/de/thm/arsnova/model/UserProfile.java +++ b/src/main/java/de/thm/arsnova/model/UserProfile.java @@ -24,6 +24,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; +import javax.validation.constraints.NotEmpty; import org.springframework.core.style.ToStringCreator; import de.thm.arsnova.model.serialization.View; @@ -43,7 +44,9 @@ public class UserProfile extends Entity { } public static class Account { + @NotEmpty private String password; + private String activationKey; private String passwordResetKey; private Date passwordResetTime; diff --git a/src/main/java/de/thm/arsnova/service/AnswerServiceImpl.java b/src/main/java/de/thm/arsnova/service/AnswerServiceImpl.java index 465779fc0d9e08ec243a8bf0e4e411dab2ad18f2..c231d311bdf22350cb8e2849427805bedc4b0cbd 100644 --- a/src/main/java/de/thm/arsnova/service/AnswerServiceImpl.java +++ b/src/main/java/de/thm/arsnova/service/AnswerServiceImpl.java @@ -35,6 +35,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; +import org.springframework.validation.Validator; import de.thm.arsnova.event.AfterCreationEvent; import de.thm.arsnova.event.BeforeCreationEvent; @@ -69,8 +70,9 @@ public class AnswerServiceImpl extends DefaultEntityServiceImpl<Answer> implemen final RoomService roomService, final UserService userService, @Qualifier("defaultJsonMessageConverter") final - MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { - super(Answer.class, repository, jackson2HttpMessageConverter.getObjectMapper()); + MappingJackson2HttpMessageConverter jackson2HttpMessageConverter, + final Validator validator) { + super(Answer.class, repository, jackson2HttpMessageConverter.getObjectMapper(), validator); this.answerRepository = repository; this.roomService = roomService; this.userService = userService; diff --git a/src/main/java/de/thm/arsnova/service/AttachmentServiceImpl.java b/src/main/java/de/thm/arsnova/service/AttachmentServiceImpl.java index d31955f1a9773b11dc57762c9a1cf463e4f1c8a4..a531c76148af3353946a6f6e0960bbdd9402e29a 100644 --- a/src/main/java/de/thm/arsnova/service/AttachmentServiceImpl.java +++ b/src/main/java/de/thm/arsnova/service/AttachmentServiceImpl.java @@ -20,6 +20,7 @@ package de.thm.arsnova.service; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.validation.Validator; import org.springframework.web.multipart.MultipartFile; import de.thm.arsnova.model.Attachment; @@ -31,8 +32,9 @@ public class AttachmentServiceImpl extends DefaultEntityServiceImpl<Attachment> public AttachmentServiceImpl( final AttachmentRepository repository, @Qualifier("defaultJsonMessageConverter") - final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { - super(Attachment.class, repository, jackson2HttpMessageConverter.getObjectMapper()); + final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter, + final Validator validator) { + super(Attachment.class, repository, jackson2HttpMessageConverter.getObjectMapper(), validator); this.attachmentRepository = repository; } diff --git a/src/main/java/de/thm/arsnova/service/CommentServiceImpl.java b/src/main/java/de/thm/arsnova/service/CommentServiceImpl.java index a640924aea473e7621bf1e6cd88771b376a3ae48..e4471b29f014d1de1a8b03a24e811020ca80b8dd 100644 --- a/src/main/java/de/thm/arsnova/service/CommentServiceImpl.java +++ b/src/main/java/de/thm/arsnova/service/CommentServiceImpl.java @@ -29,6 +29,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; +import org.springframework.validation.Validator; import de.thm.arsnova.event.BeforeDeletionEvent; import de.thm.arsnova.model.Comment; @@ -56,8 +57,9 @@ public class CommentServiceImpl extends DefaultEntityServiceImpl<Comment> implem final RoomService roomService, final UserService userService, @Qualifier("defaultJsonMessageConverter") - final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { - super(Comment.class, repository, jackson2HttpMessageConverter.getObjectMapper()); + final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter, + final Validator validator) { + super(Comment.class, repository, jackson2HttpMessageConverter.getObjectMapper(), validator); this.commentRepository = repository; this.roomService = roomService; this.userService = userService; diff --git a/src/main/java/de/thm/arsnova/service/ContentServiceImpl.java b/src/main/java/de/thm/arsnova/service/ContentServiceImpl.java index 4c003d790a4f0afff2c2aeb24cea7177413b6036..4321993f857e4ae52f833db4449a0190795e0d6d 100644 --- a/src/main/java/de/thm/arsnova/service/ContentServiceImpl.java +++ b/src/main/java/de/thm/arsnova/service/ContentServiceImpl.java @@ -38,6 +38,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; +import org.springframework.validation.Validator; import de.thm.arsnova.event.BeforeDeletionEvent; import de.thm.arsnova.model.Content; @@ -76,8 +77,9 @@ public class ContentServiceImpl extends DefaultEntityServiceImpl<Content> implem final LogEntryRepository dbLogger, final UserService userService, @Qualifier("defaultJsonMessageConverter") - final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { - super(Content.class, repository, jackson2HttpMessageConverter.getObjectMapper()); + final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter, + final Validator validator) { + super(Content.class, repository, jackson2HttpMessageConverter.getObjectMapper(), validator); this.contentRepository = repository; this.roomService = roomService; this.answerRepository = answerRepository; diff --git a/src/main/java/de/thm/arsnova/service/DefaultEntityServiceImpl.java b/src/main/java/de/thm/arsnova/service/DefaultEntityServiceImpl.java index dea9736b0f70898e896f09f52747512b23a4ae8e..731fba023f508c01f92999f52c53f8eca0ee64eb 100644 --- a/src/main/java/de/thm/arsnova/service/DefaultEntityServiceImpl.java +++ b/src/main/java/de/thm/arsnova/service/DefaultEntityServiceImpl.java @@ -34,6 +34,9 @@ import org.springframework.context.event.EventListener; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreFilter; import org.springframework.stereotype.Component; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; import de.thm.arsnova.event.AfterCreationEvent; import de.thm.arsnova.event.AfterDeletionEvent; @@ -45,6 +48,7 @@ import de.thm.arsnova.event.BeforeDeletionEvent; import de.thm.arsnova.event.BeforeFullUpdateEvent; import de.thm.arsnova.event.BeforePatchEvent; import de.thm.arsnova.model.Entity; +import de.thm.arsnova.model.EntityValidationException; import de.thm.arsnova.model.serialization.View; import de.thm.arsnova.persistence.CrudRepository; @@ -60,12 +64,17 @@ public class DefaultEntityServiceImpl<T extends Entity> implements EntityService protected CrudRepository<T, String> repository; protected ApplicationEventPublisher eventPublisher; private ObjectMapper objectMapper; + private Validator validator; public DefaultEntityServiceImpl( - final Class<T> type, final CrudRepository<T, String> repository, final ObjectMapper objectMapper) { + final Class<T> type, + final CrudRepository<T, String> repository, + final ObjectMapper objectMapper, + final Validator validator) { this.type = type; this.repository = repository; this.objectMapper = objectMapper; + this.validator = validator; } @Override @@ -106,6 +115,7 @@ public class DefaultEntityServiceImpl<T extends Entity> implements EntityService prepareCreate(entity); eventPublisher.publishEvent(new BeforeCreationEvent<>(this, entity)); + validate(entity); final T createdEntity = repository.save(entity); eventPublisher.publishEvent(new AfterCreationEvent<>(this, createdEntity)); finalizeCreate(createdEntity); @@ -144,6 +154,7 @@ public class DefaultEntityServiceImpl<T extends Entity> implements EntityService prepareUpdate(newEntity); eventPublisher.publishEvent(new BeforeFullUpdateEvent<>(this, newEntity, oldEntity)); + validate(newEntity); final T updatedEntity = repository.save(newEntity); eventPublisher.publishEvent(new AfterFullUpdateEvent<>(this, updatedEntity, oldEntity)); finalizeUpdate(updatedEntity); @@ -186,6 +197,7 @@ public class DefaultEntityServiceImpl<T extends Entity> implements EntityService entity.setUpdateTimestamp(new Date()); preparePatch(entity); eventPublisher.publishEvent(new BeforePatchEvent<>(this, entity, propertyGetter, changes)); + validate(entity); final T patchedEntity = repository.save(entity); eventPublisher.publishEvent(new AfterPatchEvent<>(this, patchedEntity, propertyGetter, changes)); modifyRetrieved(patchedEntity); @@ -210,6 +222,7 @@ public class DefaultEntityServiceImpl<T extends Entity> implements EntityService entity.setUpdateTimestamp(new Date()); preparePatch(entity); eventPublisher.publishEvent(new BeforePatchEvent<>(this, entity, propertyGetter, changes)); + validate(entity); } final Iterable<T> patchedEntities = repository.saveAll(entities); @@ -272,6 +285,14 @@ public class DefaultEntityServiceImpl<T extends Entity> implements EntityService } + protected void validate(final T entity) { + final Errors errors = new BeanPropertyBindingResult(entity, type.getName()); + validator.validate(entity, errors); + if (errors.hasErrors()) { + throw new EntityValidationException(errors, entity); + } + } + public String getTypeName() { return type.getSimpleName().toLowerCase(); } diff --git a/src/main/java/de/thm/arsnova/service/MotdServiceImpl.java b/src/main/java/de/thm/arsnova/service/MotdServiceImpl.java index e57202ff50620c46854cc11aab13f2a8090a9a25..3932c881be00cb2ad1260cd5a10df5ab2406b5c8 100644 --- a/src/main/java/de/thm/arsnova/service/MotdServiceImpl.java +++ b/src/main/java/de/thm/arsnova/service/MotdServiceImpl.java @@ -28,6 +28,7 @@ import org.springframework.cache.annotation.Cacheable; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; +import org.springframework.validation.Validator; import de.thm.arsnova.model.Motd; import de.thm.arsnova.model.Room; @@ -50,8 +51,9 @@ public class MotdServiceImpl extends DefaultEntityServiceImpl<Motd> implements M final UserService userService, final RoomService roomService, @Qualifier("defaultJsonMessageConverter") - final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { - super(Motd.class, repository, jackson2HttpMessageConverter.getObjectMapper()); + final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter, + final Validator validator) { + super(Motd.class, repository, jackson2HttpMessageConverter.getObjectMapper(), validator); this.motdRepository = repository; this.userService = userService; this.roomService = roomService; diff --git a/src/main/java/de/thm/arsnova/service/RoomServiceImpl.java b/src/main/java/de/thm/arsnova/service/RoomServiceImpl.java index 0bef23bacd870589636b41aaa77b3e0bea737a93..0ddfa42fb2372025da2f94d915b35e1b9b7dcd2a 100644 --- a/src/main/java/de/thm/arsnova/service/RoomServiceImpl.java +++ b/src/main/java/de/thm/arsnova/service/RoomServiceImpl.java @@ -40,6 +40,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert import org.springframework.scheduling.annotation.Scheduled; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; +import org.springframework.validation.Validator; import de.thm.arsnova.connector.client.ConnectorClient; import de.thm.arsnova.connector.model.Course; @@ -98,8 +99,9 @@ public class RoomServiceImpl extends DefaultEntityServiceImpl<Room> implements R final UserService userService, final ScoreCalculatorFactory scoreCalculatorFactory, @Qualifier("defaultJsonMessageConverter") - final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { - super(Room.class, repository, jackson2HttpMessageConverter.getObjectMapper()); + final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter, + final Validator validator) { + super(Room.class, repository, jackson2HttpMessageConverter.getObjectMapper(), validator); this.roomRepository = repository; this.dbLogger = dbLogger; this.userService = userService; diff --git a/src/main/java/de/thm/arsnova/service/UserServiceImpl.java b/src/main/java/de/thm/arsnova/service/UserServiceImpl.java index f8d2dd4072a8db7a7ab57e14cc0b323db16593a0..48ed77f620132206fcb7d64e94a08e18f1419e04 100644 --- a/src/main/java/de/thm/arsnova/service/UserServiceImpl.java +++ b/src/main/java/de/thm/arsnova/service/UserServiceImpl.java @@ -64,6 +64,7 @@ import org.springframework.security.ldap.authentication.LdapAuthenticationProvid import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.Validator; import org.springframework.web.util.UriUtils; import org.stagemonitor.core.metrics.MonitorGauges; @@ -152,8 +153,9 @@ public class UserServiceImpl extends DefaultEntityServiceImpl<UserProfile> imple final AuthenticationProviderProperties authenticationProviderProperties, final JavaMailSender mailSender, @Qualifier("defaultJsonMessageConverter") - final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { - super(UserProfile.class, repository, jackson2HttpMessageConverter.getObjectMapper()); + final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter, + final Validator validator) { + super(UserProfile.class, repository, jackson2HttpMessageConverter.getObjectMapper(), validator); this.userRepository = repository; this.securityProperties = securityProperties; this.registeredProperties = authenticationProviderProperties.getRegistered(); diff --git a/src/test/java/de/thm/arsnova/config/TestAppConfig.java b/src/test/java/de/thm/arsnova/config/TestAppConfig.java index cda6e61d222c635301f024db41256f093bcce00c..0ac92b12185fc080f7ebd1340bbff96196fd2ec0 100644 --- a/src/test/java/de/thm/arsnova/config/TestAppConfig.java +++ b/src/test/java/de/thm/arsnova/config/TestAppConfig.java @@ -42,6 +42,7 @@ import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mock.web.MockServletContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.validation.Validator; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import de.thm.arsnova.persistence.UserRepository; @@ -107,9 +108,10 @@ public class TestAppConfig { final AuthenticationProviderProperties authenticationProviderProperties, final JavaMailSender mailSender, @Qualifier("defaultJsonMessageConverter") - final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { + final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter, + final Validator validator) { return new StubUserService(repository, systemProperties, securityProperties, authenticationProviderProperties, - mailSender, jackson2HttpMessageConverter); + mailSender, jackson2HttpMessageConverter, validator); } @Bean diff --git a/src/test/java/de/thm/arsnova/event/StateEventDispatcherTest.java b/src/test/java/de/thm/arsnova/event/StateEventDispatcherTest.java index 852101f0e3ef6e4891cec048982bbd91635412cd..acc2c7e00306faa9ae4915028e3852114bce7a34 100644 --- a/src/test/java/de/thm/arsnova/event/StateEventDispatcherTest.java +++ b/src/test/java/de/thm/arsnova/event/StateEventDispatcherTest.java @@ -42,6 +42,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.validation.Validator; import de.thm.arsnova.config.AppConfig; import de.thm.arsnova.config.TestAppConfig; @@ -65,6 +66,7 @@ import de.thm.arsnova.test.context.support.WithMockUser; WebSocketConfig.class}) @ActiveProfiles("test") public class StateEventDispatcherTest { + private static final String SOME_TEXT = "SomeText"; public static final String SETTINGS_PROPERTY_NAME = "settings"; public static final String STATE_PROPERTY_NAME = "state"; private static final String QUESTIONS_ENABLED_PROPERTY_NAME = "questionsEnabled"; @@ -79,6 +81,9 @@ public class StateEventDispatcherTest { @Qualifier("defaultJsonMessageConverter") private MappingJackson2HttpMessageConverter jackson2HttpMessageConverter; + @Autowired + private Validator validator; + @Autowired private ApplicationEventPublisher eventPublisher; @@ -98,12 +103,13 @@ public class StateEventDispatcherTest { public void testDispatchRoomSettingsStateEvent() throws IOException { final ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper(); final DefaultEntityServiceImpl<Room> entityService = new DefaultEntityServiceImpl<>( - Room.class, roomRepository, objectMapper); + Room.class, roomRepository, objectMapper, validator); entityService.setApplicationEventPublisher(eventPublisher); when(roomRepository.save(any(Room.class))).then(returnsFirstArg()); final Room room = new Room(); + prefillRoomFields(room); room.setOwnerId(TEST_USER_ID); entityService.patch(room, Collections.singletonMap(QUESTIONS_ENABLED_PROPERTY_NAME, false), Room::getSettings); assertEquals(1, eventListenerConfig.getRoomSettingsStateChangeEvents().size()); @@ -115,22 +121,36 @@ public class StateEventDispatcherTest { public void testDispatchContentStateEvent() throws IOException { final ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper(); final DefaultEntityServiceImpl<Content> entityService = new DefaultEntityServiceImpl<>( - Content.class, contentRepository, objectMapper); + Content.class, contentRepository, objectMapper, validator); entityService.setApplicationEventPublisher(eventPublisher); final Room room = new Room(); + prefillRoomFields(room); room.setId(TEST_ROOM_ID); room.setOwnerId(TEST_USER_ID); when(contentRepository.save(any(Content.class))).then(returnsFirstArg()); when(roomRepository.findOne(eq(room.getId()))).thenReturn(room); final Content content = new Content(); + prefillContentFields(content); content.setRoomId(room.getId()); entityService.patch(content, Collections.singletonMap(VISIBLE_PROPERTY_NAME, false), Content::getState); assertEquals(1, eventListenerConfig.getContentStateChangeEvents().size()); assertEquals(STATE_PROPERTY_NAME, eventListenerConfig.getContentStateChangeEvents().get(0).getStateName()); } + private void prefillRoomFields(final Room room) { + room.setName(SOME_TEXT); + room.setAbbreviation(SOME_TEXT); + room.setShortId("12345678"); + } + + private void prefillContentFields(final Content content) { + content.setSubject(SOME_TEXT); + content.setBody(SOME_TEXT); + content.setFormat(Content.Format.CHOICE); + } + @Configuration public static class EventListenerConfig { private List<StateChangeEvent<Room, Room.Settings>> roomSettingsStateChangeEvents = new ArrayList<>(); diff --git a/src/test/java/de/thm/arsnova/service/DefaultEntityServiceImplTest.java b/src/test/java/de/thm/arsnova/service/DefaultEntityServiceImplTest.java index 84e64df881bac672ffcb2662de2b1b84bcb842e6..36260f0b988c670fd0608752276c80a04cae17bb 100644 --- a/src/test/java/de/thm/arsnova/service/DefaultEntityServiceImplTest.java +++ b/src/test/java/de/thm/arsnova/service/DefaultEntityServiceImplTest.java @@ -32,6 +32,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import javax.validation.ValidationException; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -43,6 +44,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.validation.Validator; import de.thm.arsnova.config.AppConfig; import de.thm.arsnova.config.TestAppConfig; @@ -64,10 +66,15 @@ import de.thm.arsnova.test.context.support.WithMockUser; WebSocketConfig.class}) @ActiveProfiles("test") public class DefaultEntityServiceImplTest { + private static final String SOME_TEXT = "SomeText"; + @Autowired @Qualifier("defaultJsonMessageConverter") private MappingJackson2HttpMessageConverter jackson2HttpMessageConverter; + @Autowired + private Validator validator; + @Autowired private ApplicationEventPublisher eventPublisher; @@ -82,7 +89,7 @@ public class DefaultEntityServiceImplTest { public void testPatch() throws IOException { final ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper(); final DefaultEntityServiceImpl<Room> entityService = - new DefaultEntityServiceImpl<>(Room.class, roomRepository, objectMapper); + new DefaultEntityServiceImpl<>(Room.class, roomRepository, objectMapper, validator); entityService.setApplicationEventPublisher(eventPublisher); when(roomRepository.save(any(Room.class))).then(returnsFirstArg()); @@ -92,6 +99,7 @@ public class DefaultEntityServiceImplTest { final String originalOwnerId = "TestUser"; final boolean originalActive = true; final Room room = new Room(); + prefillRoomFields(room); room.setId(originalId); room.setName(originalName); room.setClosed(originalActive); @@ -117,7 +125,7 @@ public class DefaultEntityServiceImplTest { public void testPatchWithList() throws IOException { final ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper(); final DefaultEntityServiceImpl<Room> entityService = - new DefaultEntityServiceImpl<>(Room.class, roomRepository, objectMapper); + new DefaultEntityServiceImpl<>(Room.class, roomRepository, objectMapper, validator); entityService.setApplicationEventPublisher(eventPublisher); when(roomRepository.saveAll(anyListOf(Room.class))).then(returnsFirstArg()); @@ -128,6 +136,7 @@ public class DefaultEntityServiceImplTest { final String originalOwnerId1 = "TestUser"; final boolean originalClosed1 = true; final Room room1 = new Room(); + prefillRoomFields(room1); room1.setId(originalId1); room1.setName(originalName1); room1.setClosed(originalClosed1); @@ -138,6 +147,7 @@ public class DefaultEntityServiceImplTest { final String originalOwnerId2 = "TestUser"; final boolean originalClosed2 = true; final Room room2 = new Room(); + prefillRoomFields(room2); room2.setId(originalId2); room2.setName(originalName2); room2.setClosed(originalClosed2); @@ -168,19 +178,23 @@ public class DefaultEntityServiceImplTest { public void testCaching() { final ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper(); final DefaultEntityServiceImpl<Room> entityService = - new DefaultEntityServiceImpl<>(Room.class, roomRepository, objectMapper); + new DefaultEntityServiceImpl<>(Room.class, roomRepository, objectMapper, validator); entityService.setApplicationEventPublisher(eventPublisher); final Room room1 = new Room(); + prefillRoomFields(room1); room1.setId("a34876427c634a9b9cb56789d73607f0"); room1.setOwnerId("TestUser"); final Room room2 = new Room(); + prefillRoomFields(room2); room2.setId("4638748d89884ff7936d7fe994a4090c"); room2.setOwnerId("TestUser"); final Room room3 = new Room(); + prefillRoomFields(room3); room3.setId("c9651db0a67b49789a354e90e0401032"); room3.setOwnerId("TestUser"); final Room room4 = new Room(); + prefillRoomFields(room4); room4.setId("66c1673056b2410b87335b9f317da5aa"); room4.setOwnerId("TestUser"); @@ -199,4 +213,27 @@ public class DefaultEntityServiceImplTest { assertSame("Entity should not be cached.", null, cacheManager.getCache("entity").get("room-" + room1.getId())); assertSame(room2, entityService.get(room1.getId())); } + + @Test(expected = ValidationException.class) + @WithMockUser("TestUser") + public void testValidation() { + final ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper(); + final DefaultEntityServiceImpl<Room> entityService = + new DefaultEntityServiceImpl<>(Room.class, roomRepository, objectMapper, validator); + entityService.setApplicationEventPublisher(eventPublisher); + + when(roomRepository.save(any(Room.class))).then(returnsFirstArg()); + + final Room room1 = new Room(); + room1.setOwnerId("TestUser"); + room1.setName(""); + + entityService.create(room1); + } + + private void prefillRoomFields(final Room room) { + room.setName(SOME_TEXT); + room.setAbbreviation(SOME_TEXT); + room.setShortId("12345678"); + } } diff --git a/src/test/java/de/thm/arsnova/service/StubUserService.java b/src/test/java/de/thm/arsnova/service/StubUserService.java index d357ee521edb0709a26908d6015e138e9e7b6577..99a336cf3488bc61bc4f713c310f31d943b8b845 100644 --- a/src/test/java/de/thm/arsnova/service/StubUserService.java +++ b/src/test/java/de/thm/arsnova/service/StubUserService.java @@ -26,6 +26,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert import org.springframework.mail.javamail.JavaMailSender; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.validation.Validator; import de.thm.arsnova.config.properties.SystemProperties; import de.thm.arsnova.config.properties.AuthenticationProviderProperties; @@ -45,9 +46,10 @@ public class StubUserService extends UserServiceImpl { final AuthenticationProviderProperties authenticationProviderProperties, final JavaMailSender mailSender, @Qualifier("defaultJsonMessageConverter") - final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { + final MappingJackson2HttpMessageConverter jackson2HttpMessageConverter, + final Validator validator) { super(repository, systemProperties, securityProperties, authenticationProviderProperties, - mailSender, jackson2HttpMessageConverter); + mailSender, jackson2HttpMessageConverter, validator); grantedAuthorities = new HashSet<>(); grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER")); }