diff --git a/pom.xml b/pom.xml index 667ccecefb1c3cc07e4dfad8157e8a3474baa31f..8f91f97a0f0d3eb37d323e59a314f30a1f2be199 100644 --- a/pom.xml +++ b/pom.xml @@ -121,6 +121,12 @@ <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> </dependency> + <!-- While commons-logging is not a required dependency, AJC fails without it. --> + <dependency> + <groupId>commons-logging</groupId> + <artifactId>commons-logging</artifactId> + <scope>provided</scope> + </dependency> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> @@ -186,9 +192,14 @@ <artifactId>log4j</artifactId> </dependency> <dependency> - <groupId>de.thm.couchdb4j</groupId> - <artifactId>couchdb4j</artifactId> - <version>0.8-SNAPSHOT</version> + <groupId>org.ektorp</groupId> + <artifactId>org.ektorp</artifactId> + <version>1.4.4</version> + </dependency> + <dependency> + <groupId>org.ektorp</groupId> + <artifactId>org.ektorp.spring</artifactId> + <version>1.4.4</version> </dependency> <dependency> <groupId>javax.servlet</groupId> diff --git a/src/main/java/de/thm/arsnova/cache/CacheBuster.java b/src/main/java/de/thm/arsnova/cache/CacheBuster.java index 913c36b24ae1d60efca90d156a85a2bbcc197fb3..c998bd4f30ddd4c69b9f6a0eaef4149e93e8fcb0 100644 --- a/src/main/java/de/thm/arsnova/cache/CacheBuster.java +++ b/src/main/java/de/thm/arsnova/cache/CacheBuster.java @@ -30,11 +30,11 @@ public class CacheBuster implements ICacheBuster, NovaEventVisitor { @CacheEvict(value = "statistics", allEntries = true) @Override - public void visit(NewInterposedQuestionEvent event) { } + public void visit(NewCommentEvent event) { } @CacheEvict(value = "statistics", allEntries = true) @Override - public void visit(DeleteInterposedQuestionEvent event) { } + public void visit(DeleteCommentEvent event) { } @Override public void visit(NewQuestionEvent event) { } @@ -51,7 +51,7 @@ public class CacheBuster implements ICacheBuster, NovaEventVisitor { @Override public void visit(LockQuestionsEvent lockQuestionsEvent) { } - @CacheEvict(value = "answers", key = "#event.Question") + @CacheEvict(value = "answers", key = "#event.content") @Override public void visit(NewAnswerEvent event) { } diff --git a/src/main/java/de/thm/arsnova/config/AppConfig.java b/src/main/java/de/thm/arsnova/config/AppConfig.java index 00b5d0c0fa9f4b4af754a1eea92ad47c7242e49e..43cb0b5d024176dd0dc08337ee25629a30ed8c7c 100644 --- a/src/main/java/de/thm/arsnova/config/AppConfig.java +++ b/src/main/java/de/thm/arsnova/config/AppConfig.java @@ -18,16 +18,27 @@ package de.thm.arsnova.config; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import de.thm.arsnova.ImageUtils; import de.thm.arsnova.connector.client.ConnectorClient; import de.thm.arsnova.connector.client.ConnectorClientImpl; +import de.thm.arsnova.entities.*; +import de.thm.arsnova.entities.serialization.CouchDbDocumentModule; +import de.thm.arsnova.entities.serialization.CouchDbObjectMapperFactory; +import de.thm.arsnova.entities.serialization.View; +import de.thm.arsnova.persistance.*; +import de.thm.arsnova.persistance.couchdb.*; +import de.thm.arsnova.persistance.couchdb.InitializingCouchDbConnector; import de.thm.arsnova.socket.ARSnovaSocket; import de.thm.arsnova.socket.ARSnovaSocketIOServer; import de.thm.arsnova.web.CacheControlInterceptorHandler; import de.thm.arsnova.web.CorsFilter; import de.thm.arsnova.web.DeprecatedApiInterceptorHandler; import de.thm.arsnova.web.ResponseInterceptorHandler; +import org.ektorp.CouchDbConnector; +import org.ektorp.impl.StdCouchDbInstance; +import org.ektorp.spring.HttpClientFactoryBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.PropertiesFactoryBean; @@ -60,6 +71,7 @@ import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -114,8 +126,8 @@ public class AppConfig extends WebMvcConfigurerAdapter { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(stringMessageConverter()); - converters.add(defaultJsonMessageConverter()); converters.add(apiV2JsonMessageConverter()); + converters.add(defaultJsonMessageConverter()); //converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build())); } @@ -163,6 +175,8 @@ public class AppConfig extends WebMvcConfigurerAdapter { @Bean public StringHttpMessageConverter stringMessageConverter() { StringHttpMessageConverter messageConverter = new StringHttpMessageConverter(); + messageConverter.setDefaultCharset(Charset.forName("UTF-8")); + messageConverter.setWriteAcceptCharset(false); List<MediaType> mediaTypes = new ArrayList<>(); mediaTypes.add(MediaType.TEXT_PLAIN); messageConverter.setSupportedMediaTypes(mediaTypes); @@ -178,7 +192,9 @@ public class AppConfig extends WebMvcConfigurerAdapter { .defaultViewInclusion(false) .indentOutput(apiIndent) .simpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); - MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(builder.build()); + ObjectMapper mapper = builder.build(); + mapper.setConfig(mapper.getSerializationConfig().withView(View.Public.class)); + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(mapper); List<MediaType> mediaTypes = new ArrayList<>(); mediaTypes.add(API_V3_MEDIA_TYPE); mediaTypes.add(MediaType.APPLICATION_JSON_UTF8); @@ -195,8 +211,11 @@ public class AppConfig extends WebMvcConfigurerAdapter { .defaultViewInclusion(false) .indentOutput(apiIndent) .featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .featuresToEnable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS); - MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(builder.build()); + .featuresToEnable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .modules(new CouchDbDocumentModule()); + ObjectMapper mapper = builder.build(); + mapper.setConfig(mapper.getSerializationConfig().withView(View.Public.class)); + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(mapper); List<MediaType> mediaTypes = new ArrayList<>(); mediaTypes.add(API_V2_MEDIA_TYPE); mediaTypes.add(MediaType.APPLICATION_JSON_UTF8); diff --git a/src/main/java/de/thm/arsnova/config/PersistanceConfig.java b/src/main/java/de/thm/arsnova/config/PersistanceConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..663e9ab8c5d30eb215736603e8c25d4170d55669 --- /dev/null +++ b/src/main/java/de/thm/arsnova/config/PersistanceConfig.java @@ -0,0 +1,103 @@ +package de.thm.arsnova.config; + +import de.thm.arsnova.entities.Answer; +import de.thm.arsnova.entities.Comment; +import de.thm.arsnova.entities.Content; +import de.thm.arsnova.entities.DbUser; +import de.thm.arsnova.entities.LogEntry; +import de.thm.arsnova.entities.Motd; +import de.thm.arsnova.entities.MotdList; +import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.VisitedSession; +import de.thm.arsnova.entities.serialization.CouchDbObjectMapperFactory; +import de.thm.arsnova.persistance.*; +import de.thm.arsnova.persistance.couchdb.*; +import org.ektorp.CouchDbConnector; +import org.ektorp.impl.StdCouchDbInstance; +import org.ektorp.spring.HttpClientFactoryBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Configuration +@Profile("!test") +public class PersistanceConfig { + @Value("${couchdb.name}") private String couchDbName; + @Value("${couchdb.host}") private String couchDbHost; + @Value("${couchdb.port}") private int couchDbPort; + + @Bean + public CouchDbConnector couchDbConnector() throws Exception { + return new InitializingCouchDbConnector(couchDbName, couchDbInstance(), new CouchDbObjectMapperFactory()); + } + + @Bean + public StdCouchDbInstance couchDbInstance() throws Exception { + return new StdCouchDbInstance(couchDbHttpClientFactory().getObject()); + } + + @Bean + public HttpClientFactoryBean couchDbHttpClientFactory() throws Exception { + final HttpClientFactoryBean factory = new HttpClientFactoryBean(); + factory.setHost(couchDbHost); + factory.setPort(couchDbPort); + + return factory; + } + + @Bean + public LogEntryRepository logEntryRepository() throws Exception { + return new CouchDbLogEntryRepository(LogEntry.class, couchDbConnector(), false); + } + + @Bean + public UserRepository userRepository() throws Exception { + return new CouchDbUserRepository(DbUser.class, couchDbConnector(), false); + } + + @Bean + public SessionRepository sessionRepository() throws Exception { + return new CouchDbSessionRepository(Session.class, couchDbConnector(), false); + } + + @Bean + public CommentRepository commentRepository() throws Exception { + return new CouchDbCommentRepository(Comment.class, couchDbConnector(), false); + } + + @Bean + public ContentRepository contentRepository() throws Exception { + return new CouchDbContentRepository(Content.class, couchDbConnector(), false); + } + + @Bean + public AnswerRepository answerRepository() throws Exception { + return new CouchDbAnswerRepository(Answer.class, couchDbConnector(), false); + } + + @Bean + public MotdRepository motdRepository() throws Exception { + return new CouchDbMotdRepository(Motd.class, couchDbConnector(), false); + } + + @Bean + public MotdListRepository motdListRepository() throws Exception { + return new CouchDbMotdListRepository(MotdList.class, couchDbConnector(), false); + } + + @Bean + public VisitedSessionRepository visitedSessionRepository() throws Exception { + return new CouchDbVisitedSessionRepository(VisitedSession.class, couchDbConnector(), false); + } + + @Bean + public StatisticsRepository statisticsRepository() throws Exception { + return new CouchDbStatisticsRepository(Object.class, couchDbConnector(), false); + } + + @Bean + public SessionStatisticsRepository sessionStatisticsRepository() throws Exception { + return new CouchDbSessionStatisticsRepository(Object.class, couchDbConnector(), false); + } +} diff --git a/src/main/java/de/thm/arsnova/controller/AudienceQuestionController.java b/src/main/java/de/thm/arsnova/controller/CommentController.java similarity index 59% rename from src/main/java/de/thm/arsnova/controller/AudienceQuestionController.java rename to src/main/java/de/thm/arsnova/controller/CommentController.java index 2de2dc3638733d9ded5968821bbc7ea6a3233510..e38c98630f2406217bcdc5f0c532f86db677b36c 100644 --- a/src/main/java/de/thm/arsnova/controller/AudienceQuestionController.java +++ b/src/main/java/de/thm/arsnova/controller/CommentController.java @@ -17,10 +17,10 @@ */ package de.thm.arsnova.controller; -import de.thm.arsnova.entities.InterposedReadingCount; -import de.thm.arsnova.entities.transport.InterposedQuestion; +import de.thm.arsnova.entities.CommentReadingCount; +import de.thm.arsnova.entities.transport.Comment; import de.thm.arsnova.exceptions.BadRequestException; -import de.thm.arsnova.services.IQuestionService; +import de.thm.arsnova.services.IContentService; import de.thm.arsnova.web.DeprecatedApi; import de.thm.arsnova.web.Pagination; import io.swagger.annotations.Api; @@ -41,50 +41,50 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; /** - * Handles requests related to audience questions, which are also called interposed or feedback questions. + * Handles requests related to comments. */ @RestController @RequestMapping("/audiencequestion") -@Api(value = "/audiencequestion", description = "the Audience Question API") -public class AudienceQuestionController extends PaginationController { +@Api(value = "/audiencequestion", description = "the Audience Content API") +public class CommentController extends PaginationController { @Autowired - private IQuestionService questionService; + private IContentService contentService; - @ApiOperation(value = "Count all the questions in current session", + @ApiOperation(value = "Count all the comments in current session", nickname = "getAudienceQuestionCount") @RequestMapping(value = "/count", method = RequestMethod.GET) @DeprecatedApi @Deprecated public int getInterposedCount(@ApiParam(value = "Session-Key from current session", required = true) @RequestParam final String sessionkey) { - return questionService.getInterposedCount(sessionkey); + return contentService.getInterposedCount(sessionkey); } - @ApiOperation(value = "count all unread interposed questions", + @ApiOperation(value = "count all unread comments", nickname = "getUnreadInterposedCount") @RequestMapping(value = "/readcount", method = RequestMethod.GET) @DeprecatedApi @Deprecated - public InterposedReadingCount getUnreadInterposedCount(@ApiParam(value = "Session-Key from current session", required = true) @RequestParam("sessionkey") final String sessionkey, String user) { - return questionService.getInterposedReadingCount(sessionkey, user); + public CommentReadingCount getUnreadInterposedCount(@ApiParam(value = "Session-Key from current session", required = true) @RequestParam("sessionkey") final String sessionkey, String user) { + return contentService.getInterposedReadingCount(sessionkey, user); } - @ApiOperation(value = "Retrieves all Interposed Questions for a Session", + @ApiOperation(value = "Retrieves all Comments for a Session", nickname = "getInterposedQuestions") @RequestMapping(value = "/", method = RequestMethod.GET) @Pagination - public List<InterposedQuestion> getInterposedQuestions(@ApiParam(value = "Session-Key from current session", required = true) @RequestParam final String sessionkey) { - return InterposedQuestion.fromList(questionService.getInterposedQuestions(sessionkey, offset, limit)); + public List<Comment> getInterposedQuestions(@ApiParam(value = "Session-Key from current session", required = true) @RequestParam final String sessionkey) { + return Comment.fromList(contentService.getInterposedQuestions(sessionkey, offset, limit)); } - @ApiOperation(value = "Retrieves an InterposedQuestion", + @ApiOperation(value = "Retrieves an Comment", nickname = "getInterposedQuestion") @RequestMapping(value = "/{questionId}", method = RequestMethod.GET) - public InterposedQuestion getInterposedQuestion(@ApiParam(value = "ID of the question that needs to be deleted", required = true) @PathVariable final String questionId) { - return new InterposedQuestion(questionService.readInterposedQuestion(questionId)); + public Comment getInterposedQuestion(@ApiParam(value = "ID of the Comment that needs to be deleted", required = true) @PathVariable final String questionId) { + return new Comment(contentService.readInterposedQuestion(questionId)); } - @ApiOperation(value = "Creates a new Interposed Question for a Session and returns the InterposedQuestion's data", + @ApiOperation(value = "Creates a new Comment for a Session and returns the Comment's data", nickname = "postInterposedQuestion") @ApiResponses(value = { @ApiResponse(code = 400, message = HTML_STATUS_400) @@ -93,19 +93,19 @@ public class AudienceQuestionController extends PaginationController { @ResponseStatus(HttpStatus.CREATED) public void postInterposedQuestion( @ApiParam(value = "Session-Key from current session", required = true) @RequestParam final String sessionkey, - @ApiParam(value = "the body from the new question", required = true) @RequestBody final de.thm.arsnova.entities.InterposedQuestion question + @ApiParam(value = "the body from the new comment", required = true) @RequestBody final de.thm.arsnova.entities.Comment comment ) { - if (questionService.saveQuestion(question)) { + if (contentService.saveQuestion(comment)) { return; } throw new BadRequestException(); } - @ApiOperation(value = "Deletes an InterposedQuestion", + @ApiOperation(value = "Deletes a Comment", nickname = "deleteInterposedQuestion") @RequestMapping(value = "/{questionId}", method = RequestMethod.DELETE) - public void deleteInterposedQuestion(@ApiParam(value = "ID of the question that needs to be deleted", required = true) @PathVariable final String questionId) { - questionService.deleteInterposedQuestion(questionId); + public void deleteInterposedQuestion(@ApiParam(value = "ID of the comment that needs to be deleted", required = true) @PathVariable final String questionId) { + contentService.deleteInterposedQuestion(questionId); } } diff --git a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java b/src/main/java/de/thm/arsnova/controller/ContentController.java similarity index 76% rename from src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java rename to src/main/java/de/thm/arsnova/controller/ContentController.java index 85ee7c9c57380a1bd5dd757c4d97c5c440c527fd..9695d365164c78e6bff74e820cd1d12da6c4dd3d 100644 --- a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java +++ b/src/main/java/de/thm/arsnova/controller/ContentController.java @@ -19,12 +19,12 @@ package de.thm.arsnova.controller; import de.thm.arsnova.PaginationListDecorator; import de.thm.arsnova.entities.Answer; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.exceptions.BadRequestException; import de.thm.arsnova.exceptions.ForbiddenException; import de.thm.arsnova.exceptions.NoContentException; import de.thm.arsnova.exceptions.NotFoundException; -import de.thm.arsnova.services.IQuestionService; +import de.thm.arsnova.services.IContentService; import de.thm.arsnova.web.DeprecatedApi; import de.thm.arsnova.web.Pagination; import io.swagger.annotations.Api; @@ -52,9 +52,9 @@ import java.util.List; @RestController @RequestMapping("/lecturerquestion") @Api(value = "/lecturerquestion", description = "Operations for Lecture Questions") -public class LecturerQuestionController extends PaginationController { +public class ContentController extends PaginationController { @Autowired - private IQuestionService questionService; + private IContentService contentService; @ApiOperation(value = "Get question with provided question Id", nickname = "getQuestion") @@ -62,56 +62,56 @@ public class LecturerQuestionController extends PaginationController { @ApiResponse(code = 404, message = HTML_STATUS_404) }) @RequestMapping(value = "/{questionId}", method = RequestMethod.GET) - public Question getQuestion(@PathVariable final String questionId) { - final Question question = questionService.getQuestion(questionId); - if (question != null) { - return question; + public Content getQuestion(@PathVariable final String questionId) { + final Content content = contentService.getQuestion(questionId); + if (content != null) { + return content; } throw new NotFoundException(); } - @ApiOperation(value = "Post provided question", + @ApiOperation(value = "Post provided content", nickname = "postQuestion") @ApiResponses(value = { @ApiResponse(code = 400, message = HTML_STATUS_400) }) @RequestMapping(value = "/", method = RequestMethod.POST) @ResponseStatus(HttpStatus.CREATED) - public Question postQuestion(@RequestBody final Question question) { - if (questionService.saveQuestion(question) != null) { - return question; + public Content postQuestion(@RequestBody final Content content) { + if (contentService.saveQuestion(content) != null) { + return content; } throw new BadRequestException(); } - @ApiOperation(value = "Post provided questions", nickname = "bulkPostQuestions") + @ApiOperation(value = "Post provided contents", nickname = "bulkPostQuestions") @ApiResponses(value = { @ApiResponse(code = 400, message = HTML_STATUS_400) }) @RequestMapping(value = "/bulk", method = RequestMethod.POST) @ResponseStatus(HttpStatus.CREATED) - public List<Question> bulkPostQuestions(@RequestBody final List<Question> questions) { - for (final Question question : questions) { - if (questionService.saveQuestion(question) == null) { + public List<Content> bulkPostQuestions(@RequestBody final List<Content> contents) { + for (final Content content : contents) { + if (contentService.saveQuestion(content) == null) { throw new BadRequestException(); } } - return questions; + return contents; } - @ApiOperation(value = "Update the question, identified by provided id, with the provided question in the Request Body", + @ApiOperation(value = "Update the content, identified by provided id, with the provided content in the Request Body", nickname = "updateQuestion") @ApiResponses(value = { @ApiResponse(code = 400, message = HTML_STATUS_400) }) @RequestMapping(value = "/{questionId}", method = RequestMethod.PUT) - public Question updateQuestion( + public Content updateQuestion( @PathVariable final String questionId, - @RequestBody final Question question + @RequestBody final Content content ) { try { - return questionService.update(question); + return contentService.update(content); } catch (final Exception e) { throw new BadRequestException(); } @@ -126,9 +126,9 @@ public class LecturerQuestionController extends PaginationController { ) { if (fcImage) { - return questionService.getQuestionFcImage(questionId); + return contentService.getQuestionFcImage(questionId); } else { - return questionService.getQuestionImage(questionId); + return contentService.getQuestionImage(questionId); } } @@ -139,9 +139,9 @@ public class LecturerQuestionController extends PaginationController { ) { if (time == 0) { - questionService.startNewPiRound(questionId, null); + contentService.startNewPiRound(questionId, null); } else { - questionService.startNewPiRoundDelayed(questionId, time); + contentService.startNewPiRoundDelayed(questionId, time); } } @@ -151,7 +151,7 @@ public class LecturerQuestionController extends PaginationController { public void cancelPiRound( @PathVariable final String questionId ) { - questionService.cancelPiRoundChange(questionId); + contentService.cancelPiRoundChange(questionId); } @RequestMapping(value = "/{questionId}/resetpiroundstate", method = RequestMethod.POST) @@ -160,7 +160,7 @@ public class LecturerQuestionController extends PaginationController { public void resetPiQuestion( @PathVariable final String questionId ) { - questionService.resetPiRoundState(questionId); + contentService.resetPiRoundState(questionId); } @ApiOperation(value = "Set voting admission on question, identified by provided id", @@ -176,7 +176,7 @@ public class LecturerQuestionController extends PaginationController { disable = disableVote; } - questionService.setVotingAdmission(questionId, disable); + contentService.setVotingAdmission(questionId, disable); } @ApiOperation(value = "Set voting admission for all questions", @@ -189,35 +189,35 @@ public class LecturerQuestionController extends PaginationController { @RequestParam(value = "preparationquestionsonly", defaultValue = "false", required = false) final boolean preparationQuestionsOnly ) { boolean disable = false; - List<Question> questions; + List<Content> contents; if (disableVote != null) { disable = disableVote; } if (lectureQuestionsOnly) { - questions = questionService.getLectureQuestions(sessionkey); - questionService.setVotingAdmissions(sessionkey, disable, questions); + contents = contentService.getLectureQuestions(sessionkey); + contentService.setVotingAdmissions(sessionkey, disable, contents); } else if (preparationQuestionsOnly) { - questions = questionService.getPreparationQuestions(sessionkey); - questionService.setVotingAdmissions(sessionkey, disable, questions); + contents = contentService.getPreparationQuestions(sessionkey); + contentService.setVotingAdmissions(sessionkey, disable, contents); } else { - questionService.setVotingAdmissionForAllQuestions(sessionkey, disable); + contentService.setVotingAdmissionForAllQuestions(sessionkey, disable); } } - @ApiOperation(value = "Publish a question, identified by provided id and question in Request Body.", + @ApiOperation(value = "Publish a content, identified by provided id and content in Request Body.", nickname = "publishQuestion") @RequestMapping(value = "/{questionId}/publish", method = RequestMethod.POST) public void publishQuestion( @PathVariable final String questionId, @RequestParam(required = false) final Boolean publish, - @RequestBody final Question question + @RequestBody final Content content ) { if (publish != null) { - question.setActive(publish); + content.setActive(publish); } - questionService.update(question); + contentService.update(content); } @ApiOperation(value = "Publish all questions", @@ -230,52 +230,52 @@ public class LecturerQuestionController extends PaginationController { @RequestParam(value = "preparationquestionsonly", defaultValue = "false", required = false) final boolean preparationQuestionsOnly ) { boolean p = publish == null || publish; - List<Question> questions; + List<Content> contents; if (lectureQuestionsOnly) { - questions = questionService.getLectureQuestions(sessionkey); - questionService.publishQuestions(sessionkey, p, questions); + contents = contentService.getLectureQuestions(sessionkey); + contentService.publishQuestions(sessionkey, p, contents); } else if (preparationQuestionsOnly) { - questions = questionService.getPreparationQuestions(sessionkey); - questionService.publishQuestions(sessionkey, p, questions); + contents = contentService.getPreparationQuestions(sessionkey); + contentService.publishQuestions(sessionkey, p, contents); } else { - questionService.publishAll(sessionkey, p); + contentService.publishAll(sessionkey, p); } } - @ApiOperation(value = "Publish statistics from question with provided id", + @ApiOperation(value = "Publish statistics from content with provided id", nickname = "publishStatistics") @RequestMapping(value = "/{questionId}/publishstatistics", method = RequestMethod.POST) public void publishStatistics( @PathVariable final String questionId, @RequestParam(required = false) final Boolean showStatistics, - @RequestBody final Question question + @RequestBody final Content content ) { if (showStatistics != null) { - question.setShowStatistic(showStatistics); + content.setShowStatistic(showStatistics); } - questionService.update(question); + contentService.update(content); } - @ApiOperation(value = "Publish correct answer from question with provided id", + @ApiOperation(value = "Publish correct answer from content with provided id", nickname = "publishCorrectAnswer") @RequestMapping(value = "/{questionId}/publishcorrectanswer", method = RequestMethod.POST) public void publishCorrectAnswer( @PathVariable final String questionId, @RequestParam(required = false) final Boolean showCorrectAnswer, - @RequestBody final Question question + @RequestBody final Content content ) { if (showCorrectAnswer != null) { - question.setShowAnswer(showCorrectAnswer); + content.setShowAnswer(showCorrectAnswer); } - questionService.update(question); + contentService.update(content); } @ApiOperation(value = "Get skill questions", nickname = "getSkillQuestions") @RequestMapping(value = "/", method = RequestMethod.GET) @Pagination - public List<Question> getSkillQuestions( + public List<Content> getSkillQuestions( @RequestParam final String sessionkey, @RequestParam(value = "lecturequestionsonly", defaultValue = "false") final boolean lectureQuestionsOnly, @RequestParam(value = "flashcardsonly", defaultValue = "false") final boolean flashcardsOnly, @@ -283,24 +283,24 @@ public class LecturerQuestionController extends PaginationController { @RequestParam(value = "requestImageData", defaultValue = "false") final boolean requestImageData, final HttpServletResponse response ) { - List<Question> questions; + List<Content> contents; if (lectureQuestionsOnly) { - questions = questionService.getLectureQuestions(sessionkey); + contents = contentService.getLectureQuestions(sessionkey); } else if (flashcardsOnly) { - questions = questionService.getFlashcards(sessionkey); + contents = contentService.getFlashcards(sessionkey); } else if (preparationQuestionsOnly) { - questions = questionService.getPreparationQuestions(sessionkey); + contents = contentService.getPreparationQuestions(sessionkey); } else { - questions = questionService.getSkillQuestions(sessionkey); + contents = contentService.getSkillQuestions(sessionkey); } - if (questions == null || questions.isEmpty()) { + if (contents == null || contents.isEmpty()) { response.setStatus(HttpStatus.NO_CONTENT.value()); return null; } else if (!requestImageData) { - questions = questionService.replaceImageData(questions); + contents = contentService.replaceImageData(contents); } - return new PaginationListDecorator<>(questions, offset, limit); + return new PaginationListDecorator<>(contents, offset, limit); } @ApiOperation(value = "Delete skill questions", @@ -314,13 +314,13 @@ public class LecturerQuestionController extends PaginationController { final HttpServletResponse response ) { if (lectureQuestionsOnly) { - questionService.deleteLectureQuestions(sessionkey); + contentService.deleteLectureQuestions(sessionkey); } else if (flashcardsOnly) { - questionService.deleteFlashcards(sessionkey); + contentService.deleteFlashcards(sessionkey); } else if (preparationQuestionsOnly) { - questionService.deletePreparationQuestions(sessionkey); + contentService.deletePreparationQuestions(sessionkey); } else { - questionService.deleteAllQuestions(sessionkey); + contentService.deleteAllQuestions(sessionkey); } } @@ -336,13 +336,13 @@ public class LecturerQuestionController extends PaginationController { @RequestParam(value = "preparationquestionsonly", defaultValue = "false") final boolean preparationQuestionsOnly ) { if (lectureQuestionsOnly) { - return questionService.getLectureQuestionCount(sessionkey); + return contentService.getLectureQuestionCount(sessionkey); } else if (flashcardsOnly) { - return questionService.getFlashcardCount(sessionkey); + return contentService.getFlashcardCount(sessionkey); } else if (preparationQuestionsOnly) { - return questionService.getPreparationQuestionCount(sessionkey); + return contentService.getPreparationQuestionCount(sessionkey); } else { - return questionService.getSkillQuestionCount(sessionkey); + return contentService.getSkillQuestionCount(sessionkey); } } @@ -352,7 +352,7 @@ public class LecturerQuestionController extends PaginationController { public void deleteAnswersAndQuestion( @PathVariable final String questionId ) { - questionService.deleteQuestion(questionId); + contentService.deleteQuestion(questionId); } @ApiOperation(value = "Get unanswered skill question ID by provided session ID", @@ -367,11 +367,11 @@ public class LecturerQuestionController extends PaginationController { ) { List<String> answers; if (lectureQuestionsOnly) { - answers = questionService.getUnAnsweredLectureQuestionIds(sessionkey); + answers = contentService.getUnAnsweredLectureQuestionIds(sessionkey); } else if (preparationQuestionsOnly) { - answers = questionService.getUnAnsweredPreparationQuestionIds(sessionkey); + answers = contentService.getUnAnsweredPreparationQuestionIds(sessionkey); } else { - answers = questionService.getUnAnsweredQuestionIds(sessionkey); + answers = contentService.getUnAnsweredQuestionIds(sessionkey); } if (answers == null || answers.isEmpty()) { throw new NoContentException(); @@ -384,7 +384,7 @@ public class LecturerQuestionController extends PaginationController { * returns a JSON document which represents the given answer of a question. * * @param questionId - * CouchDB Question ID for which the given answer should be + * CouchDB Content ID for which the given answer should be * retrieved * @return JSON Document of {@link Answer} or {@link NotFoundException} * @throws NotFoundException @@ -402,7 +402,7 @@ public class LecturerQuestionController extends PaginationController { @PathVariable final String questionId, final HttpServletResponse response ) { - final Answer answer = questionService.getMyAnswer(questionId); + final Answer answer = contentService.getMyAnswer(questionId); if (answer == null) { response.setStatus(HttpStatus.NO_CONTENT.value()); return null; @@ -418,7 +418,7 @@ public class LecturerQuestionController extends PaginationController { * properties are set * * @param questionId - * CouchDB Question ID for which the given answers should be + * CouchDB Content ID for which the given answers should be * retrieved * @throws NotFoundException * if wrong session, wrong question or no answers was given @@ -436,16 +436,16 @@ public class LecturerQuestionController extends PaginationController { ) { List<Answer> answers; if (allAnswers) { - answers = questionService.getAllAnswers(questionId, -1, -1); + answers = contentService.getAllAnswers(questionId, -1, -1); } else if (null == piRound) { - answers = questionService.getAnswers(questionId, offset, limit); + answers = contentService.getAnswers(questionId, offset, limit); } else { if (piRound < 1 || piRound > 2) { response.setStatus(HttpStatus.BAD_REQUEST.value()); return null; } - answers = questionService.getAnswers(questionId, piRound, offset, limit); + answers = contentService.getAnswers(questionId, piRound, offset, limit); } if (answers == null) { return new ArrayList<>(); @@ -461,7 +461,7 @@ public class LecturerQuestionController extends PaginationController { @RequestBody final de.thm.arsnova.entities.transport.Answer answer, final HttpServletResponse response ) { - return questionService.saveAnswer(questionId, answer); + return contentService.saveAnswer(questionId, answer); } @ApiOperation(value = "Update answer, provided in Request Body, identified by question ID and answer ID", @@ -473,7 +473,7 @@ public class LecturerQuestionController extends PaginationController { @RequestBody final Answer answer, final HttpServletResponse response ) { - return questionService.updateAnswer(answer); + return contentService.updateAnswer(answer); } @ApiOperation(value = "Get Image, identified by question ID and answer ID", @@ -485,7 +485,7 @@ public class LecturerQuestionController extends PaginationController { final HttpServletResponse response ) { - return questionService.getImage(questionId, answerId); + return contentService.getImage(questionId, answerId); } @ApiOperation(value = "Delete answer, identified by question ID and answer ID", @@ -496,7 +496,7 @@ public class LecturerQuestionController extends PaginationController { @PathVariable final String answerId, final HttpServletResponse response ) { - questionService.deleteAnswer(questionId, answerId); + contentService.deleteAnswer(questionId, answerId); } @ApiOperation(value = "Delete answers from a question, identified by question ID", @@ -506,7 +506,7 @@ public class LecturerQuestionController extends PaginationController { @PathVariable final String questionId, final HttpServletResponse response ) { - questionService.deleteAnswers(questionId); + contentService.deleteAnswers(questionId); } @ApiOperation(value = "Delete all answers and questions from a session, identified by sessionkey", @@ -519,18 +519,18 @@ public class LecturerQuestionController extends PaginationController { final HttpServletResponse response ) { if (lectureQuestionsOnly) { - questionService.deleteAllLectureAnswers(sessionkey); + contentService.deleteAllLectureAnswers(sessionkey); } else if (preparationQuestionsOnly) { - questionService.deleteAllPreparationAnswers(sessionkey); + contentService.deleteAllPreparationAnswers(sessionkey); } else { - questionService.deleteAllQuestionsAnswers(sessionkey); + contentService.deleteAllQuestionsAnswers(sessionkey); } } /** * * @param questionId - * CouchDB Question ID for which the given answers should be + * CouchDB Content ID for which the given answers should be * retrieved * @return count of answers for given question id * @throws NotFoundException @@ -544,7 +544,7 @@ public class LecturerQuestionController extends PaginationController { @Deprecated @RequestMapping(value = "/{questionId}/answercount", method = RequestMethod.GET) public int getAnswerCount(@PathVariable final String questionId) { - return questionService.getAnswerCount(questionId); + return contentService.getAnswerCount(questionId); } @ApiOperation(value = "Get the amount of answers for a question, identified by the question ID", @@ -552,8 +552,8 @@ public class LecturerQuestionController extends PaginationController { @RequestMapping(value = "/{questionId}/allroundanswercount", method = RequestMethod.GET) public List<Integer> getAllAnswerCount(@PathVariable final String questionId) { return Arrays.asList( - questionService.getAnswerCount(questionId, 1), - questionService.getAnswerCount(questionId, 2) + contentService.getAnswerCount(questionId, 1), + contentService.getAnswerCount(questionId, 2) ); } @@ -561,7 +561,7 @@ public class LecturerQuestionController extends PaginationController { nickname = "getTotalAnswerCountByQuestion") @RequestMapping(value = "/{questionId}/totalanswercount", method = RequestMethod.GET) public int getTotalAnswerCountByQuestion(@PathVariable final String questionId) { - return questionService.getTotalAnswerCountByQuestion(questionId); + return contentService.getTotalAnswerCountByQuestion(questionId); } @ApiOperation(value = "Get the amount of answers and abstention answers by a question, identified by the question ID", @@ -569,8 +569,8 @@ public class LecturerQuestionController extends PaginationController { @RequestMapping(value = "/{questionId}/answerandabstentioncount", method = RequestMethod.GET) public List<Integer> getAnswerAndAbstentionCount(@PathVariable final String questionId) { return Arrays.asList( - questionService.getAnswerCount(questionId), - questionService.getAbstentionAnswerCount(questionId) + contentService.getAnswerCount(questionId), + contentService.getAbstentionAnswerCount(questionId) ); } @@ -579,7 +579,7 @@ public class LecturerQuestionController extends PaginationController { @RequestMapping(value = "/{questionId}/freetextanswer/", method = RequestMethod.GET) @Pagination public List<Answer> getFreetextAnswers(@PathVariable final String questionId) { - return questionService.getFreetextAnswers(questionId, offset, limit); + return contentService.getFreetextAnswers(questionId, offset, limit); } @ApiOperation(value = "Get my answers of an session, identified by the sessionkey", @@ -588,7 +588,7 @@ public class LecturerQuestionController extends PaginationController { @Deprecated @RequestMapping(value = "/myanswers", method = RequestMethod.GET) public List<Answer> getMyAnswers(@RequestParam final String sessionkey) { - return questionService.getMyAnswers(sessionkey); + return contentService.getMyAnswers(sessionkey); } @ApiOperation(value = "Get the total amount of answers of an session, identified by the sessionkey", @@ -602,11 +602,11 @@ public class LecturerQuestionController extends PaginationController { @RequestParam(value = "preparationquestionsonly", defaultValue = "false") final boolean preparationQuestionsOnly ) { if (lectureQuestionsOnly) { - return questionService.countLectureQuestionAnswers(sessionkey); + return contentService.countLectureQuestionAnswers(sessionkey); } else if (preparationQuestionsOnly) { - return questionService.countPreparationQuestionAnswers(sessionkey); + return contentService.countPreparationQuestionAnswers(sessionkey); } else { - return questionService.getTotalAnswerCount(sessionkey); + return contentService.getTotalAnswerCount(sessionkey); } } } diff --git a/src/main/java/de/thm/arsnova/controller/LegacyController.java b/src/main/java/de/thm/arsnova/controller/LegacyController.java index c9f809c63b73415f788860754cec0716d9c2be90..a1d3a9de2a3ff25df4bba88b735f4e46853a8a94 100644 --- a/src/main/java/de/thm/arsnova/controller/LegacyController.java +++ b/src/main/java/de/thm/arsnova/controller/LegacyController.java @@ -17,7 +17,7 @@ */ package de.thm.arsnova.controller; -import de.thm.arsnova.services.IQuestionService; +import de.thm.arsnova.services.IContentService; import de.thm.arsnova.web.DeprecatedApi; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @@ -33,7 +33,7 @@ import org.springframework.web.bind.annotation.ResponseBody; public class LegacyController extends AbstractController { @Autowired - private IQuestionService questionService; + private IContentService contentService; /* specific routes */ @@ -95,7 +95,7 @@ public class LegacyController extends AbstractController { @RequestMapping(value = "/session/{sessionKey}/interposed", method = RequestMethod.DELETE) @ResponseBody public void deleteAllInterposedQuestions(@PathVariable final String sessionKey) { - questionService.deleteAllInterposedQuestions(sessionKey); + contentService.deleteAllInterposedQuestions(sessionKey); } @DeprecatedApi diff --git a/src/main/java/de/thm/arsnova/controller/SessionController.java b/src/main/java/de/thm/arsnova/controller/SessionController.java index 1c1c6599bd3873f1a4519561d905a69faac235f3..9b0dcd48a023fa8903103653c755eae633174dfc 100644 --- a/src/main/java/de/thm/arsnova/controller/SessionController.java +++ b/src/main/java/de/thm/arsnova/controller/SessionController.java @@ -298,7 +298,7 @@ public class SessionController extends PaginationController { public List<ImportExportSession> getExport( @ApiParam(value = "sessionkey", required = true) @RequestParam(value = "sessionkey", defaultValue = "") final List<String> sessionkey, @ApiParam(value = "wether statistics shall be exported", required = true) @RequestParam(value = "withAnswerStatistics", defaultValue = "false") final Boolean withAnswerStatistics, - @ApiParam(value = "wether interposed questions shall be exported", required = true) @RequestParam(value = "withFeedbackQuestions", defaultValue = "false") final Boolean withFeedbackQuestions, + @ApiParam(value = "wether comments shall be exported", required = true) @RequestParam(value = "withFeedbackQuestions", defaultValue = "false") final Boolean withFeedbackQuestions, final HttpServletResponse response ) { List<ImportExportSession> sessions = new ArrayList<>(); diff --git a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java deleted file mode 100644 index 763a2dacf2b862d752ea8a3762593aa7ac7c1e8e..0000000000000000000000000000000000000000 --- a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java +++ /dev/null @@ -1,2886 +0,0 @@ -/* - * This file is part of ARSnova Backend. - * Copyright (C) 2012-2017 The ARSnova Team - * - * 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.dao; - -import com.fourspaces.couchdb.Database; -import com.fourspaces.couchdb.Document; -import com.fourspaces.couchdb.Results; -import com.fourspaces.couchdb.RowResult; -import com.fourspaces.couchdb.View; -import com.fourspaces.couchdb.ViewResults; -import com.google.common.collect.Lists; -import de.thm.arsnova.connector.model.Course; -import de.thm.arsnova.domain.CourseScore; -import de.thm.arsnova.entities.*; -import de.thm.arsnova.entities.transport.AnswerQueueElement; -import de.thm.arsnova.entities.transport.ImportExportSession; -import de.thm.arsnova.entities.transport.ImportExportSession.ImportExportQuestion; -import de.thm.arsnova.events.NewAnswerEvent; -import de.thm.arsnova.exceptions.NotFoundException; -import de.thm.arsnova.services.ISessionService; -import net.sf.ezmorph.Morpher; -import net.sf.ezmorph.MorpherRegistry; -import net.sf.ezmorph.bean.BeanMorpher; -import net.sf.json.JSONArray; -import net.sf.json.JSONObject; -import net.sf.json.util.JSONUtils; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.aop.framework.AopContext; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.cache.annotation.Caching; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.context.annotation.Profile; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Isolation; -import org.springframework.transaction.annotation.Transactional; - -import java.io.IOException; -import java.util.*; -import java.util.Map.Entry; -import java.util.concurrent.ConcurrentLinkedQueue; - -/** - * Database implementation based on CouchDB. - * - * Note to developers: - * - * This class makes use of Spring Framework's caching annotations. When you are about to add new functionality, - * you should also think about the possibility of caching. Ideally, your methods should be dependent on domain - * objects like Session or Question, which can be used as cache keys. Relying on plain String objects as a key, e.g. - * by passing only a Session's keyword, will make your cache annotations less readable. You will also need to think - * about cases where your cache needs to be updated and evicted. - * - * In order to use cached methods from within this object, you have to use the getDatabaseDao() method instead of - * using the "this" pointer. This is because caching is only available if the method is called through a Spring Proxy, - * which is not the case when using "this". - * - * @see <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html">Spring Framework's Cache Abstraction</a> - * @see <a href="https://github.com/thm-projects/arsnova-backend/wiki/Caching">Caching in ARSnova explained</a> - */ -@Profile("!test") -@Service("databaseDao") -public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware { - - private static final int BULK_PARTITION_SIZE = 500; - - @Autowired - private ISessionService sessionService; - - private String databaseHost; - private int databasePort; - private String databaseName; - private Database database; - - private ApplicationEventPublisher publisher; - - private final Queue<AbstractMap.SimpleEntry<Document, AnswerQueueElement>> answerQueue = new ConcurrentLinkedQueue<>(); - - private static final Logger logger = LoggerFactory.getLogger(CouchDBDao.class); - - @Value("${couchdb.host}") - public void setDatabaseHost(final String newDatabaseHost) { - databaseHost = newDatabaseHost; - } - - @Value("${couchdb.port}") - public void setDatabasePort(final String newDatabasePort) { - databasePort = Integer.parseInt(newDatabasePort); - } - - @Value("${couchdb.name}") - public void setDatabaseName(final String newDatabaseName) { - databaseName = newDatabaseName; - } - - public void setSessionService(final ISessionService service) { - sessionService = service; - } - - /** - * <strike> - * Allows access to the proxy object. It has to be used instead of <code>this</code> for local calls to public - * methods for caching purposes. This is an ugly but necessary temporary workaround until a better solution is - * implemented (e.g. use of AspectJ's weaving). - * @return the proxy for CouchDBDao - * </strike> - */ - private @NonNull IDatabaseDao getDatabaseDao() { - return this; - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { - this.publisher = publisher; - } - - @Override - public void log(String event, Map<String, Object> payload, LogEntry.LogLevel level) { - final Document d = new Document(); - d.put("timestamp", System.currentTimeMillis()); - d.put("type", "log"); - d.put("event", event); - d.put("level", level.ordinal()); - d.put("payload", payload); - try { - database.saveDocument(d); - } catch (final IOException e) { - logger.error("Logging of '{}' event to database failed.", event, e); - } - } - - @Override - public void log(String event, Map<String, Object> payload) { - log(event, payload, LogEntry.LogLevel.INFO); - } - - @Override - public void log(String event, LogEntry.LogLevel level, Object... rawPayload) { - if (rawPayload.length % 2 != 0) { - throw new IllegalArgumentException(""); - } - Map<String, Object> payload = new HashMap<>(); - for (int i = 0; i < rawPayload.length; i += 2) { - payload.put((String) rawPayload[i], rawPayload[i + 1]); - } - log(event, payload, level); - } - - @Override - public void log(String event, Object... rawPayload) { - log(event, LogEntry.LogLevel.INFO, rawPayload); - } - - @Override - public List<Session> getMySessions(final User user, final int start, final int limit) { - return this.getDatabaseDao().getSessionsForUsername(user.getUsername(), start, limit); - } - - @Override - public List<Session> getSessionsForUsername(String username, final int start, final int limit) { - final View view = new View("session/partial_by_sessiontype_creator_name"); - if (start > 0) { - view.setSkip(start); - } - if (limit > 0) { - view.setLimit(limit); - } - view.setStartKeyArray("", username); - view.setEndKeyArray("", username, "{}"); - - final Results<Session> results = getDatabase().queryView(view, Session.class); - - final List<Session> result = new ArrayList<>(); - for (final RowResult<Session> row : results.getRows()) { - final Session session = row.getValue(); - session.setCreator(row.getKey().getString(1)); - session.setName(row.getKey().getString(2)); - session.set_id(row.getId()); - result.add(session); - } - return result; - } - - @Override - public List<Session> getPublicPoolSessions() { - final View view = new View("session/partial_by_ppsubject_name_for_publicpool"); - - final ViewResults sessions = getDatabase().view(view); - - final List<Session> result = new ArrayList<>(); - - for (final Document d : sessions.getResults()) { - final Session session = (Session) JSONObject.toBean( - d.getJSONObject().getJSONObject("value"), - Session.class - ); - session.set_id(d.getId()); - result.add(session); - } - return result; - } - - @Override - public List<SessionInfo> getPublicPoolSessionsInfo() { - final List<Session> sessions = this.getPublicPoolSessions(); - return getInfosForSessions(sessions); - } - - @Override - public List<Session> getMyPublicPoolSessions(final User user) { - final View view = new View("session/partial_by_sessiontype_creator_name"); - view.setStartKeyArray("public_pool", user.getUsername()); - view.setEndKeyArray("public_pool", user.getUsername(), "{}"); - - final ViewResults sessions = getDatabase().view(view); - - final List<Session> result = new ArrayList<>(); - for (final Document d : sessions.getResults()) { - final Session session = (Session) JSONObject.toBean( - d.getJSONObject().getJSONObject("value"), - Session.class - ); - session.setCreator(d.getJSONObject().getJSONArray("key").getString(1)); - session.setName(d.getJSONObject().getJSONArray("key").getString(2)); - session.set_id(d.getId()); - result.add(session); - } - return result; - } - - @Override - public List<SessionInfo> getMyPublicPoolSessionsInfo(final User user) { - final List<Session> sessions = this.getMyPublicPoolSessions(user); - if (sessions.isEmpty()) { - return new ArrayList<>(); - } - return getInfosForSessions(sessions); - } - - @Override - public List<SessionInfo> getMySessionsInfo(final User user, final int start, final int limit) { - final List<Session> sessions = this.getMySessions(user, start, limit); - if (sessions.isEmpty()) { - return new ArrayList<>(); - } - return getInfosForSessions(sessions); - } - - private List<SessionInfo> getInfosForSessions(final List<Session> sessions) { - /* TODO: migrate to new view */ - final ExtendedView questionCountView = new ExtendedView("content/by_sessionid"); - final ExtendedView answerCountView = new ExtendedView("answer/by_sessionid"); - final ExtendedView interposedCountView = new ExtendedView("comment/by_sessionid"); - final ExtendedView unreadInterposedCountView = new ExtendedView("comment/by_sessionid_read"); - - interposedCountView.setSessionIdKeys(sessions); - interposedCountView.setGroup(true); - questionCountView.setSessionIdKeys(sessions); - questionCountView.setGroup(true); - answerCountView.setSessionIdKeys(sessions); - answerCountView.setGroup(true); - List<String> unreadInterposedQueryKeys = new ArrayList<>(); - for (Session s : sessions) { - unreadInterposedQueryKeys.add("[\"" + s.get_id() + "\",false]"); - } - unreadInterposedCountView.setKeys(unreadInterposedQueryKeys); - unreadInterposedCountView.setGroup(true); - return getSessionInfoData(sessions, questionCountView, answerCountView, interposedCountView, unreadInterposedCountView); - } - - private List<SessionInfo> getInfosForVisitedSessions(final List<Session> sessions, final User user) { - final ExtendedView answeredQuestionsView = new ExtendedView("answer/by_user_sessionid"); - final ExtendedView questionIdsView = new ExtendedView("content/by_sessionid"); - questionIdsView.setSessionIdKeys(sessions); - List<String> answeredQuestionQueryKeys = new ArrayList<>(); - for (Session s : sessions) { - answeredQuestionQueryKeys.add("[\"" + user.getUsername() + "\",\"" + s.get_id() + "\"]"); - } - answeredQuestionsView.setKeys(answeredQuestionQueryKeys); - return getVisitedSessionInfoData(sessions, answeredQuestionsView, questionIdsView); - } - - private List<SessionInfo> getVisitedSessionInfoData(List<Session> sessions, - ExtendedView answeredQuestionsView, ExtendedView questionIdsView) { - final Map<String, Set<String>> answeredQuestionsMap = new HashMap<>(); - final Map<String, Set<String>> questionIdMap = new HashMap<>(); - final ViewResults answeredQuestionsViewResults = getDatabase().view(answeredQuestionsView); - final ViewResults questionIdsViewResults = getDatabase().view(questionIdsView); - - // Maps a session ID to a set of question IDs of answered questions of that session - for (final Document d : answeredQuestionsViewResults.getResults()) { - final String sessionId = d.getJSONArray("key").getString(1); - final String questionId = d.getString("value"); - Set<String> questionIdsInSession = answeredQuestionsMap.get(sessionId); - if (questionIdsInSession == null) { - questionIdsInSession = new HashSet<>(); - } - questionIdsInSession.add(questionId); - answeredQuestionsMap.put(sessionId, questionIdsInSession); - } - - // Maps a session ID to a set of question IDs of that session - for (final Document d : questionIdsViewResults.getResults()) { - final String sessionId = d.getString("key"); - final String questionId = d.getId(); - Set<String> questionIdsInSession = questionIdMap.get(sessionId); - if (questionIdsInSession == null) { - questionIdsInSession = new HashSet<>(); - } - questionIdsInSession.add(questionId); - questionIdMap.put(sessionId, questionIdsInSession); - } - - // For each session, count the question IDs that are not yet answered - Map<String, Integer> unansweredQuestionsCountMap = new HashMap<>(); - for (final Session s : sessions) { - if (!questionIdMap.containsKey(s.get_id())) { - continue; - } - // Note: create a copy of the first set so that we don't modify the contents in the original set - Set<String> questionIdsInSession = new HashSet<>(questionIdMap.get(s.get_id())); - Set<String> answeredQuestionIdsInSession = answeredQuestionsMap.get(s.get_id()); - if (answeredQuestionIdsInSession == null) { - answeredQuestionIdsInSession = new HashSet<>(); - } - questionIdsInSession.removeAll(answeredQuestionIdsInSession); - unansweredQuestionsCountMap.put(s.get_id(), questionIdsInSession.size()); - } - - List<SessionInfo> sessionInfos = new ArrayList<>(); - for (Session session : sessions) { - int numUnanswered = 0; - - if (unansweredQuestionsCountMap.containsKey(session.get_id())) { - numUnanswered = unansweredQuestionsCountMap.get(session.get_id()); - } - SessionInfo info = new SessionInfo(session); - info.setNumUnanswered(numUnanswered); - sessionInfos.add(info); - } - return sessionInfos; - } - - private List<SessionInfo> getSessionInfoData(final List<Session> sessions, - final ExtendedView questionCountView, - final ExtendedView answerCountView, - final ExtendedView interposedCountView, - final ExtendedView unredInterposedCountView) { - final ViewResults questionCountViewResults = getDatabase().view(questionCountView); - final ViewResults answerCountViewResults = getDatabase().view(answerCountView); - final ViewResults interposedCountViewResults = getDatabase().view(interposedCountView); - final ViewResults unredInterposedCountViewResults = getDatabase().view(unredInterposedCountView); - - Map<String, Integer> questionCountMap = new HashMap<>(); - for (final Document d : questionCountViewResults.getResults()) { - questionCountMap.put(d.getString("key"), d.getInt("value")); - } - Map<String, Integer> answerCountMap = new HashMap<>(); - for (final Document d : answerCountViewResults.getResults()) { - answerCountMap.put(d.getString("key"), d.getInt("value")); - } - Map<String, Integer> interposedCountMap = new HashMap<>(); - for (final Document d : interposedCountViewResults.getResults()) { - interposedCountMap.put(d.getString("key"), d.getInt("value")); - } - Map<String, Integer> unredInterposedCountMap = new HashMap<>(); - for (final Document d : unredInterposedCountViewResults.getResults()) { - unredInterposedCountMap.put(d.getJSONArray("key").getString(0), d.getInt("value")); - } - - List<SessionInfo> sessionInfos = new ArrayList<>(); - for (Session session : sessions) { - int numQuestions = 0; - int numAnswers = 0; - int numInterposed = 0; - int numUnredInterposed = 0; - if (questionCountMap.containsKey(session.get_id())) { - numQuestions = questionCountMap.get(session.get_id()); - } - if (answerCountMap.containsKey(session.get_id())) { - numAnswers = answerCountMap.get(session.get_id()); - } - if (interposedCountMap.containsKey(session.get_id())) { - numInterposed = interposedCountMap.get(session.get_id()); - } - if (unredInterposedCountMap.containsKey(session.get_id())) { - numUnredInterposed = unredInterposedCountMap.get(session.get_id()); - } - - SessionInfo info = new SessionInfo(session); - info.setNumQuestions(numQuestions); - info.setNumAnswers(numAnswers); - info.setNumInterposed(numInterposed); - info.setNumUnredInterposed(numUnredInterposed); - sessionInfos.add(info); - } - return sessionInfos; - } - - @Cacheable("skillquestions") - @Override - public List<Question> getSkillQuestionsForUsers(final Session session) { - final List<Question> questions = new ArrayList<>(); - final String viewName = "content/doc_by_sessionid_variant_active"; - final View view1 = new View(viewName); - final View view2 = new View(viewName); - final View view3 = new View(viewName); - view1.setStartKey(session.get_id(), "lecture", true); - view1.setEndKey(session.get_id(), "lecture", true, "{}"); - view2.setStartKey(session.get_id(), "preparation", true); - view2.setEndKey(session.get_id(), "preparation", true, "{}"); - view3.setStartKey(session.get_id(), "flashcard", true); - view3.setEndKey(session.get_id(), "flashcard", true, "{}"); - questions.addAll(getQuestions(view1, session)); - questions.addAll(getQuestions(view2, session)); - questions.addAll(getQuestions(view3, session)); - - return questions; - } - - @Cacheable("skillquestions") - @Override - public List<Question> getSkillQuestionsForTeachers(final Session session) { - final View view = new View("content/doc_by_sessionid_variant_active"); - view.setStartKey(session.get_id()); - view.setEndKey(session.get_id(), "{}"); - - return getQuestions(view, session); - } - - @Override - public int getSkillQuestionCount(final Session session) { - final View view = new View("content/by_sessionid_variant_active"); - view.setStartKey(session.get_id()); - view.setEndKey(session.get_id(), "{}"); - - return getQuestionCount(view); - } - - @Override - @Cacheable("sessions") - public Session getSessionFromKeyword(final String keyword) { - final View view = new View("session/by_keyword"); - view.setIncludeDocs(true); - view.setKey(keyword); - final ViewResults results = getDatabase().view(view); - - if (results.getJSONArray("rows").optJSONObject(0) == null) { - throw new NotFoundException(); - } - return (Session) JSONObject.toBean( - results.getJSONArray("rows").optJSONObject(0).optJSONObject("doc"), - Session.class - ); - } - - @Override - @Cacheable("sessions") - public Session getSessionFromId(final String sessionId) { - try { - final Document doc = getDatabase().getDocument(sessionId); - if (!"session".equals(doc.getString("type"))) { - return null; - } - - return (Session) JSONObject.toBean( - doc.getJSONObject(), - Session.class - ); - } catch (IOException e) { - return null; - } - } - - @Override - public Session saveSession(final User user, final Session session) { - session.setKeyword(sessionService.generateKeyword()); - session.setActive(true); - session.setFeedbackLock(false); - - final Document sessionDocument = new Document(); - sessionDocument.put("type", "session"); - sessionDocument.put("name", session.getName()); - sessionDocument.put("shortName", session.getShortName()); - sessionDocument.put("keyword", session.getKeyword()); - sessionDocument.put("creator", user.getUsername()); - sessionDocument.put("active", session.isActive()); - sessionDocument.put("courseType", session.getCourseType()); - sessionDocument.put("courseId", session.getCourseId()); - sessionDocument.put("creationTime", session.getCreationTime()); - sessionDocument.put("learningProgressOptions", JSONObject.fromObject(session.getLearningProgressOptions())); - sessionDocument.put("ppAuthorName", session.getPpAuthorName()); - sessionDocument.put("ppAuthorMail", session.getPpAuthorMail()); - sessionDocument.put("ppUniversity", session.getPpUniversity()); - sessionDocument.put("ppLogo", session.getPpLogo()); - sessionDocument.put("ppSubject", session.getPpSubject()); - sessionDocument.put("ppLicense", session.getPpLicense()); - sessionDocument.put("ppDescription", session.getPpDescription()); - sessionDocument.put("ppFaculty", session.getPpFaculty()); - sessionDocument.put("ppLevel", session.getPpLevel()); - sessionDocument.put("sessionType", session.getSessionType()); - sessionDocument.put("features", JSONObject.fromObject(session.getFeatures())); - sessionDocument.put("feedbackLock", session.getFeedbackLock()); - try { - database.saveDocument(sessionDocument); - session.set_id(sessionDocument.getId()); - } catch (final IOException e) { - return null; - } - - return session.get_id() != null ? session : null; - } - - @Override - public boolean sessionKeyAvailable(final String keyword) { - final View view = new View("session/by_keyword"); - view.setKey(keyword); - final ViewResults results = getDatabase().view(view); - - return !results.containsKey(keyword); - } - - private String getSessionKeyword(final String internalSessionId) throws IOException { - final Document document = getDatabase().getDocument(internalSessionId); - if (document.has("keyword")) { - return (String) document.get("keyword"); - } - logger.error("No session found for internal id {}.", internalSessionId); - return null; - } - - private Database getDatabase() { - if (database == null) { - try { - final com.fourspaces.couchdb.Session session = new com.fourspaces.couchdb.Session( - databaseHost, - databasePort - ); - database = session.getDatabase(databaseName); - } catch (final Exception e) { - logger.error("Cannot connect to CouchDB database '{}' on host '{}' using port {}.", - databaseName, databaseHost, databasePort, e); - } - } - - return database; - } - - @Caching(evict = {@CacheEvict(value = "skillquestions", key = "#session"), - @CacheEvict(value = "lecturequestions", key = "#session", condition = "#question.getQuestionVariant().equals('lecture')"), - @CacheEvict(value = "preparationquestions", key = "#session", condition = "#question.getQuestionVariant().equals('preparation')"), - @CacheEvict(value = "flashcardquestions", key = "#session", condition = "#question.getQuestionVariant().equals('flashcard')") }, - put = {@CachePut(value = "questions", key = "#question._id")}) - @Override - public Question saveQuestion(final Session session, final Question question) { - final Document q = toQuestionDocument(session, question); - try { - database.saveDocument(q); - question.set_id(q.getId()); - question.set_rev(q.getRev()); - return question; - } catch (final IOException e) { - logger.error("Could not save question {}.", question, e); - } - return null; - } - - private Document toQuestionDocument(final Session session, final Question question) { - Document q = new Document(); - - question.updateRoundManagementState(); - q.put("type", "skill_question"); - q.put("questionType", question.getQuestionType()); - q.put("ignoreCaseSensitive", question.isIgnoreCaseSensitive()); - q.put("ignoreWhitespaces", question.isIgnoreWhitespaces()); - q.put("ignorePunctuation", question.isIgnorePunctuation()); - q.put("fixedAnswer", question.isFixedAnswer()); - q.put("strictMode", question.isStrictMode()); - q.put("rating", question.getRating()); - q.put("correctAnswer", question.getCorrectAnswer()); - q.put("questionVariant", question.getQuestionVariant()); - q.put("sessionId", session.get_id()); - q.put("subject", question.getSubject()); - q.put("text", question.getText()); - q.put("active", question.isActive()); - q.put("votingDisabled", question.isVotingDisabled()); - q.put("number", 0); // TODO: This number is now unused. A clean up is necessary. - q.put("releasedFor", question.getReleasedFor()); - q.put("possibleAnswers", question.getPossibleAnswers()); - q.put("noCorrect", question.isNoCorrect()); - q.put("piRound", question.getPiRound()); - q.put("piRoundStartTime", question.getPiRoundStartTime()); - q.put("piRoundEndTime", question.getPiRoundEndTime()); - q.put("piRoundFinished", question.isPiRoundFinished()); - q.put("piRoundActive", question.isPiRoundActive()); - q.put("showStatistic", question.isShowStatistic()); - q.put("showAnswer", question.isShowAnswer()); - q.put("abstention", question.isAbstention()); - q.put("image", question.getImage()); - q.put("fcImage", question.getFcImage()); - q.put("gridSize", question.getGridSize()); - q.put("offsetX", question.getOffsetX()); - q.put("offsetY", question.getOffsetY()); - q.put("zoomLvl", question.getZoomLvl()); - q.put("gridOffsetX", question.getGridOffsetX()); - q.put("gridOffsetY", question.getGridOffsetY()); - q.put("gridZoomLvl", question.getGridZoomLvl()); - q.put("gridSizeX", question.getGridSizeX()); - q.put("gridSizeY", question.getGridSizeY()); - q.put("gridIsHidden", question.getGridIsHidden()); - q.put("imgRotation", question.getImgRotation()); - q.put("toggleFieldsLeft", question.getToggleFieldsLeft()); - q.put("numClickableFields", question.getNumClickableFields()); - q.put("thresholdCorrectAnswers", question.getThresholdCorrectAnswers()); - q.put("cvIsColored", question.getCvIsColored()); - q.put("gridLineColor", question.getGridLineColor()); - q.put("numberOfDots", question.getNumberOfDots()); - q.put("gridType", question.getGridType()); - q.put("scaleFactor", question.getScaleFactor()); - q.put("gridScaleFactor", question.getGridScaleFactor()); - q.put("imageQuestion", question.isImageQuestion()); - q.put("textAnswerEnabled", question.isTextAnswerEnabled()); - q.put("timestamp", question.getTimestamp()); - q.put("hint", question.getHint()); - q.put("solution", question.getSolution()); - return q; - } - - /* TODO: Only evict cache entry for the question's session. This requires some refactoring. */ - @Caching(evict = {@CacheEvict(value = "skillquestions", allEntries = true), - @CacheEvict(value = "lecturequestions", allEntries = true, condition = "#question.getQuestionVariant().equals('lecture')"), - @CacheEvict(value = "preparationquestions", allEntries = true, condition = "#question.getQuestionVariant().equals('preparation')"), - @CacheEvict(value = "flashcardquestions", allEntries = true, condition = "#question.getQuestionVariant().equals('flashcard')") }, - put = {@CachePut(value = "questions", key = "#question._id")}) - @Override - public Question updateQuestion(final Question question) { - try { - final Document q = database.getDocument(question.get_id()); - - question.updateRoundManagementState(); - q.put("subject", question.getSubject()); - q.put("text", question.getText()); - q.put("active", question.isActive()); - q.put("votingDisabled", question.isVotingDisabled()); - q.put("releasedFor", question.getReleasedFor()); - q.put("possibleAnswers", question.getPossibleAnswers()); - q.put("noCorrect", question.isNoCorrect()); - q.put("piRound", question.getPiRound()); - q.put("piRoundStartTime", question.getPiRoundStartTime()); - q.put("piRoundEndTime", question.getPiRoundEndTime()); - q.put("piRoundFinished", question.isPiRoundFinished()); - q.put("piRoundActive", question.isPiRoundActive()); - q.put("showStatistic", question.isShowStatistic()); - q.put("ignoreCaseSensitive", question.isIgnoreCaseSensitive()); - q.put("ignoreWhitespaces", question.isIgnoreWhitespaces()); - q.put("ignorePunctuation", question.isIgnorePunctuation()); - q.put("fixedAnswer", question.isFixedAnswer()); - q.put("strictMode", question.isStrictMode()); - q.put("rating", question.getRating()); - q.put("correctAnswer", question.getCorrectAnswer()); - q.put("showAnswer", question.isShowAnswer()); - q.put("abstention", question.isAbstention()); - q.put("image", question.getImage()); - q.put("fcImage", question.getFcImage()); - q.put("gridSize", question.getGridSize()); - q.put("offsetX", question.getOffsetX()); - q.put("offsetY", question.getOffsetY()); - q.put("zoomLvl", question.getZoomLvl()); - q.put("gridOffsetX", question.getGridOffsetX()); - q.put("gridOffsetY", question.getGridOffsetY()); - q.put("gridZoomLvl", question.getGridZoomLvl()); - q.put("gridSizeX", question.getGridSizeX()); - q.put("gridSizeY", question.getGridSizeY()); - q.put("gridIsHidden", question.getGridIsHidden()); - q.put("imgRotation", question.getImgRotation()); - q.put("toggleFieldsLeft", question.getToggleFieldsLeft()); - q.put("numClickableFields", question.getNumClickableFields()); - q.put("thresholdCorrectAnswers", question.getThresholdCorrectAnswers()); - q.put("cvIsColored", question.getCvIsColored()); - q.put("gridLineColor", question.getGridLineColor()); - q.put("numberOfDots", question.getNumberOfDots()); - q.put("gridType", question.getGridType()); - q.put("scaleFactor", question.getScaleFactor()); - q.put("gridScaleFactor", question.getGridScaleFactor()); - q.put("imageQuestion", question.isImageQuestion()); - q.put("hint", question.getHint()); - q.put("solution", question.getSolution()); - - database.saveDocument(q); - question.set_rev(q.getRev()); - - return question; - } catch (final IOException e) { - logger.error("Could not update question {}.", question, e); - } - - return null; - } - - @Override - public InterposedQuestion saveQuestion(final Session session, final InterposedQuestion question, User user) { - final Document q = new Document(); - q.put("type", "interposed_question"); - q.put("sessionId", session.get_id()); - q.put("subject", question.getSubject()); - q.put("text", question.getText()); - if (question.getTimestamp() != 0) { - q.put("timestamp", question.getTimestamp()); - } else { - q.put("timestamp", System.currentTimeMillis()); - } - q.put("read", false); - q.put("creator", user.getUsername()); - try { - database.saveDocument(q); - question.set_id(q.getId()); - question.set_rev(q.getRev()); - - return question; - } catch (final IOException e) { - logger.error("Could not save interposed question {}.", question, e); - } - - return null; - } - - @Cacheable("questions") - @Override - public Question getQuestion(final String id) { - try { - final Document q = getDatabase().getDocument(id); - if (q == null) { - return null; - } - final Question question = (Question) JSONObject.toBean(q.getJSONObject(), Question.class); - final JSONArray possibleAnswers = q.getJSONObject().getJSONArray("possibleAnswers"); - @SuppressWarnings("unchecked") - final Collection<PossibleAnswer> answers = JSONArray.toCollection(possibleAnswers, PossibleAnswer.class); - - question.updateRoundManagementState(); - question.setPossibleAnswers(new ArrayList<>(answers)); - question.setSessionKeyword(getSessionKeyword(question.getSessionId())); - return question; - } catch (final IOException e) { - logger.error("Could not get question {}.", id, e); - } - return null; - } - - @Override - public LoggedIn registerAsOnlineUser(final User user, final Session session) { - try { - final View view = new View("logged_in/all"); - view.setKey(user.getUsername()); - final ViewResults results = getDatabase().view(view); - - LoggedIn loggedIn = new LoggedIn(); - if (results.getJSONArray("rows").optJSONObject(0) != null) { - final JSONObject json = results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"); - loggedIn = (LoggedIn) JSONObject.toBean(json, LoggedIn.class); - final JSONArray vs = json.optJSONArray("visitedSessions"); - if (vs != null) { - @SuppressWarnings("unchecked") - final Collection<VisitedSession> visitedSessions = JSONArray.toCollection(vs, VisitedSession.class); - loggedIn.setVisitedSessions(new ArrayList<>(visitedSessions)); - } - - /* Do not clutter CouchDB. Only update once every 3 hours per session. */ - if (loggedIn.getSessionId().equals(session.get_id()) && loggedIn.getTimestamp() > System.currentTimeMillis() - 3 * 3600000) { - return loggedIn; - } - } - - loggedIn.setUser(user.getUsername()); - loggedIn.setSessionId(session.get_id()); - loggedIn.addVisitedSession(session); - loggedIn.updateTimestamp(); - - final JSONObject json = JSONObject.fromObject(loggedIn); - final Document doc = new Document(json); - if (doc.getId().isEmpty()) { - // If this is a new user without a logged_in document, we have - // to remove the following - // pre-filled fields. Otherwise, CouchDB will take these empty - // fields as genuine - // identifiers, and will throw errors afterwards. - doc.remove("_id"); - doc.remove("_rev"); - } - getDatabase().saveDocument(doc); - - final LoggedIn l = (LoggedIn) JSONObject.toBean(doc.getJSONObject(), LoggedIn.class); - final JSONArray vs = doc.getJSONObject().optJSONArray("visitedSessions"); - if (vs != null) { - @SuppressWarnings("unchecked") - final Collection<VisitedSession> visitedSessions = JSONArray.toCollection(vs, VisitedSession.class); - l.setVisitedSessions(new ArrayList<>(visitedSessions)); - } - return l; - } catch (final IOException e) { - return null; - } - } - - @Override - @CachePut(value = "sessions") - public Session updateSessionOwnerActivity(final Session session) { - try { - /* Do not clutter CouchDB. Only update once every 3 hours. */ - if (session.getLastOwnerActivity() > System.currentTimeMillis() - 3 * 3600000) { - return session; - } - - session.setLastOwnerActivity(System.currentTimeMillis()); - final JSONObject json = JSONObject.fromObject(session); - getDatabase().saveDocument(new Document(json)); - return session; - } catch (final IOException e) { - logger.error("Failed to update lastOwnerActivity for session {}.", session, e); - return session; - } - } - - @Override - public List<String> getQuestionIds(final Session session, final User user) { - View view = new View("content/by_sessionid_variant_active"); - view.setKey(session.get_id()); - return collectQuestionIds(view); - } - - /* TODO: Only evict cache entry for the question's session. This requires some refactoring. */ - @Caching(evict = { @CacheEvict(value = "questions", key = "#question._id"), - @CacheEvict(value = "skillquestions", allEntries = true), - @CacheEvict(value = "lecturequestions", allEntries = true, condition = "#question.getQuestionVariant().equals('lecture')"), - @CacheEvict(value = "preparationquestions", allEntries = true, condition = "#question.getQuestionVariant().equals('preparation')"), - @CacheEvict(value = "flashcardquestions", allEntries = true, condition = "#question.getQuestionVariant().equals('flashcard')") }) - @Override - public int deleteQuestionWithAnswers(final Question question) { - try { - int count = deleteAnswers(question); - deleteDocument(question.get_id()); - log("delete", "type", "question", "answerCount", count); - - return count; - } catch (final IOException e) { - logger.error("Could not delete question {}.", question.get_id(), e); - } - - return 0; - } - - @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), - @CacheEvict(value = "skillquestions", key = "#session"), - @CacheEvict(value = "lecturequestions", key = "#session"), - @CacheEvict(value = "preparationquestions", key = "#session"), - @CacheEvict(value = "flashcardquestions", key = "#session") }) - @Override - public int[] deleteAllQuestionsWithAnswers(final Session session) { - final View view = new View("content/by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id()); - view.setEndKey(session.get_id(), "{}"); - - return deleteAllQuestionDocumentsWithAnswers(view); - } - - private int[] deleteAllQuestionDocumentsWithAnswers(final View view) { - final ViewResults results = getDatabase().view(view); - - List<Question> questions = new ArrayList<>(); - for (final Document d : results.getResults()) { - final Question q = new Question(); - q.set_id(d.getId()); - q.set_rev(d.getString("value")); - questions.add(q); - } - - int[] count = deleteAllAnswersWithQuestions(questions); - log("delete", "type", "question", "questionCount", count[0]); - log("delete", "type", "answer", "answerCount", count[1]); - - return count; - } - - private void deleteDocument(final String documentId) throws IOException { - final Document d = getDatabase().getDocument(documentId); - getDatabase().deleteDocument(d); - } - - @CacheEvict("answers") - @Override - public int deleteAnswers(final Question question) { - try { - final View view = new View("answer/by_questionid"); - view.setKey(question.get_id()); - view.setIncludeDocs(true); - final ViewResults results = getDatabase().view(view); - final List<List<Document>> partitions = Lists.partition(results.getResults(), BULK_PARTITION_SIZE); - - int count = 0; - for (List<Document> partition: partitions) { - List<Document> answersToDelete = new ArrayList<>(); - for (final Document a : partition) { - final Document d = new Document(a.getJSONObject("doc")); - d.put("_deleted", true); - answersToDelete.add(d); - } - if (database.bulkSaveDocuments(answersToDelete.toArray(new Document[answersToDelete.size()]))) { - count += partition.size(); - } else { - logger.error("Could not bulk delete answers."); - } - } - log("delete", "type", "answer", "answerCount", count); - - return count; - } catch (final IOException e) { - logger.error("Could not delete answers for question {}.", question.get_id(), e); - } - - return 0; - } - - @Override - public List<String> getUnAnsweredQuestionIds(final Session session, final User user) { - final View view = new View("answer/questionid_by_user_sessionid_variant"); - view.setStartKeyArray(user.getUsername(), session.get_id()); - view.setEndKeyArray(user.getUsername(), session.get_id(), "{}"); - return collectUnansweredQuestionIds(getQuestionIds(session, user), view); - } - - @Override - public Answer getMyAnswer(final User me, final String questionId, final int piRound) { - - final View view = new View("answer/doc_by_questionid_user_piround"); - if (2 == piRound) { - view.setKey(questionId, me.getUsername(), "2"); - } else { - /* needed for legacy questions whose piRound property has not been set */ - view.setStartKey(questionId, me.getUsername()); - view.setEndKey(questionId, me.getUsername(), "1"); - } - final ViewResults results = getDatabase().view(view); - if (results.getResults().isEmpty()) { - return null; - } - return (Answer) JSONObject.toBean( - results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), - Answer.class - ); - } - - @SuppressWarnings("unchecked") - @Override - public <T> T getObjectFromId(final String documentId, final Class<T> klass) { - try { - final Document doc = getDatabase().getDocument(documentId); - if (doc == null) { - return null; - } - // TODO: This needs some more error checking... - return (T) JSONObject.toBean(doc.getJSONObject(), klass); - } catch (ClassCastException | IOException | net.sf.json.JSONException e) { - return null; - } - } - - @Override - public List<Answer> getAnswers(final Question question, final int piRound) { - final String questionId = question.get_id(); - final View view = new View("answer/by_questionid_piround_text_subject"); - if (2 == piRound) { - view.setStartKey(questionId, 2); - view.setEndKey(questionId, 2, "{}"); - } else { - /* needed for legacy questions whose piRound property has not been set */ - view.setStartKeyArray(questionId); - view.setEndKeyArray(questionId, 1, "{}"); - } - view.setGroup(true); - final ViewResults results = getDatabase().view(view); - final int abstentionCount = getDatabaseDao().getAbstentionAnswerCount(questionId); - final List<Answer> answers = new ArrayList<>(); - - for (final Document d : results.getResults()) { - final Answer a = new Answer(); - a.setAnswerCount(d.getInt("value")); - a.setAbstentionCount(abstentionCount); - a.setQuestionId(d.getJSONObject().getJSONArray("key").getString(0)); - a.setPiRound(piRound); - final String answerText = d.getJSONObject().getJSONArray("key").getString(3); - a.setAnswerText("null".equals(answerText) ? null : answerText); - answers.add(a); - } - return answers; - } - - @Override - public List<Answer> getAllAnswers(final Question question) { - final String questionId = question.get_id(); - final View view = new View("answer/by_questionid_piround_text_subject"); - view.setStartKeyArray(questionId); - view.setEndKeyArray(questionId, "{}"); - view.setGroup(true); - final ViewResults results = getDatabase().view(view); - final int abstentionCount = getDatabaseDao().getAbstentionAnswerCount(questionId); - - final List<Answer> answers = new ArrayList<>(); - for (final Document d : results.getResults()) { - final Answer a = new Answer(); - a.setAnswerCount(d.getInt("value")); - a.setAbstentionCount(abstentionCount); - a.setQuestionId(d.getJSONObject().getJSONArray("key").getString(0)); - final String answerText = d.getJSONObject().getJSONArray("key").getString(3); - final String answerSubject = d.getJSONObject().getJSONArray("key").getString(4); - final boolean successfulFreeTextAnswer = d.getJSONObject().getJSONArray("key").getBoolean(5); - a.setAnswerText("null".equals(answerText) ? null : answerText); - a.setAnswerSubject("null".equals(answerSubject) ? null : answerSubject); - a.setSuccessfulFreeTextAnswer(successfulFreeTextAnswer); - answers.add(a); - } - return answers; - } - - @Cacheable("answers") - @Override - public List<Answer> getAnswers(final Question question) { - return this.getAnswers(question, question.getPiRound()); - } - - @Override - public int getAbstentionAnswerCount(final String questionId) { - final View view = new View("answer/by_questionid_piround_text_subject"); - view.setStartKeyArray(questionId); - view.setEndKeyArray(questionId, "{}"); - view.setGroup(true); - final ViewResults results = getDatabase().view(view); - if (results.getResults().isEmpty()) { - return 0; - } - return results.getJSONArray("rows").optJSONObject(0).optInt("value"); - } - - @Override - public int getAnswerCount(final Question question, final int piRound) { - final View view = new View("answer/by_questionid_piround_text_subject"); - view.setStartKey(question.get_id(), piRound); - view.setEndKey(question.get_id(), piRound, "{}"); - view.setGroup(true); - final ViewResults results = getDatabase().view(view); - if (results.getResults().isEmpty()) { - return 0; - } - - return results.getJSONArray("rows").optJSONObject(0).optInt("value"); - } - - @Override - public int getTotalAnswerCountByQuestion(final Question question) { - final View view = new View("answer/by_questionid_piround_text_subject"); - view.setStartKeyArray(question.get_id()); - view.setEndKeyArray(question.get_id(), "{}"); - view.setGroup(true); - final ViewResults results = getDatabase().view(view); - - if (results.getResults().isEmpty()) { - return 0; - } - - return results.getJSONArray("rows").optJSONObject(0).optInt("value"); - } - - private boolean isEmptyResults(final ViewResults results) { - return results == null || results.getResults().isEmpty() || results.getJSONArray("rows").isEmpty(); - } - - @Override - public List<Answer> getFreetextAnswers(final String questionId, final int start, final int limit) { - final List<Answer> answers = new ArrayList<>(); - final View view = new View("answer/doc_by_questionid_timestamp"); - if (start > 0) { - view.setSkip(start); - } - if (limit > 0) { - view.setLimit(limit); - } - view.setDescending(true); - view.setStartKeyArray(questionId, "{}"); - view.setEndKeyArray(questionId); - final ViewResults results = getDatabase().view(view); - if (results.getResults().isEmpty()) { - return answers; - } - for (final Document d : results.getResults()) { - final Answer a = (Answer) JSONObject.toBean(d.getJSONObject().getJSONObject("value"), Answer.class); - a.setQuestionId(questionId); - answers.add(a); - } - return answers; - } - - @Override - public List<Answer> getMyAnswers(final User me, final Session s) { - final View view = new View("answer/doc_by_user_sessionid"); - view.setKey(me.getUsername(), s.get_id()); - final ViewResults results = getDatabase().view(view); - final List<Answer> answers = new ArrayList<>(); - if (results == null || results.getResults() == null || results.getResults().isEmpty()) { - return answers; - } - for (final Document d : results.getResults()) { - final Answer a = (Answer) JSONObject.toBean(d.getJSONObject().getJSONObject("value"), Answer.class); - a.set_id(d.getId()); - a.set_rev(d.getRev()); - a.setUser(me.getUsername()); - a.setSessionId(s.get_id()); - answers.add(a); - } - return answers; - } - - @Override - public int getTotalAnswerCount(final String sessionKey) { - final Session s = getDatabaseDao().getSessionFromKeyword(sessionKey); - if (s == null) { - throw new NotFoundException(); - } - - final View view = new View("answer/by_sessionid_variant"); - view.setKey(s.get_id()); - final ViewResults results = getDatabase().view(view); - if (results.getResults().isEmpty()) { - return 0; - } - return results.getJSONArray("rows").optJSONObject(0).optInt("value"); - } - - @Override - public int getInterposedCount(final String sessionKey) { - final Session s = getDatabaseDao().getSessionFromKeyword(sessionKey); - if (s == null) { - throw new NotFoundException(); - } - - final View view = new View("comment/by_sessionid"); - view.setKey(s.get_id()); - view.setGroup(true); - final ViewResults results = getDatabase().view(view); - if (results.isEmpty() || results.getResults().isEmpty()) { - return 0; - } - return results.getJSONArray("rows").optJSONObject(0).optInt("value"); - } - - @Override - public InterposedReadingCount getInterposedReadingCount(final Session session) { - final View view = new View("comment/by_sessionid_read"); - view.setStartKeyArray(session.get_id()); - view.setEndKeyArray(session.get_id(), "{}"); - view.setGroup(true); - return getInterposedReadingCount(view); - } - - @Override - public InterposedReadingCount getInterposedReadingCount(final Session session, final User user) { - final View view = new View("comment/by_sessionid_creator_read"); - view.setStartKeyArray(session.get_id(), user.getUsername()); - view.setEndKeyArray(session.get_id(), user.getUsername(), "{}"); - view.setGroup(true); - return getInterposedReadingCount(view); - } - - private InterposedReadingCount getInterposedReadingCount(final View view) { - final ViewResults results = getDatabase().view(view); - if (results.isEmpty() || results.getResults().isEmpty()) { - return new InterposedReadingCount(); - } - // A complete result looks like this. Note that the second row is optional, and that the first one may be - // 'unread' or 'read', i.e., results may be switched around or only one result may be present. - // count = {"rows":[ - // {"key":["cecebabb21b096e592d81f9c1322b877","Guestc9350cf4a3","read"],"value":1}, - // {"key":["cecebabb21b096e592d81f9c1322b877","Guestc9350cf4a3","unread"],"value":1} - // ]} - int read = 0, unread = 0; - boolean isRead = false; - final JSONObject fst = results.getJSONArray("rows").getJSONObject(0); - final JSONObject snd = results.getJSONArray("rows").optJSONObject(1); - - final JSONArray fstkey = fst.getJSONArray("key"); - if (fstkey.size() == 2) { - isRead = fstkey.getBoolean(1); - } else if (fstkey.size() == 3) { - isRead = fstkey.getBoolean(2); - } - if (isRead) { - read = fst.optInt("value"); - } else { - unread = fst.optInt("value"); - } - - if (snd != null) { - final JSONArray sndkey = snd.getJSONArray("key"); - if (sndkey.size() == 2) { - isRead = sndkey.getBoolean(1); - } else { - isRead = sndkey.getBoolean(2); - } - if (isRead) { - read = snd.optInt("value"); - } else { - unread = snd.optInt("value"); - } - } - return new InterposedReadingCount(read, unread); - } - - @Override - public List<InterposedQuestion> getInterposedQuestions(final Session session, final int start, final int limit) { - final View view = new View("comment/doc_by_sessionid_timestamp"); - if (start > 0) { - view.setSkip(start); - } - if (limit > 0) { - view.setLimit(limit); - } - view.setDescending(true); - view.setStartKeyArray(session.get_id(), "{}"); - view.setEndKeyArray(session.get_id()); - final ViewResults questions = getDatabase().view(view); - if (questions == null || questions.isEmpty()) { - return null; - } - return createInterposedList(session, questions); - } - - @Override - public List<InterposedQuestion> getInterposedQuestions(final Session session, final User user, final int start, final int limit) { - final View view = new View("comment/doc_by_sessionid_creator_timestamp"); - if (start > 0) { - view.setSkip(start); - } - if (limit > 0) { - view.setLimit(limit); - } - view.setDescending(true); - view.setStartKeyArray(session.get_id(), user.getUsername(), "{}"); - view.setEndKeyArray(session.get_id(), user.getUsername()); - final ViewResults questions = getDatabase().view(view); - if (questions == null || questions.isEmpty()) { - return null; - } - return createInterposedList(session, questions); - } - - private List<InterposedQuestion> createInterposedList( - final Session session, final ViewResults questions) { - final List<InterposedQuestion> result = new ArrayList<>(); - for (final Document document : questions.getResults()) { - final InterposedQuestion question = (InterposedQuestion) JSONObject.toBean( - document.getJSONObject().getJSONObject("value"), - InterposedQuestion.class - ); - question.setSessionId(session.getKeyword()); - question.set_id(document.getId()); - result.add(question); - } - return result; - } - - @Cacheable("statistics") - @Override - public Statistics getStatistics() { - final Statistics stats = new Statistics(); - try { - final View statsView = new View("statistics/statistics"); - final View creatorView = new View("statistics/unique_session_creators"); - final View studentUserView = new View("statistics/active_student_users"); - statsView.setGroup(true); - creatorView.setGroup(true); - studentUserView.setGroup(true); - - final ViewResults statsResults = getDatabase().view(statsView); - final ViewResults creatorResults = getDatabase().view(creatorView); - final ViewResults studentUserResults = getDatabase().view(studentUserView); - - if (!isEmptyResults(statsResults)) { - final JSONArray rows = statsResults.getJSONArray("rows"); - for (int i = 0; i < rows.size(); i++) { - final JSONObject row = rows.getJSONObject(i); - final int value = row.getInt("value"); - switch (row.getString("key")) { - case "openSessions": - stats.setOpenSessions(stats.getOpenSessions() + value); - break; - case "closedSessions": - stats.setClosedSessions(stats.getClosedSessions() + value); - break; - case "deletedSessions": - /* Deleted sessions are not exposed separately for now. */ - stats.setClosedSessions(stats.getClosedSessions() + value); - break; - case "answers": - stats.setAnswers(stats.getAnswers() + value); - break; - case "lectureQuestions": - stats.setLectureQuestions(stats.getLectureQuestions() + value); - break; - case "preparationQuestions": - stats.setPreparationQuestions(stats.getPreparationQuestions() + value); - break; - case "interposedQuestions": - stats.setInterposedQuestions(stats.getInterposedQuestions() + value); - break; - case "conceptQuestions": - stats.setConceptQuestions(stats.getConceptQuestions() + value); - break; - case "flashcards": - stats.setFlashcards(stats.getFlashcards() + value); - break; - } - } - } - if (!isEmptyResults(creatorResults)) { - final JSONArray rows = creatorResults.getJSONArray("rows"); - Set<String> creators = new HashSet<>(); - for (int i = 0; i < rows.size(); i++) { - final JSONObject row = rows.getJSONObject(i); - creators.add(row.getString("key")); - } - stats.setCreators(creators.size()); - } - if (!isEmptyResults(studentUserResults)) { - final JSONArray rows = studentUserResults.getJSONArray("rows"); - Set<String> students = new HashSet<>(); - for (int i = 0; i < rows.size(); i++) { - final JSONObject row = rows.getJSONObject(i); - students.add(row.getString("key")); - } - stats.setActiveStudents(students.size()); - } - return stats; - } catch (final Exception e) { - logger.error("Could not retrieve session count.", e); - } - return stats; - } - - @Override - public InterposedQuestion getInterposedQuestion(final String questionId) { - try { - final Document document = getDatabase().getDocument(questionId); - final InterposedQuestion question = (InterposedQuestion) JSONObject.toBean(document.getJSONObject(), - InterposedQuestion.class); - question.setSessionId(getSessionKeyword(question.getSessionId())); - return question; - } catch (final IOException e) { - logger.error("Could not load interposed question {}.", questionId, e); - } - return null; - } - - @Override - public void markInterposedQuestionAsRead(final InterposedQuestion question) { - try { - question.setRead(true); - final Document document = getDatabase().getDocument(question.get_id()); - document.put("read", question.isRead()); - getDatabase().saveDocument(document); - } catch (final IOException e) { - logger.error("Could not mark interposed question as read {}.", question.get_id(), e); - } - } - - @Override - public List<Session> getMyVisitedSessions(final User user, final int start, final int limit) { - final View view = new View("logged_in/visited_sessions_by_user"); - if (start > 0) { - view.setSkip(start); - } - if (limit > 0) { - view.setLimit(limit); - } - view.setKey(user.getUsername()); - final ViewResults sessions = getDatabase().view(view); - final List<Session> allSessions = new ArrayList<>(); - for (final Document d : sessions.getResults()) { - // Not all users have visited sessions - if (d.getJSONObject().optJSONArray("value") != null) { - @SuppressWarnings("unchecked") - final Collection<Session> visitedSessions = JSONArray.toCollection( - d.getJSONObject().getJSONArray("value"), - Session.class - ); - allSessions.addAll(visitedSessions); - } - } - // Filter sessions that don't exist anymore, also filter my own sessions - final List<Session> result = new ArrayList<>(); - final List<Session> filteredSessions = new ArrayList<>(); - for (final Session s : allSessions) { - try { - final Session session = getDatabaseDao().getSessionFromKeyword(s.getKeyword()); - if (session != null && !session.isCreator(user)) { - result.add(session); - } else { - filteredSessions.add(s); - } - } catch (final NotFoundException e) { - filteredSessions.add(s); - } - } - if (filteredSessions.isEmpty()) { - return result; - } - // Update document to remove sessions that don't exist anymore - try { - List<VisitedSession> visitedSessions = new ArrayList<>(); - for (final Session s : result) { - visitedSessions.add(new VisitedSession(s)); - } - final LoggedIn loggedIn = new LoggedIn(); - final Document loggedInDocument = getDatabase().getDocument(sessions.getResults().get(0).getString("id")); - loggedIn.setSessionId(loggedInDocument.getString("sessionId")); - loggedIn.setUser(user.getUsername()); - loggedIn.setTimestamp(loggedInDocument.getLong("timestamp")); - loggedIn.setType(loggedInDocument.getString("type")); - loggedIn.setVisitedSessions(visitedSessions); - loggedIn.set_id(loggedInDocument.getId()); - loggedIn.set_rev(loggedInDocument.getRev()); - - final JSONObject json = JSONObject.fromObject(loggedIn); - final Document doc = new Document(json); - getDatabase().saveDocument(doc); - } catch (IOException e) { - logger.error("Could not clean up logged_in document of {}.", user.getUsername(), e); - } - return result; - } - - @Override - public List<Session> getVisitedSessionsForUsername(String username, final int start, final int limit) { - final View view = new View("logged_in/visited_sessions_by_user"); - if (start > 0) { - view.setSkip(start); - } - if (limit > 0) { - view.setLimit(limit); - } - view.setKey(username); - final ViewResults sessions = getDatabase().view(view); - final List<Session> allSessions = new ArrayList<>(); - for (final Document d : sessions.getResults()) { - // Not all users have visited sessions - if (d.getJSONObject().optJSONArray("value") != null) { - @SuppressWarnings("unchecked") - final Collection<Session> visitedSessions = JSONArray.toCollection( - d.getJSONObject().getJSONArray("value"), - Session.class - ); - allSessions.addAll(visitedSessions); - } - } - // Filter sessions that don't exist anymore, also filter my own sessions - final List<Session> result = new ArrayList<>(); - final List<Session> filteredSessions = new ArrayList<>(); - for (final Session s : allSessions) { - try { - final Session session = getDatabaseDao().getSessionFromKeyword(s.getKeyword()); - if (session != null && !(session.getCreator().equals(username))) { - result.add(session); - } else { - filteredSessions.add(s); - } - } catch (final NotFoundException e) { - filteredSessions.add(s); - } - } - if (filteredSessions.isEmpty()) { - return result; - } - // Update document to remove sessions that don't exist anymore - try { - List<VisitedSession> visitedSessions = new ArrayList<>(); - for (final Session s : result) { - visitedSessions.add(new VisitedSession(s)); - } - final LoggedIn loggedIn = new LoggedIn(); - final Document loggedInDocument = getDatabase().getDocument(sessions.getResults().get(0).getString("id")); - loggedIn.setSessionId(loggedInDocument.getString("sessionId")); - loggedIn.setUser(username); - loggedIn.setTimestamp(loggedInDocument.getLong("timestamp")); - loggedIn.setType(loggedInDocument.getString("type")); - loggedIn.setVisitedSessions(visitedSessions); - loggedIn.set_id(loggedInDocument.getId()); - loggedIn.set_rev(loggedInDocument.getRev()); - - final JSONObject json = JSONObject.fromObject(loggedIn); - final Document doc = new Document(json); - getDatabase().saveDocument(doc); - } catch (IOException e) { - logger.error("Could not clean up logged_in document of {}.", username, e); - } - return result; - } - - @Override - public List<SessionInfo> getMyVisitedSessionsInfo(final User user, final int start, final int limit) { - List<Session> sessions = this.getMyVisitedSessions(user, start, limit); - if (sessions.isEmpty()) { - return new ArrayList<>(); - } - return this.getInfosForVisitedSessions(sessions, user); - } - - @CacheEvict(value = "answers", key = "#question") - @Override - public Answer saveAnswer(final Answer answer, final User user, final Question question, final Session session) { - final Document a = new Document(); - a.put("type", "skill_question_answer"); - a.put("sessionId", answer.getSessionId()); - a.put("questionId", answer.getQuestionId()); - a.put("answerSubject", answer.getAnswerSubject()); - a.put("questionVariant", answer.getQuestionVariant()); - a.put("questionValue", answer.getQuestionValue()); - a.put("answerText", answer.getAnswerText()); - a.put("answerTextRaw", answer.getAnswerTextRaw()); - a.put("successfulFreeTextAnswer", answer.isSuccessfulFreeTextAnswer()); - a.put("timestamp", answer.getTimestamp()); - a.put("user", user.getUsername()); - a.put("piRound", answer.getPiRound()); - a.put("abstention", answer.isAbstention()); - a.put("answerImage", answer.getAnswerImage()); - a.put("answerThumbnailImage", answer.getAnswerThumbnailImage()); - AnswerQueueElement answerQueueElement = new AnswerQueueElement(session, question, answer, user); - this.answerQueue.offer(new AbstractMap.SimpleEntry<>(a, answerQueueElement)); - return answer; - } - - @Scheduled(fixedDelay = 5000) - public void flushAnswerQueue() { - final Map<Document, Answer> map = new HashMap<>(); - final List<Document> answerList = new ArrayList<>(); - final List<AnswerQueueElement> elements = new ArrayList<>(); - AbstractMap.SimpleEntry<Document, AnswerQueueElement> entry; - while ((entry = this.answerQueue.poll()) != null) { - final Document doc = entry.getKey(); - final Answer answer = entry.getValue().getAnswer(); - map.put(doc, answer); - answerList.add(doc); - elements.add(entry.getValue()); - } - if (answerList.isEmpty()) { - // no need to send an empty bulk request. ;-) - return; - } - try { - getDatabase().bulkSaveDocuments(answerList.toArray(new Document[answerList.size()])); - for (Document d : answerList) { - final Answer answer = map.get(d); - answer.set_id(d.getId()); - answer.set_rev(d.getRev()); - } - // Send NewAnswerEvents ... - for (AnswerQueueElement e : elements) { - this.publisher.publishEvent(new NewAnswerEvent(this, e.getSession(), e.getAnswer(), e.getUser(), e.getQuestion())); - } - } catch (IOException e) { - logger.error("Could not bulk save answers from queue.", e); - } - } - - /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ - @CacheEvict(value = "answers", allEntries = true) - @Override - public Answer updateAnswer(final Answer answer) { - try { - final Document a = database.getDocument(answer.get_id()); - a.put("answerSubject", answer.getAnswerSubject()); - a.put("answerText", answer.getAnswerText()); - a.put("answerTextRaw", answer.getAnswerTextRaw()); - a.put("successfulFreeTextAnswer", answer.isSuccessfulFreeTextAnswer()); - a.put("timestamp", answer.getTimestamp()); - a.put("abstention", answer.isAbstention()); - a.put("questionValue", answer.getQuestionValue()); - a.put("answerImage", answer.getAnswerImage()); - a.put("answerThumbnailImage", answer.getAnswerThumbnailImage()); - a.put("read", answer.isRead()); - database.saveDocument(a); - answer.set_rev(a.getRev()); - return answer; - } catch (final IOException e) { - logger.error("Could not update answer {}.", answer, e); - } - return null; - } - - /* TODO: Only evict cache entry for the answer's session. This requires some refactoring. */ - @CacheEvict(value = "answers", allEntries = true) - @Override - public void deleteAnswer(final String answerId) { - try { - database.deleteDocument(database.getDocument(answerId)); - log("delete", "type", "answer"); - } catch (final IOException e) { - logger.error("Could not delete answer {}.", answerId, e); - } - } - - @Override - public void deleteInterposedQuestion(final InterposedQuestion question) { - try { - deleteDocument(question.get_id()); - log("delete", "type", "comment"); - } catch (final IOException e) { - logger.error("Could not delete interposed question {}.", question.get_id(), e); - } - } - - @Override - public List<Session> getCourseSessions(final List<Course> courses) { - final ExtendedView view = new ExtendedView("session/by_courseid"); - view.setIncludeDocs(true); - view.setCourseIdKeys(courses); - - final ViewResults sessions = getDatabase().view(view); - - final List<Session> result = new ArrayList<>(); - for (final Document d : sessions.getResults()) { - final Session session = (Session) JSONObject.toBean( - d.getJSONObject().getJSONObject("doc"), - Session.class - ); - result.add(session); - } - return result; - } - - /** - * Adds convenience methods to CouchDB4J's view class. - */ - private static class ExtendedView extends View { - - ExtendedView(final String fullname) { - super(fullname); - } - - void setCourseIdKeys(final List<Course> courses) { - List<String> courseIds = new ArrayList<>(); - for (Course c : courses) { - courseIds.add(c.getId()); - } - setKeys(courseIds); - } - - void setSessionIdKeys(final List<Session> sessions) { - List<String> sessionIds = new ArrayList<>(); - for (Session s : sessions) { - sessionIds.add(s.get_id()); - } - setKeys(sessionIds); - } - } - - @Override - @CachePut(value = "sessions") - public Session updateSession(final Session session) { - try { - final Document s = database.getDocument(session.get_id()); - s.put("name", session.getName()); - s.put("shortName", session.getShortName()); - s.put("active", session.isActive()); - s.put("ppAuthorName", session.getPpAuthorName()); - s.put("ppAuthorMail", session.getPpAuthorMail()); - s.put("ppUniversity", session.getPpUniversity()); - s.put("ppLogo", session.getPpLogo()); - s.put("ppSubject", session.getPpSubject()); - s.put("ppLicense", session.getPpLicense()); - s.put("ppDescription", session.getPpDescription()); - s.put("ppFaculty", session.getPpFaculty()); - s.put("ppLevel", session.getPpLevel()); - s.put("learningProgressOptions", JSONObject.fromObject(session.getLearningProgressOptions())); - s.put("features", JSONObject.fromObject(session.getFeatures())); - s.put("feedbackLock", session.getFeedbackLock()); - database.saveDocument(s); - session.set_rev(s.getRev()); - - return session; - } catch (final IOException e) { - logger.error("Could not update session {}.", session, e); - } - - return null; - } - - @Override - @Caching(evict = { @CacheEvict("sessions"), @CacheEvict(cacheNames = "sessions", key = "#p0.keyword") }) - public Session changeSessionCreator(Session session, final String newCreator) { - try { - final Document s = database.getDocument(session.get_id()); - s.put("creator", newCreator); - database.saveDocument(s); - session.set_rev(s.getRev()); - } catch (final IOException e) { - logger.error("Could not update creator for session {}.", session, e); - } - - return session; - } - - @Override - @Caching(evict = { @CacheEvict("sessions"), @CacheEvict(cacheNames = "sessions", key = "#p0.keyword") }) - public int[] deleteSession(final Session session) { - int[] count = new int[] {0, 0}; - try { - count = deleteAllQuestionsWithAnswers(session); - deleteDocument(session.get_id()); - logger.debug("Deleted session document {} and related data.", session.get_id()); - log("delete", "type", "session", "id", session.get_id()); - } catch (final IOException e) { - logger.error("Could not delete session {}.", session, e); - } - - return count; - } - - @Override - public int[] deleteInactiveGuestSessions(long lastActivityBefore) { - View view = new View("session/by_lastactivity_for_guests"); - view.setEndKey(lastActivityBefore); - final List<Document> results = this.getDatabase().view(view).getResults(); - int[] count = new int[3]; - - for (Document oldDoc : results) { - Session s = new Session(); - s.set_id(oldDoc.getId()); - s.set_rev(oldDoc.getJSONObject("value").getString("_rev")); - int[] qaCount = deleteSession(s); - count[1] += qaCount[0]; - count[2] += qaCount[1]; - } - - if (!results.isEmpty()) { - logger.info("Deleted {} inactive guest sessions.", results.size()); - log("cleanup", "type", "session", "sessionCount", results.size(), "questionCount", count[1], "answerCount", count[2]); - } - count[0] = results.size(); - - return count; - } - - @Override - public int deleteInactiveGuestVisitedSessionLists(long lastActivityBefore) { - try { - View view = new View("logged_in/by_last_activity_for_guests"); - view.setEndKey(lastActivityBefore); - List<Document> results = this.getDatabase().view(view).getResults(); - - int count = 0; - List<List<Document>> partitions = Lists.partition(results, BULK_PARTITION_SIZE); - for (List<Document> partition: partitions) { - final List<Document> newDocs = new ArrayList<>(); - for (final Document oldDoc : partition) { - final Document newDoc = new Document(); - newDoc.setId(oldDoc.getId()); - newDoc.setRev(oldDoc.getJSONObject("value").getString("_rev")); - newDoc.put("_deleted", true); - newDocs.add(newDoc); - logger.debug("Marked logged_in document {} for deletion.", oldDoc.getId()); - /* Use log type 'user' since effectively the user is deleted in case of guests */ - log("delete", "type", "user", "id", oldDoc.getId()); - } - - if (!newDocs.isEmpty()) { - if (getDatabase().bulkSaveDocuments(newDocs.toArray(new Document[newDocs.size()]))) { - count += newDocs.size(); - } else { - logger.error("Could not bulk delete visited session lists."); - } - } - } - - if (count > 0) { - logger.info("Deleted {} visited session lists of inactive users.", count); - log("cleanup", "type", "visitedsessions", "count", count); - } - - return count; - } catch (IOException e) { - logger.error("Could not delete visited session lists of inactive users.", e); - } - - return 0; - } - - @Cacheable("lecturequestions") - @Override - public List<Question> getLectureQuestionsForUsers(final Session session) { - final View view = new View("content/doc_by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "lecture", true); - view.setEndKeyArray(session.get_id(), "lecture", true, "{}"); - - return getQuestions(view, session); - } - - @Override - public List<Question> getLectureQuestionsForTeachers(final Session session) { - final View view = new View("content/doc_by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "lecture"); - view.setEndKeyArray(session.get_id(), "lecture", "{}"); - - return getQuestions(view, session); - } - - @Cacheable("flashcardquestions") - @Override - public List<Question> getFlashcardsForUsers(final Session session) { - final View view = new View("content/doc_by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "flashcard", true); - view.setEndKeyArray(session.get_id(), "flashcard", true, "{}"); - - return getQuestions(view, session); - } - - @Override - public List<Question> getFlashcardsForTeachers(final Session session) { - final View view = new View("content/doc_by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "flashcard"); - view.setEndKeyArray(session.get_id(), "flashcard", "{}"); - - return getQuestions(view, session); - } - - @Cacheable("preparationquestions") - @Override - public List<Question> getPreparationQuestionsForUsers(final Session session) { - final View view = new View("content/doc_by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "preparation", true); - view.setEndKeyArray(session.get_id(), "preparation", true, "{}"); - - return getQuestions(view, session); - } - - @Override - public List<Question> getPreparationQuestionsForTeachers(final Session session) { - final View view = new View("content/doc_by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "preparation"); - view.setEndKeyArray(session.get_id(), "preparation", "{}"); - - return getQuestions(view, session); - } - - @Override - public List<Question> getAllSkillQuestions(final Session session) { - final View view = new View("content/doc_by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id()); - view.setEndKeyArray(session.get_id(), "{}"); - - return getQuestions(view, session); - } - - private List<Question> getQuestions(final View view, final Session session) { - final ViewResults viewResults = getDatabase().view(view); - if (viewResults == null || viewResults.isEmpty()) { - return null; - } - - final List<Question> questions = new ArrayList<>(); - - Results<Question> results = getDatabase().queryView(view, Question.class); - for (final RowResult<Question> row : results.getRows()) { - Question question = row.getValue(); - question.updateRoundManagementState(); - question.setSessionKeyword(session.getKeyword()); - if (!"freetext".equals(question.getQuestionType()) && 0 == question.getPiRound()) { - /* needed for legacy questions whose piRound property has not been set */ - question.setPiRound(1); - } - - questions.add(question); - } - return questions; - } - - @Override - public int getLectureQuestionCount(final Session session) { - final View view = new View("content/by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "lecture"); - view.setEndKeyArray(session.get_id(), "lecture", "{}"); - - return getQuestionCount(view); - } - - @Override - public int getFlashcardCount(final Session session) { - final View view = new View("content/by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "flashcard"); - view.setEndKeyArray(session.get_id(), "flashcard", "{}"); - - return getQuestionCount(view); - } - - @Override - public int getPreparationQuestionCount(final Session session) { - final View view = new View("content/by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "preparation"); - view.setEndKeyArray(session.get_id(), "preparation", "{}"); - - return getQuestionCount(view); - } - - private int getQuestionCount(final View view) { - view.setReduce(true); - final ViewResults results = getDatabase().view(view); - if (results.getJSONArray("rows").optJSONObject(0) == null) { - return 0; - } - return results.getJSONArray("rows").optJSONObject(0).optInt("value"); - } - - @Override - public int countLectureQuestionAnswers(final Session session) { - return countQuestionVariantAnswers(session, "lecture"); - } - - @Override - public int countPreparationQuestionAnswers(final Session session) { - return countQuestionVariantAnswers(session, "preparation"); - } - - private int countQuestionVariantAnswers(final Session session, final String variant) { - final View view = new View("answer/by_sessionid_variant"); - view.setKey(session.get_id(), variant); - view.setReduce(true); - final ViewResults results = getDatabase().view(view); - if (results.getResults().isEmpty()) { - return 0; - } - return results.getJSONArray("rows").optJSONObject(0).optInt("value"); - } - - /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ - @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), - @CacheEvict("skillquestions"), - @CacheEvict("lecturequestions"), - @CacheEvict(value = "answers", allEntries = true)}) - @Override - public int[] deleteAllLectureQuestionsWithAnswers(final Session session) { - final View view = new View("content/by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "lecture"); - view.setEndKey(session.get_id(), "lecture", "{}"); - - return deleteAllQuestionDocumentsWithAnswers(view); - } - - /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ - @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), - @CacheEvict("skillquestions"), - @CacheEvict("flashcardquestions"), - @CacheEvict(value = "answers", allEntries = true)}) - @Override - public int[] deleteAllFlashcardsWithAnswers(final Session session) { - final View view = new View("content/by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "flashcard"); - view.setEndKey(session.get_id(), "flashcard", "{}"); - - return deleteAllQuestionDocumentsWithAnswers(view); - } - - /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ - @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), - @CacheEvict("skillquestions"), - @CacheEvict("preparationquestions"), - @CacheEvict(value = "answers", allEntries = true)}) - @Override - public int[] deleteAllPreparationQuestionsWithAnswers(final Session session) { - final View view = new View("content/by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "preparation"); - view.setEndKey(session.get_id(), "preparation", "{}"); - - return deleteAllQuestionDocumentsWithAnswers(view); - } - - @Override - public List<String> getUnAnsweredLectureQuestionIds(final Session session, final User user) { - final View view = new View("answer/questionid_piround_by_user_sessionid_variant"); - view.setKey(user.getUsername(), session.get_id(), "lecture"); - return collectUnansweredQuestionIdsByPiRound(getDatabaseDao().getLectureQuestionsForUsers(session), view); - } - - @Override - public List<String> getUnAnsweredPreparationQuestionIds(final Session session, final User user) { - final View view = new View("answer/questionid_piround_by_user_sessionid_variant"); - view.setKey(user.getUsername(), session.get_id(), "preparation"); - return collectUnansweredQuestionIdsByPiRound(getDatabaseDao().getPreparationQuestionsForUsers(session), view); - } - - private List<String> collectUnansweredQuestionIds( - final List<String> questions, - final View view - ) { - final ViewResults answeredQuestions = getDatabase().view(view); - - final List<String> answered = new ArrayList<>(); - for (final Document d : answeredQuestions.getResults()) { - answered.add(d.getString("value")); - } - - final List<String> unanswered = new ArrayList<>(); - for (final String questionId : questions) { - if (!answered.contains(questionId)) { - unanswered.add(questionId); - } - } - return unanswered; - } - - private List<String> collectUnansweredQuestionIdsByPiRound( - final List<Question> questions, - final View view - ) { - final ViewResults answeredQuestions = getDatabase().view(view); - - final Map<String, Integer> answered = new HashMap<>(); - for (final Document d : answeredQuestions.getResults()) { - answered.put(d.getJSONArray("value").getString(0), d.getJSONArray("value").getInt(1)); - } - - final List<String> unanswered = new ArrayList<>(); - - for (final Question question : questions) { - if (!"slide".equals(question.getQuestionType()) && (!answered.containsKey(question.get_id()) - || (answered.containsKey(question.get_id()) && answered.get(question.get_id()) != question.getPiRound()))) { - unanswered.add(question.get_id()); - } - } - - return unanswered; - } - - private List<String> collectQuestionIds(final View view) { - final ViewResults results = getDatabase().view(view); - if (results.getResults().isEmpty()) { - return new ArrayList<>(); - } - final List<String> ids = new ArrayList<>(); - for (final Document d : results.getResults()) { - ids.add(d.getId()); - } - return ids; - } - - @Override - public int deleteAllInterposedQuestions(final Session session) { - final View view = new View("comment/by_sessionid"); - view.setKey(session.get_id()); - final ViewResults questions = getDatabase().view(view); - - return deleteAllInterposedQuestions(session, questions); - } - - @Override - public int deleteAllInterposedQuestions(final Session session, final User user) { - final View view = new View("comment/by_sessionid_creator_read"); - view.setStartKeyArray(session.get_id(), user.getUsername()); - view.setEndKeyArray(session.get_id(), user.getUsername(), "{}"); - final ViewResults questions = getDatabase().view(view); - - return deleteAllInterposedQuestions(session, questions); - } - - private int deleteAllInterposedQuestions(final Session session, final ViewResults questions) { - if (questions == null || questions.isEmpty()) { - return 0; - } - List<Document> results = questions.getResults(); - /* TODO: use bulk delete */ - for (final Document document : results) { - try { - deleteDocument(document.getId()); - } catch (final IOException e) { - logger.error("Could not delete all interposed questions {}.", session, e); - } - } - - /* This does account for failed deletions */ - log("delete", "type", "comment", "commentCount", results.size()); - - return results.size(); - } - - @Override - public List<Question> publishAllQuestions(final Session session, final boolean publish) { - final View view = new View("content/doc_by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id()); - view.setEndKeyArray(session.get_id(), "{}"); - final List<Question> questions = getQuestions(view, session); - getDatabaseDao().publishQuestions(session, publish, questions); - - return questions; - } - - @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), - @CacheEvict(value = "skillquestions", key = "#session"), - @CacheEvict(value = "lecturequestions", key = "#session"), - @CacheEvict(value = "preparationquestions", key = "#session"), - @CacheEvict(value = "flashcardquestions", key = "#session") }) - @Override - public void publishQuestions(final Session session, final boolean publish, List<Question> questions) { - for (final Question q : questions) { - q.setActive(publish); - } - final List<Document> documents = new ArrayList<>(); - for (final Question q : questions) { - final Document d = toQuestionDocument(session, q); - d.setId(q.get_id()); - d.setRev(q.get_rev()); - documents.add(d); - } - try { - database.bulkSaveDocuments(documents.toArray(new Document[documents.size()])); - } catch (final IOException e) { - logger.error("Could not bulk publish all questions.", e); - } - } - - @Override - public List<Question> setVotingAdmissionForAllQuestions(final Session session, final boolean disableVoting) { - final View view = new View("content/doc_by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id()); - view.setEndKeyArray(session.get_id(), "{}"); - final List<Question> questions = getQuestions(view, session); - getDatabaseDao().setVotingAdmissions(session, disableVoting, questions); - - return questions; - } - - @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), - @CacheEvict(value = "skillquestions", key = "#session"), - @CacheEvict(value = "lecturequestions", key = "#session"), - @CacheEvict(value = "preparationquestions", key = "#session"), - @CacheEvict(value = "flashcardquestions", key = "#session") }) - @Override - public void setVotingAdmissions(final Session session, final boolean disableVoting, List<Question> questions) { - for (final Question q : questions) { - if (!"flashcard".equals(q.getQuestionType())) { - q.setVotingDisabled(disableVoting); - } - } - final List<Document> documents = new ArrayList<>(); - for (final Question q : questions) { - final Document d = toQuestionDocument(session, q); - d.setId(q.get_id()); - d.setRev(q.get_rev()); - documents.add(d); - } - - try { - database.bulkSaveDocuments(documents.toArray(new Document[documents.size()])); - } catch (final IOException e) { - logger.error("Could not bulk set voting admission for all questions.", e); - } - } - - /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ - @CacheEvict(value = "answers", allEntries = true) - @Override - public int deleteAllQuestionsAnswers(final Session session) { - final View view = new View("content/doc_by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id()); - view.setEndKeyArray(session.get_id(), "{}"); - final List<Question> questions = getQuestions(view, session); - getDatabaseDao().resetQuestionsRoundState(session, questions); - - return deleteAllAnswersForQuestions(questions); - } - - /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ - @CacheEvict(value = "answers", allEntries = true) - @Override - public int deleteAllPreparationAnswers(final Session session) { - final View view = new View("content/doc_by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "preparation"); - view.setEndKeyArray(session.get_id(), "preparation", "{}"); - final List<Question> questions = getQuestions(view, session); - getDatabaseDao().resetQuestionsRoundState(session, questions); - - return deleteAllAnswersForQuestions(questions); - } - - /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ - @CacheEvict(value = "answers", allEntries = true) - @Override - public int deleteAllLectureAnswers(final Session session) { - final View view = new View("content/doc_by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), "lecture"); - view.setEndKeyArray(session.get_id(), "lecture", "{}"); - final List<Question> questions = getQuestions(view, session); - getDatabaseDao().resetQuestionsRoundState(session, questions); - - return deleteAllAnswersForQuestions(questions); - } - - @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), - @CacheEvict(value = "skillquestions", key = "#session"), - @CacheEvict(value = "lecturequestions", key = "#session"), - @CacheEvict(value = "preparationquestions", key = "#session"), - @CacheEvict(value = "flashcardquestions", key = "#session") }) - @Override - public void resetQuestionsRoundState(final Session session, List<Question> questions) { - for (final Question q : questions) { - q.resetQuestionState(); - } - final List<Document> documents = new ArrayList<>(); - for (final Question q : questions) { - final Document d = toQuestionDocument(session, q); - d.setId(q.get_id()); - d.setRev(q.get_rev()); - documents.add(d); - } - try { - database.bulkSaveDocuments(documents.toArray(new Document[documents.size()])); - } catch (final IOException e) { - logger.error("Could not bulk reset all questions round state.", e); - } - } - - private int deleteAllAnswersForQuestions(List<Question> questions) { - List<String> questionIds = new ArrayList<>(); - for (Question q : questions) { - questionIds.add(q.get_id()); - } - final View bulkView = new View("answer/by_questionid"); - bulkView.setKeys(questionIds); - bulkView.setIncludeDocs(true); - final List<Document> result = getDatabase().view(bulkView).getResults(); - final List<Document> allAnswers = new ArrayList<>(); - for (Document a : result) { - final Document d = new Document(a.getJSONObject("doc")); - d.put("_deleted", true); - allAnswers.add(d); - } - try { - getDatabase().bulkSaveDocuments(allAnswers.toArray(new Document[allAnswers.size()])); - - return allAnswers.size(); - } catch (IOException e) { - logger.error("Could not bulk delete answers.", e); - } - - return 0; - } - - private int[] deleteAllAnswersWithQuestions(List<Question> questions) { - List<String> questionIds = new ArrayList<>(); - final List<Document> allQuestions = new ArrayList<>(); - for (Question q : questions) { - final Document d = new Document(); - d.put("_id", q.get_id()); - d.put("_rev", q.get_rev()); - d.put("_deleted", true); - questionIds.add(q.get_id()); - allQuestions.add(d); - } - final View bulkView = new View("answer/by_questionid"); - bulkView.setKeys(questionIds); - bulkView.setIncludeDocs(true); - final List<Document> result = getDatabase().view(bulkView).getResults(); - - final List<Document> allAnswers = new ArrayList<>(); - for (Document a : result) { - final Document d = new Document(a.getJSONObject("doc")); - d.put("_deleted", true); - allAnswers.add(d); - } - - try { - List<Document> deleteList = new ArrayList<>(allAnswers); - deleteList.addAll(allQuestions); - getDatabase().bulkSaveDocuments(deleteList.toArray(new Document[deleteList.size()])); - - return new int[] {deleteList.size(), result.size()}; - } catch (IOException e) { - logger.error("Could not bulk delete questions and answers.", e); - } - - return new int[] {0, 0}; - } - - @Cacheable("learningprogress") - @Override - public CourseScore getLearningProgress(final Session session) { - final View maximumValueView = new View("learning_progress/maximum_value_of_question"); - final View answerSumView = new View("learning_progress/question_value_achieved_for_user"); - maximumValueView.setStartKeyArray(session.get_id()); - maximumValueView.setEndKeyArray(session.get_id(), "{}"); - answerSumView.setStartKeyArray(session.get_id()); - answerSumView.setEndKeyArray(session.get_id(), "{}"); - - final List<Document> maximumValueResult = getDatabase().view(maximumValueView).getResults(); - final List<Document> answerSumResult = getDatabase().view(answerSumView).getResults(); - - CourseScore courseScore = new CourseScore(); - - // no results found - if (maximumValueResult.isEmpty() && answerSumResult.isEmpty()) { - return courseScore; - } - - // collect mapping (questionId -> max value) - for (Document d : maximumValueResult) { - String questionId = d.getJSONArray("key").getString(1); - JSONObject value = d.getJSONObject("value"); - int questionScore = value.getInt("value"); - String questionVariant = value.getString("questionVariant"); - int piRound = value.getInt("piRound"); - courseScore.addQuestion(questionId, questionVariant, piRound, questionScore); - } - // collect mapping (questionId -> (user -> value)) - for (Document d : answerSumResult) { - String username = d.getJSONArray("key").getString(1); - JSONObject value = d.getJSONObject("value"); - String questionId = value.getString("questionId"); - int userscore = value.getInt("score"); - int piRound = value.getInt("piRound"); - courseScore.addAnswer(questionId, piRound, username, userscore); - } - return courseScore; - } - - @Override - public DbUser createOrUpdateUser(DbUser user) { - try { - String id = user.getId(); - String rev = user.getRev(); - Document d = new Document(); - - if (null != id) { - d = database.getDocument(id, rev); - } - - d.put("type", "userdetails"); - d.put("username", user.getUsername()); - d.put("password", user.getPassword()); - d.put("activationKey", user.getActivationKey()); - d.put("passwordResetKey", user.getPasswordResetKey()); - d.put("passwordResetTime", user.getPasswordResetTime()); - d.put("creation", user.getCreation()); - d.put("lastLogin", user.getLastLogin()); - - database.saveDocument(d, id); - user.setId(d.getId()); - user.setRev(d.getRev()); - - return user; - } catch (IOException e) { - logger.error("Could not save user {}.", user, e); - } - - return null; - } - - @Override - public DbUser getUser(String username) { - View view = new View("user/doc_by_username"); - view.setKey(username); - ViewResults results = this.getDatabase().view(view); - - if (results.getJSONArray("rows").optJSONObject(0) == null) { - return null; - } - - return (DbUser) JSONObject.toBean( - results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), - DbUser.class - ); - } - - @Override - public boolean deleteUser(final DbUser dbUser) { - try { - this.deleteDocument(dbUser.getId()); - log("delete", "type", "user", "id", dbUser.getId()); - - return true; - } catch (IOException e) { - logger.error("Could not delete user {}.", dbUser.getId(), e); - } - - return false; - } - - @Override - public int deleteInactiveUsers(long lastActivityBefore) { - try { - View view = new View("user/by_creation_for_inactive"); - view.setEndKey(lastActivityBefore); - List<Document> results = this.getDatabase().view(view).getResults(); - - int count = 0; - final List<List<Document>> partitions = Lists.partition(results, BULK_PARTITION_SIZE); - for (List<Document> partition: partitions) { - final List<Document> newDocs = new ArrayList<>(); - for (Document oldDoc : partition) { - final Document newDoc = new Document(); - newDoc.setId(oldDoc.getId()); - newDoc.setRev(oldDoc.getJSONObject("value").getString("_rev")); - newDoc.put("_deleted", true); - newDocs.add(newDoc); - logger.debug("Marked user document {} for deletion.", oldDoc.getId()); - } - - if (newDocs.size() > 0) { - if (getDatabase().bulkSaveDocuments(newDocs.toArray(new Document[newDocs.size()]))) { - count += newDocs.size(); - } - } - } - - if (count > 0) { - logger.info("Deleted {} inactive users.", count); - log("cleanup", "type", "user", "count", count); - } - - return count; - } catch (IOException e) { - logger.error("Could not delete inactive users.", e); - } - - return 0; - } - - @Override - public SessionInfo importSession(User user, ImportExportSession importSession) { - final Session session = this.saveSession(user, importSession.generateSessionEntity(user)); - List<Document> questions = new ArrayList<>(); - // We need to remember which answers belong to which question. - // The answers need a questionId, so we first store the questions to get the IDs. - // Then we update the answer objects and store them as well. - Map<Document, ImportExportQuestion> mapping = new HashMap<>(); - // Later, generate all answer documents - List<Document> answers = new ArrayList<>(); - // We can then push answers together with interposed questions in one large bulk request - List<Document> interposedQuestions = new ArrayList<>(); - // Motds shouldn't be forgotten, too - List<Document> motds = new ArrayList<>(); - try { - // add session id to all questions and generate documents - for (ImportExportQuestion question : importSession.getQuestions()) { - Document doc = toQuestionDocument(session, question); - question.setSessionId(session.get_id()); - questions.add(doc); - mapping.put(doc, question); - } - database.bulkSaveDocuments(questions.toArray(new Document[questions.size()])); - - // bulk import answers together with interposed questions - for (Entry<Document, ImportExportQuestion> entry : mapping.entrySet()) { - final Document doc = entry.getKey(); - final ImportExportQuestion question = entry.getValue(); - question.set_id(doc.getId()); - question.set_rev(doc.getRev()); - for (de.thm.arsnova.entities.transport.Answer answer : question.getAnswers()) { - final Answer a = answer.generateAnswerEntity(user, question); - final Document answerDoc = new Document(); - answerDoc.put("type", "skill_question_answer"); - answerDoc.put("sessionId", a.getSessionId()); - answerDoc.put("questionId", a.getQuestionId()); - answerDoc.put("answerSubject", a.getAnswerSubject()); - answerDoc.put("questionVariant", a.getQuestionVariant()); - answerDoc.put("questionValue", a.getQuestionValue()); - answerDoc.put("answerText", a.getAnswerText()); - answerDoc.put("answerTextRaw", a.getAnswerTextRaw()); - answerDoc.put("timestamp", a.getTimestamp()); - answerDoc.put("piRound", a.getPiRound()); - answerDoc.put("abstention", a.isAbstention()); - answerDoc.put("successfulFreeTextAnswer", a.isSuccessfulFreeTextAnswer()); - // we do not store the user's name - answerDoc.put("user", ""); - answers.add(answerDoc); - } - } - for (de.thm.arsnova.entities.transport.InterposedQuestion i : importSession.getFeedbackQuestions()) { - final Document q = new Document(); - q.put("type", "interposed_question"); - q.put("sessionId", session.get_id()); - q.put("subject", i.getSubject()); - q.put("text", i.getText()); - q.put("timestamp", i.getTimestamp()); - q.put("read", i.isRead()); - // we do not store the creator's name - q.put("creator", ""); - interposedQuestions.add(q); - } - for (Motd m : importSession.getMotds()) { - final Document d = new Document(); - d.put("type", "motd"); - d.put("motdkey", m.getMotdkey()); - d.put("title", m.getTitle()); - d.put("text", m.getText()); - d.put("audience", m.getAudience()); - d.put("sessionkey", session.getKeyword()); - d.put("startdate", String.valueOf(m.getStartdate().getTime())); - d.put("enddate", String.valueOf(m.getEnddate().getTime())); - motds.add(d); - } - List<Document> documents = new ArrayList<>(answers); - database.bulkSaveDocuments(interposedQuestions.toArray(new Document[interposedQuestions.size()])); - database.bulkSaveDocuments(motds.toArray(new Document[motds.size()])); - database.bulkSaveDocuments(documents.toArray(new Document[documents.size()])); - } catch (IOException e) { - logger.error("Could not import session.", e); - // Something went wrong, delete this session since we do not want a partial import - this.deleteSession(session); - return null; - } - return this.calculateSessionInfo(importSession, session); - } - - @Override - public ImportExportSession exportSession(String sessionkey, Boolean withAnswers, Boolean withFeedbackQuestions) { - ImportExportSession importExportSession = new ImportExportSession(); - Session session = getDatabaseDao().getSessionFromKeyword(sessionkey); - importExportSession.setSessionFromSessionObject(session); - List<Question> questionList = getDatabaseDao().getAllSkillQuestions(session); - for (Question question : questionList) { - List<de.thm.arsnova.entities.transport.Answer> answerList = new ArrayList<>(); - if (withAnswers) { - for (Answer a : this.getDatabaseDao().getAllAnswers(question)) { - de.thm.arsnova.entities.transport.Answer transportAnswer = new de.thm.arsnova.entities.transport.Answer(a); - answerList.add(transportAnswer); - } - // getAllAnswers does not grep for whole answer object so i need to add empty entries for abstentions - int i = this.getDatabaseDao().getAbstentionAnswerCount(question.get_id()); - for (int b = 0; b < i; b++) { - de.thm.arsnova.entities.transport.Answer ans = new de.thm.arsnova.entities.transport.Answer(); - ans.setAnswerSubject(""); - ans.setAnswerImage(""); - ans.setAnswerText(""); - ans.setAbstention(true); - answerList.add(ans); - } - } - importExportSession.addQuestionWithAnswers(question, answerList); - } - if (withFeedbackQuestions) { - List<de.thm.arsnova.entities.transport.InterposedQuestion> interposedQuestionList = new ArrayList<>(); - for (InterposedQuestion i : getDatabaseDao().getInterposedQuestions(session, 0, 0)) { - de.thm.arsnova.entities.transport.InterposedQuestion transportInterposedQuestion = new de.thm.arsnova.entities.transport.InterposedQuestion(i); - interposedQuestionList.add(transportInterposedQuestion); - } - importExportSession.setFeedbackQuestions(interposedQuestionList); - } - if (withAnswers) { - importExportSession.setSessionInfo(this.calculateSessionInfo(importExportSession, session)); - } - importExportSession.setMotds(getDatabaseDao().getMotdsForSession(session.getKeyword())); - return importExportSession; - } - - private SessionInfo calculateSessionInfo(ImportExportSession importExportSession, Session session) { - int unreadInterposed = 0; - int numUnanswered = 0; - int numAnswers = 0; - for (de.thm.arsnova.entities.transport.InterposedQuestion i : importExportSession.getFeedbackQuestions()) { - if (!i.isRead()) { - unreadInterposed++; - } - } - for (ImportExportQuestion question : importExportSession.getQuestions()) { - numAnswers += question.getAnswers().size(); - if (question.getAnswers().isEmpty()) { - numUnanswered++; - } - } - final SessionInfo info = new SessionInfo(session); - info.setNumQuestions(importExportSession.getQuestions().size()); - info.setNumUnanswered(numUnanswered); - info.setNumAnswers(numAnswers); - info.setNumInterposed(importExportSession.getFeedbackQuestions().size()); - info.setNumUnredInterposed(unreadInterposed); - return info; - } - - @Override - public List<String> getSubjects(Session session, String questionVariant) { - final View view = new View("content/by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), questionVariant); - view.setEndKeyArray(session.get_id(), questionVariant, "{}"); - ViewResults results = this.getDatabase().view(view); - - if (results.getJSONArray("rows").optJSONObject(0) == null) { - return null; - } - - Set<String> uniqueSubjects = new HashSet<>(); - - for (final Document d : results.getResults()) { - uniqueSubjects.add(d.getJSONArray("key").getString(3)); - } - - return new ArrayList<>(uniqueSubjects); - } - - /* TODO: remove if this method is no longer used */ - @Override - public List<String> getQuestionIdsBySubject(Session session, String questionVariant, String subject) { - final View view = new View("content/by_sessionid_variant_active"); - view.setStartKeyArray(session.get_id(), questionVariant, 1, subject); - view.setEndKeyArray(session.get_id(), questionVariant, 1, subject, "{}"); - ViewResults results = this.getDatabase().view(view); - - if (results.getJSONArray("rows").optJSONObject(0) == null) { - return null; - } - - List<String> qids = new ArrayList<>(); - - for (final Document d : results.getResults()) { - final String s = d.getId(); - qids.add(s); - } - - return qids; - } - - @Override - public List<Question> getQuestionsByIds(List<String> ids, final Session session) { - View view = new View("_all_docs"); - view.setKeys(ids); - view.setIncludeDocs(true); - final List<Document> questiondocs = getDatabase().view(view).getResults(); - if (questiondocs == null || questiondocs.isEmpty()) { - - return null; - } - final List<Question> result = new ArrayList<>(); - final MorpherRegistry morpherRegistry = JSONUtils.getMorpherRegistry(); - final Morpher dynaMorpher = new BeanMorpher(PossibleAnswer.class, morpherRegistry); - morpherRegistry.registerMorpher(dynaMorpher); - for (final Document document : questiondocs) { - if (!"".equals(document.optString("error"))) { - // Skip documents we could not load. Maybe they were deleted. - continue; - } - final Question question = (Question) JSONObject.toBean( - document.getJSONObject().getJSONObject("doc"), - Question.class - ); - @SuppressWarnings("unchecked") - final Collection<PossibleAnswer> answers = JSONArray.toCollection( - document.getJSONObject().getJSONObject("doc").getJSONArray("possibleAnswers"), - PossibleAnswer.class - ); - question.setPossibleAnswers(new ArrayList<>(answers)); - question.setSessionKeyword(session.getKeyword()); - if (!"freetext".equals(question.getQuestionType()) && 0 == question.getPiRound()) { - /* needed for legacy questions whose piRound property has not been set */ - question.setPiRound(1); - } - - if (question.getImage() != null) { - question.setImage("true"); - } - - result.add(question); - } - return result; - } - - @Override - public List<Motd> getAdminMotds() { - final View view = new View("motd/doc_by_audience_for_global"); - return getMotds(view); - } - - @Override - @Cacheable(cacheNames = "motds", key = "'all'") - public List<Motd> getMotdsForAll() { - final View view = new View("motd/doc_by_audience_for_global"); - return getMotds(view); - } - - @Override - @Cacheable(cacheNames = "motds", key = "'loggedIn'") - public List<Motd> getMotdsForLoggedIn() { - final View view = new View("motd/doc_by_audience_for_global"); - view.setKey("loggedIn"); - return getMotds(view); - } - - @Override - @Cacheable(cacheNames = "motds", key = "'tutors'") - public List<Motd> getMotdsForTutors() { - final View view1 = new View("motd/doc_by_audience_for_global"); - final View view2 = new View("motd/doc_by_audience_for_global"); - view1.setKey("loggedIn"); - view2.setKey("tutors"); - final List<Motd> union = new ArrayList<>(); - union.addAll(getMotds(view1)); - union.addAll(getMotds(view2)); - - return union; - } - - @Override - @Cacheable(cacheNames = "motds", key = "'students'") - public List<Motd> getMotdsForStudents() { - final View view1 = new View("motd/doc_by_audience_for_global"); - final View view2 = new View("motd/doc_by_audience_for_global"); - view1.setKey("loggedIn"); - view2.setKey("students"); - final List<Motd> union = new ArrayList<>(); - union.addAll(getMotds(view1)); - union.addAll(getMotds(view2)); - - return union; - } - - @Override - @Cacheable(cacheNames = "motds", key = "('session').concat(#p0)") - public List<Motd> getMotdsForSession(final String sessionkey) { - final View view = new View("motd/doc_by_sessionkey"); - view.setKey(sessionkey); - return getMotds(view); - } - - @Override - public List<Motd> getMotds(View view) { - final ViewResults motddocs = this.getDatabase().view(view); - List<Motd> motdlist = new ArrayList<>(); - for (final Document d : motddocs.getResults()) { - Motd motd = new Motd(); - motd.set_id(d.getId()); - motd.set_rev(d.getJSONObject("value").getString("_rev")); - motd.setMotdkey(d.getJSONObject("value").getString("motdkey")); - Date start = new Date(Long.parseLong(d.getJSONObject("value").getString("startdate"))); - motd.setStartdate(start); - Date end = new Date(Long.parseLong(d.getJSONObject("value").getString("enddate"))); - motd.setEnddate(end); - motd.setTitle(d.getJSONObject("value").getString("title")); - motd.setText(d.getJSONObject("value").getString("text")); - motd.setAudience(d.getJSONObject("value").getString("audience")); - motd.setSessionkey(d.getJSONObject("value").getString("sessionkey")); - motdlist.add(motd); - } - return motdlist; - } - - @Override - public Motd getMotdByKey(String key) { - final View view = new View("motd/by_motdkey"); - view.setIncludeDocs(true); - view.setKey(key); - Motd motd = new Motd(); - - ViewResults results = this.getDatabase().view(view); - - for (final Document d : results.getResults()) { - motd.set_id(d.getId()); - motd.set_rev(d.getJSONObject("doc").getString("_rev")); - motd.setMotdkey(d.getJSONObject("doc").getString("motdkey")); - Date start = new Date(Long.parseLong(d.getJSONObject("doc").getString("startdate"))); - motd.setStartdate(start); - Date end = new Date(Long.parseLong(d.getJSONObject("doc").getString("enddate"))); - motd.setEnddate(end); - motd.setTitle(d.getJSONObject("doc").getString("title")); - motd.setText(d.getJSONObject("doc").getString("text")); - motd.setAudience(d.getJSONObject("doc").getString("audience")); - motd.setSessionkey(d.getJSONObject("doc").getString("sessionkey")); - } - - return motd; - } - - @Override - @CacheEvict(cacheNames = "motds", key = "#p0.audience.concat(#p0.sessionkey)") - public Motd createOrUpdateMotd(Motd motd) { - try { - String id = motd.get_id(); - String rev = motd.get_rev(); - Document d = new Document(); - - if (null != id) { - d = database.getDocument(id, rev); - } else { - motd.setMotdkey(sessionService.generateKeyword()); - d.put("motdkey", motd.getMotdkey()); - } - d.put("type", "motd"); - d.put("startdate", String.valueOf(motd.getStartdate().getTime())); - d.put("enddate", String.valueOf(motd.getEnddate().getTime())); - d.put("title", motd.getTitle()); - d.put("text", motd.getText()); - d.put("audience", motd.getAudience()); - d.put("sessionId", motd.getSessionId()); - d.put("sessionkey", motd.getSessionkey()); - - database.saveDocument(d, id); - motd.set_id(d.getId()); - motd.set_rev(d.getRev()); - - return motd; - } catch (IOException e) { - logger.error("Could not save MotD {}.", motd, e); - } - - return null; - } - - @Override - @CacheEvict(cacheNames = "motds", key = "#p0.audience.concat(#p0.sessionkey)") - public void deleteMotd(Motd motd) { - try { - this.deleteDocument(motd.get_id()); - } catch (IOException e) { - logger.error("Could not delete MotD {}.", motd.get_id(), e); - } - } - - @Override - @Cacheable(cacheNames = "motdlist", key = "#p0") - public MotdList getMotdListForUser(final String username) { - View view = new View("motdlist/doc_by_username"); - view.setKey(username); - - ViewResults results = this.getDatabase().view(view); - - MotdList motdlist = new MotdList(); - for (final Document d : results.getResults()) { - motdlist.set_id(d.getId()); - motdlist.set_rev(d.getJSONObject("value").getString("_rev")); - motdlist.setUsername(d.getJSONObject("value").getString("username")); - motdlist.setMotdkeys(d.getJSONObject("value").getString("motdkeys")); - } - return motdlist; - } - - @Override - @CachePut(cacheNames = "motdlist", key = "#p0.username") - public MotdList createOrUpdateMotdList(MotdList motdlist) { - try { - String id = motdlist.get_id(); - String rev = motdlist.get_rev(); - Document d = new Document(); - - if (null != id) { - d = database.getDocument(id, rev); - } - d.put("type", "motdlist"); - d.put("username", motdlist.getUsername()); - d.put("motdkeys", motdlist.getMotdkeys()); - - database.saveDocument(d, id); - motdlist.set_id(d.getId()); - motdlist.set_rev(d.getRev()); - - return motdlist; - } catch (IOException e) { - logger.error("Could not save MotD list {}.", motdlist, e); - } - - return null; - } - -} diff --git a/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java b/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java deleted file mode 100644 index bc3a37da2c502e6932c4ff3f461af8fbd510b180..0000000000000000000000000000000000000000 --- a/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * This file is part of ARSnova Backend. - * Copyright (C) 2012-2017 The ARSnova Team - * - * 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.dao; - -import com.fourspaces.couchdb.View; -import de.thm.arsnova.connector.model.Course; -import de.thm.arsnova.domain.CourseScore; -import de.thm.arsnova.entities.*; -import de.thm.arsnova.entities.transport.ImportExportSession; - -import java.util.List; -import java.util.Map; - -/** - * All methods the database must support. - */ -public interface IDatabaseDao { - /** - * Logs an event to the database. Arbitrary data can be attached as payload. Database logging should only be used - * if the logged data is later analyzed by the backend itself. Otherwise use the default logging mechanisms. - * - * @param event type of the event - * @param payload arbitrary logging data - * @param level severity of the event - */ - void log(String event, Map<String, Object> payload, LogEntry.LogLevel level); - - /** - * Logs an event of informational severity to the database. Arbitrary data can be attached as payload. Database - * logging should only be used if the logged data is later analyzed by the backend itself. Otherwise use the default - * logging mechanisms. - * - * @param event type of the event - * @param payload arbitrary logging data - */ - void log(String event, Map<String, Object> payload); - - /** - * Logs an event to the database. Arbitrary data can be attached as payload. Database logging should only be used - * if the logged data is later analyzed by the backend itself. Otherwise use the default logging mechanisms. - * - * @param event type of the event - * @param level severity of the event - * @param rawPayload key/value pairs of arbitrary logging data - */ - void log(String event, LogEntry.LogLevel level, Object... rawPayload); - - /** - * Logs an event of informational severity to the database. Arbitrary data can be attached as payload. Database - * logging should only be used if the logged data is later analyzed by the backend itself. Otherwise use the default - * logging mechanisms. - * - * @param event type of the event - * @param rawPayload key/value pairs of arbitrary logging data - */ - void log(String event, Object... rawPayload); - - Session getSessionFromKeyword(String keyword); - - List<Session> getMySessions(User user, final int start, final int limit); - - List<Session> getSessionsForUsername(String username, final int start, final int limit); - - List<Session> getPublicPoolSessions(); - - List<Session> getMyPublicPoolSessions(User user); - - Session saveSession(User user, Session session); - - boolean sessionKeyAvailable(String keyword); - - Question saveQuestion(Session session, Question question); - - InterposedQuestion saveQuestion(Session session, InterposedQuestion question, User user); - - Question getQuestion(String id); - - List<Question> getSkillQuestionsForUsers(Session session); - - List<Question> getSkillQuestionsForTeachers(Session session); - - int getSkillQuestionCount(Session session); - - LoggedIn registerAsOnlineUser(User u, Session s); - - Session updateSessionOwnerActivity(Session session); - - List<String> getQuestionIds(Session session, User user); - - int deleteQuestionWithAnswers(Question question); - - int[] deleteAllQuestionsWithAnswers(Session session); - - List<String> getUnAnsweredQuestionIds(Session session, User user); - - Answer getMyAnswer(User me, String questionId, int piRound); - - List<Answer> getAnswers(Question question, int piRound); - - List<Answer> getAnswers(Question question); - - List<Answer> getAllAnswers(Question question); - - int getAnswerCount(Question question, int piRound); - - int getTotalAnswerCountByQuestion(Question question); - - int getAbstentionAnswerCount(String questionId); - - List<Answer> getFreetextAnswers(String questionId, final int start, final int limit); - - List<Answer> getMyAnswers(User me, Session session); - - int getTotalAnswerCount(String sessionKey); - - int getInterposedCount(String sessionKey); - - InterposedReadingCount getInterposedReadingCount(Session session); - - InterposedReadingCount getInterposedReadingCount(Session session, User user); - - List<InterposedQuestion> getInterposedQuestions(Session session, final int start, final int limit); - - List<InterposedQuestion> getInterposedQuestions(Session session, User user, final int start, final int limit); - - InterposedQuestion getInterposedQuestion(String questionId); - - void markInterposedQuestionAsRead(InterposedQuestion question); - - List<Session> getMyVisitedSessions(User user, final int start, final int limit); - - List<Session> getVisitedSessionsForUsername(String username, final int start, final int limit); - - Question updateQuestion(Question question); - - int deleteAnswers(Question question); - - Answer saveAnswer(Answer answer, User user, Question question, Session session); - - Answer updateAnswer(Answer answer); - - Session getSessionFromId(String sessionId); - - void deleteAnswer(String answerId); - - void deleteInterposedQuestion(InterposedQuestion question); - - List<Session> getCourseSessions(List<Course> courses); - - Session updateSession(Session session); - - Session changeSessionCreator(Session session, String newCreator); - - /** - * Deletes a session and related data. - * - * @param session the session for deletion - */ - int[] deleteSession(Session session); - - int[] deleteInactiveGuestSessions(long lastActivityBefore); - - int deleteInactiveGuestVisitedSessionLists(long lastActivityBefore); - - List<Question> getLectureQuestionsForUsers(Session session); - - List<Question> getLectureQuestionsForTeachers(Session session); - - List<Question> getFlashcardsForUsers(Session session); - - List<Question> getFlashcardsForTeachers(Session session); - - List<Question> getPreparationQuestionsForUsers(Session session); - - List<Question> getPreparationQuestionsForTeachers(Session session); - - List<Question> getAllSkillQuestions(Session session); - - int getLectureQuestionCount(Session session); - - int getFlashcardCount(Session session); - - int getPreparationQuestionCount(Session session); - - int countLectureQuestionAnswers(Session session); - - int countPreparationQuestionAnswers(Session session); - - int[] deleteAllLectureQuestionsWithAnswers(Session session); - - int[] deleteAllFlashcardsWithAnswers(Session session); - - int[] deleteAllPreparationQuestionsWithAnswers(Session session); - - List<String> getUnAnsweredLectureQuestionIds(Session session, User user); - - List<String> getUnAnsweredPreparationQuestionIds(Session session, User user); - - int deleteAllInterposedQuestions(Session session); - - int deleteAllInterposedQuestions(Session session, User user); - - void publishQuestions(Session session, boolean publish, List<Question> questions); - - List<Question> publishAllQuestions(Session session, boolean publish); - - int deleteAllQuestionsAnswers(Session session); - - DbUser createOrUpdateUser(DbUser user); - - DbUser getUser(String username); - - boolean deleteUser(DbUser dbUser); - - int deleteInactiveUsers(long lastActivityBefore); - - CourseScore getLearningProgress(Session session); - - List<SessionInfo> getMySessionsInfo(User user, final int start, final int limit); - - List<SessionInfo> getPublicPoolSessionsInfo(); - - List<SessionInfo> getMyPublicPoolSessionsInfo(final User user); - - List<SessionInfo> getMyVisitedSessionsInfo(User currentUser, final int start, final int limit); - - int deleteAllPreparationAnswers(Session session); - - int deleteAllLectureAnswers(Session session); - - SessionInfo importSession(User user, ImportExportSession importSession); - - ImportExportSession exportSession(String sessionkey, Boolean withAnswer, Boolean withFeedbackQuestions); - - Statistics getStatistics(); - - List<String> getSubjects(Session session, String questionVariant); - - List<String> getQuestionIdsBySubject(Session session, String questionVariant, String subject); - - List<Question> getQuestionsByIds(List<String> ids, Session session); - - void resetQuestionsRoundState(Session session, List<Question> questions); - - void setVotingAdmissions(Session session, boolean disableVoting, List<Question> questions); - - List<Question> setVotingAdmissionForAllQuestions(Session session, boolean disableVoting); - - <T> T getObjectFromId(String documentId, Class<T> klass); - - List<Motd> getAdminMotds(); - - List<Motd> getMotdsForAll(); - - List<Motd> getMotdsForLoggedIn(); - - List<Motd> getMotdsForTutors(); - - List<Motd> getMotdsForStudents(); - - List<Motd> getMotdsForSession(final String sessionkey); - - List<Motd> getMotds(View view); - - Motd getMotdByKey(String key); - - Motd createOrUpdateMotd(Motd motd); - - void deleteMotd(Motd motd); - - MotdList getMotdListForUser(final String username); - - MotdList createOrUpdateMotdList(MotdList motdlist); -} diff --git a/src/main/java/de/thm/arsnova/dao/package-info.java b/src/main/java/de/thm/arsnova/dao/package-info.java deleted file mode 100644 index 1b1c0dd99512dc8a48b58c97a9d33b92f34b43a3..0000000000000000000000000000000000000000 --- a/src/main/java/de/thm/arsnova/dao/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Classes and interfaces for accessing the database - */ -package de.thm.arsnova.dao; diff --git a/src/main/java/de/thm/arsnova/domain/LearningProgressFactory.java b/src/main/java/de/thm/arsnova/domain/LearningProgressFactory.java index 89a750ce1675bf3fe33d75f571413499b4124ef9..a5596c0b73bafa8a37d2ed287ce4b9e66853606b 100644 --- a/src/main/java/de/thm/arsnova/domain/LearningProgressFactory.java +++ b/src/main/java/de/thm/arsnova/domain/LearningProgressFactory.java @@ -17,8 +17,8 @@ */ package de.thm.arsnova.domain; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.events.*; +import de.thm.arsnova.persistance.SessionStatisticsRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.context.ApplicationEventPublisher; @@ -34,7 +34,7 @@ import org.springframework.stereotype.Component; public class LearningProgressFactory implements NovaEventVisitor, ILearningProgressFactory, ApplicationEventPublisherAware { @Autowired - private IDatabaseDao databaseDao; + private SessionStatisticsRepository sessionStatisticsRepository; private ApplicationEventPublisher publisher; @@ -42,19 +42,19 @@ public class LearningProgressFactory implements NovaEventVisitor, ILearningProgr public LearningProgress create(String progressType, String questionVariant) { VariantLearningProgress learningProgress; if ("questions".equals(progressType)) { - learningProgress = new QuestionBasedLearningProgress(databaseDao); + learningProgress = new QuestionBasedLearningProgress(sessionStatisticsRepository); } else { - learningProgress = new PointBasedLearningProgress(databaseDao); + learningProgress = new PointBasedLearningProgress(sessionStatisticsRepository); } learningProgress.setQuestionVariant(questionVariant); return learningProgress; } @Override - public void visit(NewInterposedQuestionEvent event) { } + public void visit(NewCommentEvent event) { } @Override - public void visit(DeleteInterposedQuestionEvent deleteInterposedQuestionEvent) { } + public void visit(DeleteCommentEvent deleteCommentEvent) { } @CacheEvict(value = "learningprogress", key = "#event.Session") @Override diff --git a/src/main/java/de/thm/arsnova/domain/PointBasedLearningProgress.java b/src/main/java/de/thm/arsnova/domain/PointBasedLearningProgress.java index a904caf6b4599c9720a34a4917691cb2b3212bbd..21f2fbd3d966ccd6b92749ddde815fb184a15fae 100644 --- a/src/main/java/de/thm/arsnova/domain/PointBasedLearningProgress.java +++ b/src/main/java/de/thm/arsnova/domain/PointBasedLearningProgress.java @@ -17,17 +17,17 @@ */ package de.thm.arsnova.domain; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.User; import de.thm.arsnova.entities.transport.LearningProgressValues; +import de.thm.arsnova.persistance.SessionStatisticsRepository; /** * Calculates learning progress based on a question's value. */ public class PointBasedLearningProgress extends VariantLearningProgress { - public PointBasedLearningProgress(IDatabaseDao dao) { - super(dao); + public PointBasedLearningProgress(SessionStatisticsRepository sessionStatisticsRepository) { + super(sessionStatisticsRepository); } @Override diff --git a/src/main/java/de/thm/arsnova/domain/QuestionBasedLearningProgress.java b/src/main/java/de/thm/arsnova/domain/QuestionBasedLearningProgress.java index dba9b41803ba939c47338945805eebd4a30d6b13..37b6c63974f639cca4e76bdf8563b25f5e9dada7 100644 --- a/src/main/java/de/thm/arsnova/domain/QuestionBasedLearningProgress.java +++ b/src/main/java/de/thm/arsnova/domain/QuestionBasedLearningProgress.java @@ -17,9 +17,9 @@ */ package de.thm.arsnova.domain; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.User; import de.thm.arsnova.entities.transport.LearningProgressValues; +import de.thm.arsnova.persistance.SessionStatisticsRepository; /** * Calculates learning progress based on overall correctness of an answer. A question is answered correctly if and @@ -27,8 +27,8 @@ import de.thm.arsnova.entities.transport.LearningProgressValues; */ public class QuestionBasedLearningProgress extends VariantLearningProgress { - public QuestionBasedLearningProgress(IDatabaseDao dao) { - super(dao); + public QuestionBasedLearningProgress(SessionStatisticsRepository sessionStatisticsRepository) { + super(sessionStatisticsRepository); } @Override diff --git a/src/main/java/de/thm/arsnova/domain/VariantLearningProgress.java b/src/main/java/de/thm/arsnova/domain/VariantLearningProgress.java index 708acb4b79b454803a6d0bb3233e4e9de3ece960..7e1c069701fa3fe336d6f7a1042185a69d080ac0 100644 --- a/src/main/java/de/thm/arsnova/domain/VariantLearningProgress.java +++ b/src/main/java/de/thm/arsnova/domain/VariantLearningProgress.java @@ -17,10 +17,10 @@ */ package de.thm.arsnova.domain; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; import de.thm.arsnova.entities.transport.LearningProgressValues; +import de.thm.arsnova.persistance.SessionStatisticsRepository; /** * Base class for the learning progress feature that allows filtering on the question variant. @@ -31,14 +31,14 @@ abstract class VariantLearningProgress implements LearningProgress { private String questionVariant; - private final IDatabaseDao databaseDao; + private final SessionStatisticsRepository sessionStatisticsRepository; - public VariantLearningProgress(final IDatabaseDao dao) { - this.databaseDao = dao; + public VariantLearningProgress(final SessionStatisticsRepository sessionStatisticsRepository) { + this.sessionStatisticsRepository = sessionStatisticsRepository; } private void loadProgress(final Session session) { - this.courseScore = databaseDao.getLearningProgress(session); + this.courseScore = sessionStatisticsRepository.getLearningProgress(session); } public void setQuestionVariant(final String variant) { diff --git a/src/main/java/de/thm/arsnova/entities/Answer.java b/src/main/java/de/thm/arsnova/entities/Answer.java index 283060bb155cd307944bb5b7037f428bfadd2708..146acaf85f960b692c825dc7baf2b4f5f0fafc54 100644 --- a/src/main/java/de/thm/arsnova/entities/Answer.java +++ b/src/main/java/de/thm/arsnova/entities/Answer.java @@ -18,6 +18,8 @@ package de.thm.arsnova.entities; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -29,10 +31,9 @@ import java.io.Serializable; * This class has additional fields to transport generated answer statistics. */ @ApiModel(value = "Answer", description = "the answer entity") -public class Answer implements Serializable { - - private String _id; - private String _rev; +public class Answer implements Entity { + private String id; + private String rev; private String type; private String sessionId; private String questionId; @@ -58,143 +59,158 @@ public class Answer implements Serializable { } @ApiModelProperty(required = true, value = "the couchDB ID") - public final String get_id() { - return _id; - } - - public final void set_id(String _id) { - this._id = _id; - } - - public final String get_rev() { - return _rev; + @JsonView({View.Persistence.class, View.Public.class}) + public String getId() { + return id; } - public final void set_rev(final String _rev) { - this._rev = _rev; + @JsonView({View.Persistence.class, View.Public.class}) + public void setId(final String id) { + this.id = id; } - @ApiModelProperty(required = true, value = "\"skill_question_answer\" - used to filter in the couchDB") - public final String getType() { - return type; + @JsonView({View.Persistence.class, View.Public.class}) + public void setRevision(final String rev) { + this.rev = rev; } - public final void setType(final String type) { - this.type = type; + @JsonView({View.Persistence.class, View.Public.class}) + public String getRevision() { + return rev; } @ApiModelProperty(required = true, value = "ID of the session, the answer is assigned to") + @JsonView({View.Persistence.class, View.Public.class}) public final String getSessionId() { return sessionId; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setSessionId(final String sessionId) { this.sessionId = sessionId; } @ApiModelProperty(required = true, value = "used to display question id") + @JsonView({View.Persistence.class, View.Public.class}) public final String getQuestionId() { return questionId; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setQuestionId(final String questionId) { this.questionId = questionId; } @ApiModelProperty(required = true, value = "the answer text") + @JsonView({View.Persistence.class, View.Public.class}) public final String getAnswerText() { return answerText; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setAnswerText(final String answerText) { this.answerText = answerText; } + @JsonView({View.Persistence.class, View.Public.class}) public final String getAnswerTextRaw() { return this.answerTextRaw; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setAnswerTextRaw(final String answerTextRaw) { this.answerTextRaw = answerTextRaw; } @ApiModelProperty(required = true, value = "the answer subject") + @JsonView({View.Persistence.class, View.Public.class}) public final String getAnswerSubject() { return answerSubject; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setAnswerSubject(final String answerSubject) { this.answerSubject = answerSubject; } + @JsonView({View.Persistence.class, View.Public.class}) public final boolean isSuccessfulFreeTextAnswer() { return this.successfulFreeTextAnswer; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setSuccessfulFreeTextAnswer(final boolean successfulFreeTextAnswer) { this.successfulFreeTextAnswer = successfulFreeTextAnswer; } @ApiModelProperty(required = true, value = "the peer instruction round nr.") + @JsonView({View.Persistence.class, View.Public.class}) public int getPiRound() { return piRound; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPiRound(int piRound) { this.piRound = piRound; } - /* TODO: use JsonViews instead of JsonIgnore when supported by Spring (4.1) - * http://wiki.fasterxml.com/JacksonJsonViews - * https://jira.spring.io/browse/SPR-7156 */ @ApiModelProperty(required = true, value = "the user") - @JsonIgnore + @JsonView(View.Persistence.class) public final String getUser() { return user; } + @JsonView({View.Persistence.class, View.Public.class}) + public final void setUser(final String user) { + this.user = user; + } + @ApiModelProperty(required = true, value = "the answer image") - @JsonIgnore + @JsonView(View.Persistence.class) public String getAnswerImage() { return answerImage; } + @JsonView({View.Persistence.class, View.Public.class}) public void setAnswerImage(String answerImage) { this.answerImage = answerImage; } @ApiModelProperty(required = true, value = "the answer thumbnail") + @JsonView({View.Persistence.class, View.Public.class}) public String getAnswerThumbnailImage() { return answerThumbnailImage; } + @JsonView({View.Persistence.class, View.Public.class}) public void setAnswerThumbnailImage(String answerThumbnailImage) { this.answerThumbnailImage = answerThumbnailImage; } - public final void setUser(final String user) { - this.user = user; - } - @ApiModelProperty(required = true, value = "the creation date timestamp") + @JsonView({View.Persistence.class, View.Public.class}) public long getTimestamp() { return timestamp; } + @JsonView(View.Persistence.class) public void setTimestamp(long timestamp) { this.timestamp = timestamp; } @ApiModelProperty(required = true, value = "displays whether the answer is read") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isRead() { return read; } + @JsonView({View.Persistence.class, View.Public.class}) public void setRead(boolean read) { this.read = read; } @ApiModelProperty(required = true, value = "the number of answers given. used for statistics") + @JsonView(View.Public.class) public final int getAnswerCount() { return answerCount; } @@ -204,15 +220,18 @@ public class Answer implements Serializable { } @ApiModelProperty(required = true, value = "the abstention") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isAbstention() { return abstention; } + @JsonView({View.Persistence.class, View.Public.class}) public void setAbstention(boolean abstention) { this.abstention = abstention; } @ApiModelProperty(required = true, value = "the number of abstentions given. used for statistics") + @JsonView(View.Public.class) public int getAbstentionCount() { return abstentionCount; } @@ -222,19 +241,23 @@ public class Answer implements Serializable { } @ApiModelProperty(required = true, value = "either lecture or preparation") + @JsonView({View.Persistence.class, View.Public.class}) public String getQuestionVariant() { return questionVariant; } + @JsonView({View.Persistence.class, View.Public.class}) public void setQuestionVariant(String questionVariant) { this.questionVariant = questionVariant; } @ApiModelProperty(required = true, value = "used to display question value") + @JsonView({View.Persistence.class, View.Public.class}) public int getQuestionValue() { return questionValue; } + @JsonView({View.Persistence.class, View.Public.class}) public void setQuestionValue(int questionValue) { this.questionValue = questionValue; } @@ -255,8 +278,8 @@ public class Answer implements Serializable { // auto generated! final int prime = 31; int result = 1; - result = prime * result + ((_id == null) ? 0 : _id.hashCode()); - result = prime * result + ((_rev == null) ? 0 : _rev.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((rev == null) ? 0 : rev.hashCode()); result = prime * result + ((answerSubject == null) ? 0 : answerSubject.hashCode()); result = prime * result + ((answerText == null) ? 0 : answerText.hashCode()); result = prime * result + piRound; @@ -280,18 +303,18 @@ public class Answer implements Serializable { return false; } Answer other = (Answer) obj; - if (_id == null) { - if (other._id != null) { + if (id == null) { + if (other.id != null) { return false; } - } else if (!_id.equals(other._id)) { + } else if (!id.equals(other.id)) { return false; } - if (_rev == null) { - if (other._rev != null) { + if (rev == null) { + if (other.rev != null) { return false; } - } else if (!_rev.equals(other._rev)) { + } else if (!rev.equals(other.rev)) { return false; } if (answerSubject == null) { diff --git a/src/main/java/de/thm/arsnova/entities/Authorize.java b/src/main/java/de/thm/arsnova/entities/Authorize.java index de37e72a5164c3991549d71af68dc843f8b931e0..eacc7c5a859be40fd1758e8f287f0ea6da27b68a 100644 --- a/src/main/java/de/thm/arsnova/entities/Authorize.java +++ b/src/main/java/de/thm/arsnova/entities/Authorize.java @@ -17,10 +17,14 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; + public class Authorize { private String user; private String socketid; + @JsonView(View.Public.class) public final String getUser() { return user; } @@ -29,6 +33,7 @@ public class Authorize { this.user = user; } + @JsonView(View.Public.class) public final String getSocketid() { return socketid; } diff --git a/src/main/java/de/thm/arsnova/entities/InterposedQuestion.java b/src/main/java/de/thm/arsnova/entities/Comment.java similarity index 63% rename from src/main/java/de/thm/arsnova/entities/InterposedQuestion.java rename to src/main/java/de/thm/arsnova/entities/Comment.java index 7b206698b9d2ee5b93198696fada21a3c5b25b75..de2a93955c148c67ede7b3afa709643293a3e598 100644 --- a/src/main/java/de/thm/arsnova/entities/InterposedQuestion.java +++ b/src/main/java/de/thm/arsnova/entities/Comment.java @@ -17,21 +17,18 @@ */ package de.thm.arsnova.entities; -import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; -import java.io.Serializable; - /** - * A question the user is asking the teacher. Also known as feedback or audience question. + * A question the user is asking the teacher. Also known as comment, feedback or audience question. */ -@ApiModel(value = "audiencequestion", description = "the interposed question entity") -public class InterposedQuestion implements Serializable { - - private String _id; - private String _rev; - private String type; +@ApiModel(value = "comment", description = "the comment entity") +public class Comment implements Entity { + private String id; + private String rev; private String subject; private String text; /* FIXME sessionId actually is used to hold the sessionKey. @@ -44,77 +41,87 @@ public class InterposedQuestion implements Serializable { private boolean read; private String creator; - @ApiModelProperty(required = true, value = "the couchDB ID") - public String get_id() { - return _id; + @JsonView({View.Persistence.class, View.Public.class}) + public String getId() { + return id; } - public void set_id(String _id) { - this._id = _id; + + @JsonView({View.Persistence.class, View.Public.class}) + public void setId(final String id) { + this.id = id; } - public String get_rev() { - return _rev; + @JsonView({View.Persistence.class, View.Public.class}) + public void setRevision(final String rev) { + this.rev = rev; } - public void set_rev(String _rev) { - this._rev = _rev; + + @JsonView({View.Persistence.class, View.Public.class}) + public String getRevision() { + return rev; } @ApiModelProperty(required = true, value = "is read") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isRead() { return read; } + + @JsonView({View.Persistence.class, View.Public.class}) public void setRead(boolean read) { this.read = read; } - @ApiModelProperty(required = true, value = "used to display the type") - public String getType() { - return type; - } - public void setType(String type) { - this.type = type; - } - @ApiModelProperty(required = true, value = "the subject") + @JsonView({View.Persistence.class, View.Public.class}) public String getSubject() { return subject; } + + @JsonView({View.Persistence.class, View.Public.class}) public void setSubject(String subject) { this.subject = subject; } @ApiModelProperty(required = true, value = "the Text") + @JsonView({View.Persistence.class, View.Public.class}) public String getText() { return text; } + + @JsonView({View.Persistence.class, View.Public.class}) public void setText(String text) { this.text = text; } - @ApiModelProperty(required = true, value = "ID of the session, the question is assigned to") + @ApiModelProperty(required = true, value = "ID of the session, the comment is assigned to") + @JsonView(View.Persistence.class) public String getSessionId() { return sessionId; } + + @JsonView({View.Persistence.class, View.Public.class}) public void setSessionId(String sessionId) { this.sessionId = sessionId; } @ApiModelProperty(required = true, value = "creation date timestamp") + @JsonView({View.Persistence.class, View.Public.class}) public long getTimestamp() { return timestamp; } + + @JsonView({View.Persistence.class, View.Public.class}) public void setTimestamp(long timestamp) { this.timestamp = timestamp; } - /* TODO: use JsonViews instead of JsonIgnore when supported by Spring (4.1) - * http://wiki.fasterxml.com/JacksonJsonViews - * https://jira.spring.io/browse/SPR-7156 */ - @JsonIgnore + @JsonView(View.Persistence.class) public String getCreator() { return creator; } + @JsonView(View.Persistence.class) public void setCreator(String creator) { this.creator = creator; } diff --git a/src/main/java/de/thm/arsnova/entities/InterposedReadingCount.java b/src/main/java/de/thm/arsnova/entities/CommentReadingCount.java similarity index 76% rename from src/main/java/de/thm/arsnova/entities/InterposedReadingCount.java rename to src/main/java/de/thm/arsnova/entities/CommentReadingCount.java index a254cb6c691b3154d2feb071e176a04d027dd0b2..eb6382e4018eb75392dc9ec1ede4a2891d8f6f5a 100644 --- a/src/main/java/de/thm/arsnova/entities/InterposedReadingCount.java +++ b/src/main/java/de/thm/arsnova/entities/CommentReadingCount.java @@ -17,29 +17,32 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; /** - * Wrapper class for counting read and unread interposed questions for a session or a single user. + * Wrapper class for counting read and unread comments for a session or a single user. */ -@ApiModel(value = "audiencequestion/readcount", description = "the interposed reading count entity") -public class InterposedReadingCount { +@ApiModel(value = "audiencequestion/readcount", description = "the comment reading count entity") +public class CommentReadingCount { private int read; private int unread; - public InterposedReadingCount(int readCount, int unreadCount) { + public CommentReadingCount(int readCount, int unreadCount) { this.read = readCount; this.unread = unreadCount; } - public InterposedReadingCount() { + public CommentReadingCount() { this.read = 0; this.unread = 0; } - @ApiModelProperty(required = true, value = "the number of read interposed questions") + @ApiModelProperty(required = true, value = "the number of read comments") + @JsonView(View.Public.class) public int getRead() { return read; } @@ -48,7 +51,8 @@ public class InterposedReadingCount { this.read = read; } - @ApiModelProperty(required = true, value = "the number of unread interposed questions") + @ApiModelProperty(required = true, value = "the number of unread comments") + @JsonView(View.Public.class) public int getUnread() { return unread; } @@ -57,7 +61,8 @@ public class InterposedReadingCount { this.unread = unread; } - @ApiModelProperty(required = true, value = "the number of total interposed questions") + @ApiModelProperty(required = true, value = "the number of total comments") + @JsonView(View.Public.class) public int getTotal() { return getRead() + getUnread(); } diff --git a/src/main/java/de/thm/arsnova/entities/Question.java b/src/main/java/de/thm/arsnova/entities/Content.java similarity index 75% rename from src/main/java/de/thm/arsnova/entities/Question.java rename to src/main/java/de/thm/arsnova/entities/Content.java index a99670287ba7b6ce8c87f1bdbc730db9c2969e0e..653a645518299427ef98036fd3e8e110114fde57 100644 --- a/src/main/java/de/thm/arsnova/entities/Question.java +++ b/src/main/java/de/thm/arsnova/entities/Content.java @@ -17,20 +17,21 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; -import java.io.Serializable; import java.util.Date; import java.util.List; /** * A question the teacher is asking. */ -@ApiModel(value = "lecturerquestion", description = "the question entity") -public class Question implements Serializable { - - private String type; +@ApiModel(value = "content", description = "the content entity") +public class Content implements Entity { + private String id; + private String rev; private String questionType; private String questionVariant; private String subject; @@ -62,8 +63,6 @@ public class Question implements Serializable { private boolean strictMode; private int rating; private String correctAnswer; - private String _id; - private String _rev; private String image; private String fcImage; @@ -92,56 +91,78 @@ public class Question implements Serializable { private String hint; private String solution; - @ApiModelProperty(required = true, value = "the type") - public final String getType() { - return type; + @ApiModelProperty(required = true, value = "the couchDB ID") + @JsonView({View.Persistence.class, View.Public.class}) + public String getId() { + return id; + } + + @JsonView({View.Persistence.class, View.Public.class}) + public void setId(final String id) { + this.id = id; + } + + @JsonView({View.Persistence.class, View.Public.class}) + public void setRevision(final String rev) { + this.rev = rev; } - public final void setType(final String type) { - this.type = type; + @JsonView({View.Persistence.class, View.Public.class}) + public String getRevision() { + return rev; } @ApiModelProperty(required = true, value = "the question type") + @JsonView({View.Persistence.class, View.Public.class}) public final String getQuestionType() { return questionType; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setQuestionType(final String questionType) { this.questionType = questionType; } @ApiModelProperty(required = true, value = "either lecture or preparation") + @JsonView({View.Persistence.class, View.Public.class}) public final String getQuestionVariant() { return questionVariant; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setQuestionVariant(final String questionVariant) { this.questionVariant = questionVariant; } @ApiModelProperty(required = true, value = "used to display subject") + @JsonView({View.Persistence.class, View.Public.class}) public final String getSubject() { return subject; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setSubject(final String subject) { this.subject = subject; } @ApiModelProperty(required = true, value = "the text") + @JsonView({View.Persistence.class, View.Public.class}) public final String getText() { return text; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setText(final String text) { this.text = text; } @ApiModelProperty(required = true, value = "true for active question") + @JsonView({View.Persistence.class, View.Public.class}) public final boolean isActive() { return active; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setActive(final boolean active) { this.active = active; } @@ -156,10 +177,12 @@ public class Question implements Serializable { } @ApiModelProperty(required = true, value = "list of possible answers") + @JsonView({View.Persistence.class, View.Public.class}) public final List<PossibleAnswer> getPossibleAnswers() { return possibleAnswers; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setPossibleAnswers(final List<PossibleAnswer> possibleAnswers) { this.possibleAnswers = possibleAnswers; } @@ -174,10 +197,12 @@ public class Question implements Serializable { } @ApiModelProperty(required = true, value = "couchDB ID of the session, the question is assigned to") + @JsonView({View.Persistence.class, View.Public.class}) public final String getSessionId() { return sessionId; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setSessionId(final String sessionId) { this.sessionId = sessionId; } @@ -201,10 +226,12 @@ public class Question implements Serializable { } @ApiModelProperty(required = true, value = "creation date timestamp") + @JsonView(View.Persistence.class) public final long getTimestamp() { return timestamp; } + @JsonView(View.Persistence.class) public final void setTimestamp(final long timestamp) { this.timestamp = timestamp; } @@ -219,46 +246,56 @@ public class Question implements Serializable { } @ApiModelProperty(required = true, value = "used to display duration") + @JsonView({View.Persistence.class, View.Public.class}) public final int getDuration() { return duration; } + @JsonView({View.Persistence.class, View.Public.class}) + public final void setDuration(final int duration) { + this.duration = duration; + } + @ApiModelProperty(required = true, value = "true for image question") + @JsonView({View.Persistence.class, View.Public.class}) public final boolean isImageQuestion() { return imageQuestion; } + @JsonView({View.Persistence.class, View.Public.class}) public void setImageQuestion(boolean imageQuestion) { this.imageQuestion = imageQuestion; } - public final void setDuration(final int duration) { - this.duration = duration; - } - @ApiModelProperty(required = true, value = "the peer instruction round no.") + @JsonView({View.Persistence.class, View.Public.class}) public int getPiRound() { return piRound; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPiRound(final int piRound) { this.piRound = piRound; } @ApiModelProperty(required = true, value = "the peer instruction round end timestamp") + @JsonView({View.Persistence.class, View.Public.class}) public long getPiRoundEndTime() { return piRoundEndTime; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPiRoundEndTime(long piRoundEndTime) { this.piRoundEndTime = piRoundEndTime; } @ApiModelProperty(required = true, value = "the peer instruction round start timestamp") + @JsonView({View.Persistence.class, View.Public.class}) public long getPiRoundStartTime() { return piRoundStartTime; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPiRoundStartTime(long piRoundStartTime) { this.piRoundStartTime = piRoundStartTime; } @@ -282,340 +319,395 @@ public class Question implements Serializable { } @ApiModelProperty(required = true, value = "used to display showStatistic") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isShowStatistic() { return showStatistic; } + @JsonView({View.Persistence.class, View.Public.class}) public void setShowStatistic(final boolean showStatistic) { this.showStatistic = showStatistic; } @ApiModelProperty(required = true, value = "used to display cvIsColored") + @JsonView({View.Persistence.class, View.Public.class}) public boolean getCvIsColored() { return cvIsColored; } + @JsonView({View.Persistence.class, View.Public.class}) public void setCvIsColored(boolean cvIsColored) { this.cvIsColored = cvIsColored; } @ApiModelProperty(required = true, value = "used to display showAnswer") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isShowAnswer() { return showAnswer; } + @JsonView({View.Persistence.class, View.Public.class}) public void setShowAnswer(final boolean showAnswer) { this.showAnswer = showAnswer; } @ApiModelProperty(required = true, value = "used to display abstention") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isAbstention() { return abstention; } + @JsonView({View.Persistence.class, View.Public.class}) public void setAbstention(final boolean abstention) { this.abstention = abstention; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isIgnoreCaseSensitive() { return ignoreCaseSensitive; } + @JsonView({View.Persistence.class, View.Public.class}) public void setIgnoreCaseSensitive(final boolean ignoreCaseSensitive) { this.ignoreCaseSensitive = ignoreCaseSensitive; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isIgnoreWhitespaces() { return ignoreWhitespaces; } + @JsonView({View.Persistence.class, View.Public.class}) public void setIgnoreWhitespaces(final boolean ignoreWhitespaces) { this.ignoreWhitespaces = ignoreWhitespaces; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isIgnorePunctuation() { return ignorePunctuation; } + @JsonView({View.Persistence.class, View.Public.class}) public void setIgnorePunctuation(final boolean ignorePunctuation) { this.ignorePunctuation = ignorePunctuation; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isFixedAnswer() { return this.fixedAnswer; } + @JsonView({View.Persistence.class, View.Public.class}) public void setFixedAnswer(final boolean fixedAnswer) { this.fixedAnswer = fixedAnswer; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isStrictMode() { return this.strictMode; } + @JsonView({View.Persistence.class, View.Public.class}) public void setStrictMode(final boolean strictMode) { this.strictMode = strictMode; } + @JsonView({View.Persistence.class, View.Public.class}) public final int getRating() { return this.rating; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setRating(final int rating) { this.rating = rating; } + @JsonView({View.Persistence.class, View.Public.class}) public final String getCorrectAnswer() { return correctAnswer; } + @JsonView({View.Persistence.class, View.Public.class}) public final void setCorrectAnswer(final String correctAnswer) { this.correctAnswer = correctAnswer; } - @ApiModelProperty(required = true, value = "the couchDB ID") - public final String get_id() { - return _id; - } - - public final void set_id(final String _id) { - this._id = _id; - } - - public final String get_rev() { - return _rev; - } - - public final void set_rev(final String _rev) { - this._rev = _rev; - } - @ApiModelProperty(required = true, value = "the image") + @JsonView({View.Persistence.class, View.Public.class}) public String getImage() { return image; } + @JsonView({View.Persistence.class, View.Public.class}) public void setImage(final String image) { this.image = image; } @ApiModelProperty(required = true, value = "the fcImage") + @JsonView({View.Persistence.class, View.Public.class}) public String getFcImage() { return fcImage; } + @JsonView({View.Persistence.class, View.Public.class}) public void setFcImage(final String fcImage) { this.fcImage = fcImage; } @ApiModelProperty(required = true, value = "the grid size") + @JsonView({View.Persistence.class, View.Public.class}) public int getGridSize() { return gridSize; } + @JsonView({View.Persistence.class, View.Public.class}) public void setGridSize(final int gridSize) { this.gridSize = gridSize; } @ApiModelProperty(required = true, value = "the image X offset") + @JsonView({View.Persistence.class, View.Public.class}) public int getOffsetX() { return offsetX; } + @JsonView({View.Persistence.class, View.Public.class}) public void setOffsetX(final int offsetX) { this.offsetX = offsetX; } @ApiModelProperty(required = true, value = "the image Y offset") + @JsonView({View.Persistence.class, View.Public.class}) public int getOffsetY() { return offsetY; } + @JsonView({View.Persistence.class, View.Public.class}) public void setOffsetY(final int offsetY) { this.offsetY = offsetY; } @ApiModelProperty(required = true, value = "the image zoom level") + @JsonView({View.Persistence.class, View.Public.class}) public int getZoomLvl() { return zoomLvl; } + @JsonView({View.Persistence.class, View.Public.class}) public void setZoomLvl(final int zoomLvl) { this.zoomLvl = zoomLvl; } @ApiModelProperty(required = true, value = "the grid X offset") + @JsonView({View.Persistence.class, View.Public.class}) public int getGridOffsetX() { return gridOffsetX; } + @JsonView({View.Persistence.class, View.Public.class}) public void setGridOffsetX(int gridOffsetX) { this.gridOffsetX = gridOffsetX; } @ApiModelProperty(required = true, value = "the grid Y offset") + @JsonView({View.Persistence.class, View.Public.class}) public int getGridOffsetY() { return gridOffsetY; } + @JsonView({View.Persistence.class, View.Public.class}) public void setGridOffsetY(int gridOffsetY) { this.gridOffsetY = gridOffsetY; } @ApiModelProperty(required = true, value = "the grid zoom lvl") + @JsonView({View.Persistence.class, View.Public.class}) public int getGridZoomLvl() { return gridZoomLvl; } + @JsonView({View.Persistence.class, View.Public.class}) public void setGridZoomLvl(int gridZoomLvl) { this.gridZoomLvl = gridZoomLvl; } @ApiModelProperty(required = true, value = "the grid X size") + @JsonView({View.Persistence.class, View.Public.class}) public int getGridSizeX() { return gridSizeX; } + @JsonView({View.Persistence.class, View.Public.class}) public void setGridSizeX(int gridSizeX) { this.gridSizeX = gridSizeX; } @ApiModelProperty(required = true, value = "the grid Y size") + @JsonView({View.Persistence.class, View.Public.class}) public int getGridSizeY() { return gridSizeY; } + @JsonView({View.Persistence.class, View.Public.class}) public void setGridSizeY(int gridSizeY) { this.gridSizeY = gridSizeY; } @ApiModelProperty(required = true, value = "true for hidden grid") + @JsonView({View.Persistence.class, View.Public.class}) public boolean getGridIsHidden() { return gridIsHidden; } + @JsonView({View.Persistence.class, View.Public.class}) public void setGridIsHidden(boolean gridIsHidden) { this.gridIsHidden = gridIsHidden; } @ApiModelProperty(required = true, value = "the image rotation") + @JsonView({View.Persistence.class, View.Public.class}) public int getImgRotation() { return imgRotation; } + @JsonView({View.Persistence.class, View.Public.class}) public void setImgRotation(int imgRotation) { this.imgRotation = imgRotation; } @ApiModelProperty(required = true, value = "the toggled left fields") + @JsonView({View.Persistence.class, View.Public.class}) public boolean getToggleFieldsLeft() { return toggleFieldsLeft; } + @JsonView({View.Persistence.class, View.Public.class}) public void setToggleFieldsLeft(boolean toggleFieldsLeft) { this.toggleFieldsLeft = toggleFieldsLeft; } @ApiModelProperty(required = true, value = "the number of clickable fields") + @JsonView({View.Persistence.class, View.Public.class}) public int getNumClickableFields() { return numClickableFields; } + @JsonView({View.Persistence.class, View.Public.class}) public void setNumClickableFields(int numClickableFields) { this.numClickableFields = numClickableFields; } @ApiModelProperty(required = true, value = "the threshold of correct answers") + @JsonView({View.Persistence.class, View.Public.class}) public int getThresholdCorrectAnswers() { return thresholdCorrectAnswers; } + @JsonView({View.Persistence.class, View.Public.class}) public void setThresholdCorrectAnswers(int thresholdCorrectAnswers) { this.thresholdCorrectAnswers = thresholdCorrectAnswers; } @ApiModelProperty(required = true, value = "the grid line color") + @JsonView({View.Persistence.class, View.Public.class}) public String getGridLineColor() { return gridLineColor; } + @JsonView({View.Persistence.class, View.Public.class}) public void setGridLineColor(String gridLineColor) { this.gridLineColor = gridLineColor; } @ApiModelProperty(required = true, value = "the number of dots") + @JsonView({View.Persistence.class, View.Public.class}) public int getNumberOfDots() { return numberOfDots; } + @JsonView({View.Persistence.class, View.Public.class}) public void setNumberOfDots(int numberOfDots) { this.numberOfDots = numberOfDots; } @ApiModelProperty(required = true, value = "the grid type") + @JsonView({View.Persistence.class, View.Public.class}) public String getGridType() { return gridType; } + @JsonView({View.Persistence.class, View.Public.class}) public void setGridType(String gridType) { this.gridType = gridType; } + @JsonView({View.Persistence.class, View.Public.class}) public void setScaleFactor(String scaleFactor) { this.scaleFactor = scaleFactor; } @ApiModelProperty(required = true, value = "the image scale factor") + @JsonView({View.Persistence.class, View.Public.class}) public String getScaleFactor() { return this.scaleFactor; } + @JsonView({View.Persistence.class, View.Public.class}) public void setGridScaleFactor(String scaleFactor) { this.gridScaleFactor = scaleFactor; } @ApiModelProperty(required = true, value = "the grid scale factor") + @JsonView({View.Persistence.class, View.Public.class}) public String getGridScaleFactor() { return this.gridScaleFactor; } @ApiModelProperty(required = true, value = "true for a question that can be answered via text") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isTextAnswerEnabled() { return this.textAnswerEnabled; } + @JsonView({View.Persistence.class, View.Public.class}) public void setTextAnswerEnabled(boolean textAnswerEnabled) { this.textAnswerEnabled = textAnswerEnabled; } @ApiModelProperty(required = true, value = "true for disabled voting") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isVotingDisabled() { return votingDisabled; } + @JsonView({View.Persistence.class, View.Public.class}) public void setVotingDisabled(boolean votingDisabled) { this.votingDisabled = votingDisabled; } + @JsonView({View.Persistence.class, View.Public.class}) public String getHint() { return hint; } + @JsonView({View.Persistence.class, View.Public.class}) public void setHint(String hint) { this.hint = hint; } + @JsonView({View.Persistence.class, View.Public.class}) public String getSolution() { return solution; } + @JsonView({View.Persistence.class, View.Public.class}) public void setSolution(String solution) { this.solution = solution; } @Override public final String toString() { - return "Question type '" + type + "': " + subject + ";\n" + text + possibleAnswers; + return "Content type '" + questionType + "': " + subject + ";\n" + text + possibleAnswers; } @Override @@ -623,7 +715,7 @@ public class Question implements Serializable { // auto generated! final int prime = 31; int result = 1; - result = prime * result + ((_id == null) ? 0 : _id.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @@ -639,12 +731,12 @@ public class Question implements Serializable { if (getClass() != obj.getClass()) { return false; } - Question other = (Question) obj; - if (_id == null) { - if (other._id != null) { + Content other = (Content) obj; + if (id == null) { + if (other.id != null) { return false; } - } else if (!_id.equals(other._id)) { + } else if (!id.equals(other.id)) { return false; } return true; diff --git a/src/main/java/de/thm/arsnova/entities/DbUser.java b/src/main/java/de/thm/arsnova/entities/DbUser.java index 75d9ba712d373b262cd2c36e9553b655c8ac89bc..0bdc45121c35f9975ae0cb33fba49d53cdcf0fd7 100644 --- a/src/main/java/de/thm/arsnova/entities/DbUser.java +++ b/src/main/java/de/thm/arsnova/entities/DbUser.java @@ -17,10 +17,13 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; + /** * A user account for ARSnova's own registration and login process. */ -public class DbUser { +public class DbUser implements Entity { private String id; private String rev; private String username; @@ -31,90 +34,93 @@ public class DbUser { private long creation; private long lastLogin; + @JsonView(View.Persistence.class) public String getId() { return id; } + @JsonView(View.Persistence.class) public void setId(String id) { this.id = id; } - /* CouchDB deserialization */ - public void set_id(String id) { - this.id = id; - } - + @JsonView(View.Persistence.class) public String getRev() { return rev; } + @JsonView(View.Persistence.class) public void setRev(String rev) { this.rev = rev; } - /* CouchDB deserialization */ - public void set_rev(String rev) { - this.rev = rev; - } - + @JsonView({View.Persistence.class, View.Public.class}) public String getUsername() { return username; } + @JsonView({View.Persistence.class, View.Public.class}) public void setUsername(String username) { this.username = username; } + @JsonView(View.Persistence.class) public String getPassword() { return password; } + @JsonView(View.Persistence.class) public void setPassword(String password) { this.password = password; } + @JsonView(View.Persistence.class) public String getActivationKey() { return activationKey; } + @JsonView(View.Persistence.class) public void setActivationKey(String activationKey) { this.activationKey = activationKey; } + @JsonView(View.Persistence.class) public String getPasswordResetKey() { return passwordResetKey; } + @JsonView(View.Persistence.class) public void setPasswordResetKey(String passwordResetKey) { this.passwordResetKey = passwordResetKey; } + @JsonView(View.Persistence.class) public long getPasswordResetTime() { return passwordResetTime; } + @JsonView(View.Persistence.class) public void setPasswordResetTime(long passwordResetTime) { this.passwordResetTime = passwordResetTime; } + @JsonView(View.Persistence.class) public long getCreation() { return creation; } + @JsonView(View.Persistence.class) public void setCreation(long creation) { this.creation = creation; } + @JsonView(View.Persistence.class) public long getLastLogin() { return lastLogin; } + @JsonView(View.Persistence.class) public void setLastLogin(long lastLogin) { this.lastLogin = lastLogin; } - - /* CouchDB deserialization */ - public void setType(String type) { - /* no op */ - } } diff --git a/src/main/java/de/thm/arsnova/entities/Entity.java b/src/main/java/de/thm/arsnova/entities/Entity.java new file mode 100644 index 0000000000000000000000000000000000000000..4de04ab4b7d1923cbfce3a50649e1af520db468f --- /dev/null +++ b/src/main/java/de/thm/arsnova/entities/Entity.java @@ -0,0 +1,31 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.entities; + +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; + +public interface Entity { + String getId(); + void setId(String id); + + @JsonView(View.Persistence.class) + default Class<? extends Entity> getType() { + return getClass(); + } +} diff --git a/src/main/java/de/thm/arsnova/entities/Feedback.java b/src/main/java/de/thm/arsnova/entities/Feedback.java index c50480f094e537aed33f579f4d71f94e1ed83313..dcccb924cfa4e7a713abd310128ccf6fab8bc707 100644 --- a/src/main/java/de/thm/arsnova/entities/Feedback.java +++ b/src/main/java/de/thm/arsnova/entities/Feedback.java @@ -17,6 +17,9 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; + import java.util.ArrayList; import java.util.List; @@ -42,6 +45,7 @@ public class Feedback { values.add(d); } + @JsonView(View.Public.class) public final List<Integer> getValues() { return values; } diff --git a/src/main/java/de/thm/arsnova/entities/LearningProgressOptions.java b/src/main/java/de/thm/arsnova/entities/LearningProgressOptions.java index 4d682884a1b50bb7e7f60077bc92c710a0f854da..342d2c29ae9fda8142356792dadd863211d1b9ee 100644 --- a/src/main/java/de/thm/arsnova/entities/LearningProgressOptions.java +++ b/src/main/java/de/thm/arsnova/entities/LearningProgressOptions.java @@ -17,6 +17,8 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -41,19 +43,23 @@ public class LearningProgressOptions implements Serializable { public LearningProgressOptions() { } @ApiModelProperty(required = true, value = "the type") + @JsonView({View.Persistence.class, View.Public.class}) public String getType() { return type; } + @JsonView({View.Persistence.class, View.Public.class}) public void setType(String learningProgressType) { this.type = learningProgressType; } @ApiModelProperty(required = true, value = "either lecture or preparation") + @JsonView({View.Persistence.class, View.Public.class}) public String getQuestionVariant() { return questionVariant; } + @JsonView({View.Persistence.class, View.Public.class}) public void setQuestionVariant(String questionVariant) { this.questionVariant = questionVariant; } diff --git a/src/main/java/de/thm/arsnova/entities/LogEntry.java b/src/main/java/de/thm/arsnova/entities/LogEntry.java index d4e20cc1706675d4d8c9aeebfa3a88d3fc2918d3..f317f053bae15ae5205374b37339750d402d5846 100644 --- a/src/main/java/de/thm/arsnova/entities/LogEntry.java +++ b/src/main/java/de/thm/arsnova/entities/LogEntry.java @@ -17,9 +17,13 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; + import java.util.Map; -public class LogEntry { +public class LogEntry implements Entity { public enum LogLevel { TRACE, DEBUG, @@ -31,69 +35,78 @@ public class LogEntry { private String id; private String rev; - private long timestamp; + private long timestamp = System.currentTimeMillis(); private String event; private int level; private Map<String, Object> payload; - public String getId() { - return id; + public LogEntry(@JsonProperty String event, @JsonProperty int level, @JsonProperty Map<String, Object> payload) { + this.event = event; + this.level = level; + this.payload = payload; } - public void setId(String id) { - this.id = id; + @JsonView(View.Persistence.class) + public String getId() { + return id; } - /* CouchDB deserialization */ - public void set_id(String id) { + @JsonView(View.Persistence.class) + public void setId(final String id) { this.id = id; } - public String getRev() { - return rev; - } - - public void setRev(String rev) { + @JsonView(View.Persistence.class) + public void setRevision(final String rev) { this.rev = rev; } - /* CouchDB deserialization */ - public void set_rev(String rev) { - this.rev = rev; + @JsonView(View.Persistence.class) + public String getRevision() { + return rev; } + @JsonView(View.Persistence.class) public long getTimestamp() { return timestamp; } + @JsonView(View.Persistence.class) public void setTimestamp(long timestamp) { this.timestamp = timestamp; } + @JsonView(View.Persistence.class) public String getEvent() { return event; } + @JsonView(View.Persistence.class) public void setEvent(String event) { this.event = event; } + @JsonView(View.Persistence.class) public int getLevel() { return level; } + @JsonView(View.Persistence.class) public void setLevel(int level) { this.level = level; } + @JsonView(View.Persistence.class) public void setLevel(LogLevel level) { this.level = level.ordinal(); } + @JsonView(View.Persistence.class) public Map<String, Object> getPayload() { return payload; } + @JsonView(View.Persistence.class) public void setPayload(Map<String, Object> payload) { this.payload = payload; } diff --git a/src/main/java/de/thm/arsnova/entities/LoggedIn.java b/src/main/java/de/thm/arsnova/entities/LoggedIn.java index 69f0eebf187547c57c016cb4fc49bc08042db0fd..cb9982c88fe24ee2219a6bb5114f1e259639a8df 100644 --- a/src/main/java/de/thm/arsnova/entities/LoggedIn.java +++ b/src/main/java/de/thm/arsnova/entities/LoggedIn.java @@ -17,25 +17,24 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; + import java.util.ArrayList; import java.util.List; /** * Once a user joins a session, this class is used to identify a returning user. */ -public class LoggedIn { - - private String _id; - private String _rev; - private String type; +public class LoggedIn implements Entity { + private String id; + private String rev; private String user; private String sessionId; private long timestamp; private List<VisitedSession> visitedSessions = new ArrayList<>(); - private List<String> _conflicts; public LoggedIn() { - this.type = "logged_in"; this.updateTimestamp(); } @@ -47,88 +46,80 @@ public class LoggedIn { private boolean isAlreadyVisited(Session s) { for (VisitedSession vs : this.visitedSessions) { - if (vs.get_id().equals(s.get_id())) { + if (vs.getId().equals(s.getId())) { return true; } } return false; } - public void updateTimestamp() { - this.timestamp = System.currentTimeMillis(); - } - - public String get_id() { - return _id; - } - - public void set_id(String _id) { - this._id = _id; + @JsonView(View.Persistence.class) + public String getId() { + return id; } - public String get_rev() { - return _rev; + @JsonView(View.Persistence.class) + public void setId(final String id) { + this.id = id; } - public void set_rev(String _rev) { - this._rev = _rev; + @JsonView(View.Persistence.class) + public String getRevision() { + return rev; } - public String getType() { - return type; + @JsonView(View.Persistence.class) + public void setRevision(final String rev) { + this.rev = rev; } - public void setType(String type) { - this.type = type; + public void updateTimestamp() { + this.timestamp = System.currentTimeMillis(); } + @JsonView(View.Persistence.class) public String getUser() { return user; } + @JsonView(View.Persistence.class) public void setUser(String user) { this.user = user; } + @JsonView(View.Persistence.class) public String getSessionId() { return sessionId; } + @JsonView(View.Persistence.class) public void setSessionId(String sessionId) { this.sessionId = sessionId; } + @JsonView(View.Persistence.class) public long getTimestamp() { return timestamp; } + @JsonView(View.Persistence.class) public void setTimestamp(long timestamp) { this.timestamp = timestamp; } + @JsonView(View.Persistence.class) public List<VisitedSession> getVisitedSessions() { return visitedSessions; } + @JsonView(View.Persistence.class) public void setVisitedSessions(List<VisitedSession> visitedSessions) { this.visitedSessions = visitedSessions; } - public List<String> get_conflicts() { - return _conflicts; - } - - public void set_conflicts(List<String> _conflicts) { - this._conflicts = _conflicts; - } - - public boolean hasConflicts() { - return !(_conflicts == null || _conflicts.isEmpty()); - } - @Override public String toString() { - return "LoggedIn [_id=" + _id + ", _rev=" + _rev + ", type=" + type + return "LoggedIn [id=" + id + ", rev=" + rev + ", type=" + getType() + ", user=" + user + ", sessionId=" + sessionId + ", timestamp=" + timestamp + ", visitedSessions=" + visitedSessions + "]"; diff --git a/src/main/java/de/thm/arsnova/entities/Motd.java b/src/main/java/de/thm/arsnova/entities/Motd.java index 35864703d31170f44226a2ffbeba76f8d8607685..1b4ea1d73d9b604abe74f995c3057ffad83d392b 100644 --- a/src/main/java/de/thm/arsnova/entities/Motd.java +++ b/src/main/java/de/thm/arsnova/entities/Motd.java @@ -17,6 +17,8 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -26,7 +28,7 @@ import java.util.Date; * This class represents a message of the day. */ @ApiModel(value = "motd", description = "the message of the day entity") -public class Motd { +public class Motd implements Entity { private String motdkey; //ID private Date startdate; @@ -36,95 +38,115 @@ public class Motd { private String audience; private String sessionId; private String sessionkey; - private String _id; - private String _rev; + private String id; + private String rev; @ApiModelProperty(required = true, value = "the identification string") + @JsonView({View.Persistence.class, View.Public.class}) public String getMotdkey() { return motdkey; } + @JsonView({View.Persistence.class, View.Public.class}) public void setMotdkey(final String key) { motdkey = key; } @ApiModelProperty(required = true, value = "startdate for showing this message (timestamp format)") + @JsonView({View.Persistence.class, View.Public.class}) public Date getStartdate() { return startdate; } + @JsonView({View.Persistence.class, View.Public.class}) public void setStartdate(final Date timestamp) { startdate = timestamp; } @ApiModelProperty(required = true, value = "enddate for showing this message (timestamp format)") + @JsonView({View.Persistence.class, View.Public.class}) public Date getEnddate() { return enddate; } + @JsonView({View.Persistence.class, View.Public.class}) public void setEnddate(final Date timestamp) { enddate = timestamp; } @ApiModelProperty(required = true, value = "tite of the message") + @JsonView({View.Persistence.class, View.Public.class}) public String getTitle() { return title; } + @JsonView({View.Persistence.class, View.Public.class}) public void setTitle(final String ttitle) { title = ttitle; } @ApiModelProperty(required = true, value = "text of the message") + @JsonView({View.Persistence.class, View.Public.class}) public String getText() { return text; } + @JsonView({View.Persistence.class, View.Public.class}) public void setText(final String ttext) { text = ttext; } @ApiModelProperty(required = true, value = "defines the target audience for this motd (one of the following: 'student', 'tutor', 'loggedIn', 'all')") + @JsonView({View.Persistence.class, View.Public.class}) public String getAudience() { return audience; } + @JsonView({View.Persistence.class, View.Public.class}) public void setAudience(String a) { audience = a; } + @JsonView({View.Persistence.class, View.Public.class}) public String getSessionId() { return sessionId; } + @JsonView({View.Persistence.class, View.Public.class}) public void setSessionId(String sessionId) { this.sessionId = sessionId; } @ApiModelProperty(required = true, value = "when audience equals session, the sessionkey referes to the session the messages belong to") + @JsonView({View.Persistence.class, View.Public.class}) public String getSessionkey() { return sessionkey; } + @JsonView({View.Persistence.class, View.Public.class}) public void setSessionkey(String a) { sessionkey = a; } @ApiModelProperty(required = true, value = "the couchDB ID") - public String get_id() { - return _id; + @JsonView({View.Persistence.class, View.Public.class}) + public String getId() { + return id; } - public void set_id(final String id) { - _id = id; + @JsonView({View.Persistence.class, View.Public.class}) + public void setId(final String id) { + this.id = id; } - public void set_rev(final String rev) { - _rev = rev; + @JsonView({View.Persistence.class, View.Public.class}) + public void setRevision(final String rev) { + this.rev = rev; } - public String get_rev() { - return _rev; + @JsonView({View.Persistence.class, View.Public.class}) + public String getRevision() { + return rev; } @Override diff --git a/src/main/java/de/thm/arsnova/entities/MotdList.java b/src/main/java/de/thm/arsnova/entities/MotdList.java index 307fb6696398d1b95e94b581f9a389e48960fae8..35b611fee0c03f8f62f5201d982b5cf645ae08ac 100644 --- a/src/main/java/de/thm/arsnova/entities/MotdList.java +++ b/src/main/java/de/thm/arsnova/entities/MotdList.java @@ -17,6 +17,8 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -24,45 +26,52 @@ import io.swagger.annotations.ApiModelProperty; * This class represents a list of motdkeys for a user. */ @ApiModel(value = "motdlist", description = "the motdlist to save the messages a user has confirmed to be read") -public class MotdList { - +public class MotdList implements Entity { + private String id; + private String rev; private String motdkeys; private String username; - private String _id; - private String _rev; + + @ApiModelProperty(required = true, value = "the couchDB ID") + @JsonView({View.Persistence.class, View.Public.class}) + public String getId() { + return id; + } + + @JsonView({View.Persistence.class, View.Public.class}) + public void setId(final String id) { + this.id = id; + } + + @JsonView({View.Persistence.class, View.Public.class}) + public void setRevision(final String rev) { + this.rev = rev; + } + + @JsonView({View.Persistence.class, View.Public.class}) + public String getRevision() { + return rev; + } @ApiModelProperty(required = true, value = "the motdkeylist") + @JsonView({View.Persistence.class, View.Public.class}) public String getMotdkeys() { return motdkeys; } + @JsonView({View.Persistence.class, View.Public.class}) public void setMotdkeys(String motds) { motdkeys = motds; } @ApiModelProperty(required = true, value = "the username") + @JsonView({View.Persistence.class, View.Public.class}) public String getUsername() { return username; } + @JsonView({View.Persistence.class, View.Public.class}) public void setUsername(final String u) { username = u; } - - @ApiModelProperty(required = true, value = "the couchDB ID") - public String get_id() { - return _id; - } - - public void set_id(final String id) { - _id = id; - } - - public void set_rev(final String rev) { - _rev = rev; - } - - public String get_rev() { - return _rev; - } } diff --git a/src/main/java/de/thm/arsnova/entities/PossibleAnswer.java b/src/main/java/de/thm/arsnova/entities/PossibleAnswer.java index 0b7fa7983f2dfadf0a92c1ea9e32002e5b87a995..f3bfb039ade4d7ed66a4d8e8b13125004e01c1cf 100644 --- a/src/main/java/de/thm/arsnova/entities/PossibleAnswer.java +++ b/src/main/java/de/thm/arsnova/entities/PossibleAnswer.java @@ -17,6 +17,8 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -43,28 +45,34 @@ public class PossibleAnswer implements Serializable { } @ApiModelProperty(required = true, value = "the text") + @JsonView({View.Persistence.class, View.Public.class}) public String getText() { return text; } + @JsonView({View.Persistence.class, View.Public.class}) public void setText(String text) { this.text = text; } @ApiModelProperty(required = true, value = "true for a correct answer") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isCorrect() { return correct; } + @JsonView({View.Persistence.class, View.Public.class}) public void setCorrect(boolean correct) { this.correct = correct; } @ApiModelProperty(required = true, value = "the value") + @JsonView({View.Persistence.class, View.Public.class}) public int getValue() { return value; } + @JsonView({View.Persistence.class, View.Public.class}) public void setValue(int value) { this.value = value; } diff --git a/src/main/java/de/thm/arsnova/entities/ServiceDescription.java b/src/main/java/de/thm/arsnova/entities/ServiceDescription.java index 421cc20471345e4483acbbec26dc4dbd8140657a..96d1251788186cb93ead776b33be90f634f55ad2 100644 --- a/src/main/java/de/thm/arsnova/entities/ServiceDescription.java +++ b/src/main/java/de/thm/arsnova/entities/ServiceDescription.java @@ -17,6 +17,9 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; + /** * A login service description. For example, this class is used to display the login buttons in ARSnova mobile. */ @@ -51,6 +54,7 @@ public class ServiceDescription { } } + @JsonView(View.Public.class) public String getId() { return id; } @@ -59,6 +63,7 @@ public class ServiceDescription { this.id = id; } + @JsonView(View.Public.class) public String getName() { return name; } @@ -67,6 +72,7 @@ public class ServiceDescription { this.name = name; } + @JsonView(View.Public.class) public String getDialogUrl() { return dialogUrl; } @@ -75,6 +81,7 @@ public class ServiceDescription { this.dialogUrl = dialogUrl; } + @JsonView(View.Public.class) public String getImage() { return image; } @@ -83,6 +90,7 @@ public class ServiceDescription { this.image = image; } + @JsonView(View.Public.class) public int getOrder() { return order; } @@ -91,6 +99,7 @@ public class ServiceDescription { this.order = order; } + @JsonView(View.Public.class) public String[] getAllowedRoles() { return allowedRoles; } diff --git a/src/main/java/de/thm/arsnova/entities/Session.java b/src/main/java/de/thm/arsnova/entities/Session.java index 28ad60ecc01caaae4b8230bdd6fd1085d10f5628..65bf6c6263075a06a9f40260e202ea676a88dea1 100644 --- a/src/main/java/de/thm/arsnova/entities/Session.java +++ b/src/main/java/de/thm/arsnova/entities/Session.java @@ -18,21 +18,18 @@ package de.thm.arsnova.entities; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; -import java.io.Serializable; -import java.util.List; - /** * Represents an ARSnova session. */ @ApiModel(value = "session", description = "the session entity") -public class Session implements Serializable { - - private static final long serialVersionUID = 1L; - - private String type; +public class Session implements Entity { + private String id; + private String rev; private String name; private String shortName; private String keyword; @@ -41,7 +38,6 @@ public class Session implements Serializable { private long lastOwnerActivity; private String courseType; private String courseId; - private List<String> _conflicts; private long creationTime; private LearningProgressOptions learningProgressOptions = new LearningProgressOptions(); private SessionFeature features = new SessionFeature(); @@ -59,9 +55,6 @@ public class Session implements Serializable { private boolean feedbackLock; private boolean flipFlashcards; - private String _id; - private String _rev; - /** * Returns a copy of the given session without any information that identifies a person. * @param original The session to create a anonymized copy of @@ -69,7 +62,6 @@ public class Session implements Serializable { */ public static Session anonymizedCopy(final Session original) { final Session copy = new Session(); - copy.type = original.type; copy.name = original.name; copy.shortName = original.shortName; copy.keyword = original.keyword; @@ -95,118 +87,119 @@ public class Session implements Serializable { copy.feedbackLock = original.feedbackLock; copy.flipFlashcards = original.flipFlashcards; - copy._id = original._id; - copy._rev = original._rev; + copy.id = original.id; + copy.rev = original.rev; return copy; } - @ApiModelProperty(required = true, value = "\"session\" - used to filter in the couchDB") - public String getType() { - return type; + @JsonView({View.Persistence.class, View.Public.class}) + public String getId() { + return id; + } + + @JsonView({View.Persistence.class, View.Public.class}) + public void setId(final String id) { + this.id = id; + } + + @JsonView({View.Persistence.class, View.Public.class}) + public String getRevision() { + return rev; } - public void setType(final String type) { - this.type = type; + @JsonView({View.Persistence.class, View.Public.class}) + public void setRevision(final String rev) { + this.rev = rev; } @ApiModelProperty(required = true, value = "the name") + @JsonView({View.Persistence.class, View.Public.class}) public String getName() { return name; } + @JsonView({View.Persistence.class, View.Public.class}) public void setName(final String name) { this.name = name; } @ApiModelProperty(required = true, value = "the short name") + @JsonView({View.Persistence.class, View.Public.class}) public String getShortName() { return shortName; } + @JsonView({View.Persistence.class, View.Public.class}) public void setShortName(final String shortName) { this.shortName = shortName; } @ApiModelProperty(required = true, value = "the keyword") + @JsonView({View.Persistence.class, View.Public.class}) public String getKeyword() { return keyword; } + @JsonView({View.Persistence.class, View.Public.class}) public void setKeyword(final String keyword) { this.keyword = keyword; } @ApiModelProperty(required = true, value = "the session creator") + @JsonView(View.Persistence.class) public String getCreator() { return creator; } + @JsonView(View.Persistence.class) public void setCreator(final String creator) { this.creator = creator; } @ApiModelProperty(required = true, value = "true for active session") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isActive() { return active; } + @JsonView({View.Persistence.class, View.Public.class}) public void setActive(final boolean active) { this.active = active; } @ApiModelProperty(required = true, value = "timestamp from the last activity of the owner") + @JsonView(View.Persistence.class) public long getLastOwnerActivity() { return lastOwnerActivity; } + @JsonView(View.Persistence.class) public void setLastOwnerActivity(final long lastOwnerActivity) { this.lastOwnerActivity = lastOwnerActivity; } - public void set_id(final String id) { - _id = id; - } - - @ApiModelProperty(required = true, value = "the couchDB ID") - public String get_id() { - return _id; - } - - public void set_rev(final String rev) { - _rev = rev; - } - - public String get_rev() { - return _rev; - } - - public void set_conflicts(final List<String> conflicts) { - _conflicts = conflicts; - } - - @ApiModelProperty(required = true, value = "potential couchDB conflicts") - public List<String> get_conflicts() { - return _conflicts; - } - public boolean isCreator(final User user) { return user.getUsername().equals(creator); } @ApiModelProperty(required = true, value = "the source the course comes from (example: moodle)") + @JsonView({View.Persistence.class, View.Public.class}) public String getCourseType() { return courseType; } + @JsonView({View.Persistence.class, View.Public.class}) public void setCourseType(final String courseType) { this.courseType = courseType; } @ApiModelProperty(required = true, value = "the course ID") + @JsonView({View.Persistence.class, View.Public.class}) public String getCourseId() { return courseId; } + @JsonView({View.Persistence.class, View.Public.class}) public void setCourseId(final String courseId) { this.courseId = courseId; } @@ -217,143 +210,173 @@ public class Session implements Serializable { } @ApiModelProperty(required = true, value = "creation timestamp") + @JsonView({View.Persistence.class, View.Public.class}) public long getCreationTime() { return creationTime; } + @JsonView(View.Persistence.class) public void setCreationTime(long creationTime) { this.creationTime = creationTime; } @ApiModelProperty(required = true, value = "the learning progress options") + @JsonView({View.Persistence.class, View.Public.class}) public LearningProgressOptions getLearningProgressOptions() { return learningProgressOptions; } + @JsonView({View.Persistence.class, View.Public.class}) public void setLearningProgressOptions(LearningProgressOptions learningProgressOptions) { this.learningProgressOptions = learningProgressOptions; } @ApiModelProperty(required = true, value = "the enabled features (e.g. feedback, interposed, learning Progress, lecture)") + @JsonView({View.Persistence.class, View.Public.class}) public SessionFeature getFeatures() { return features; } + @JsonView({View.Persistence.class, View.Public.class}) public void setFeatures(SessionFeature features) { this.features = features; } @ApiModelProperty(required = true, value = "the public pool author name") + @JsonView({View.Persistence.class, View.Public.class}) public String getPpAuthorName() { return ppAuthorName; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPpAuthorName(final String ppAuthorName) { this.ppAuthorName = ppAuthorName; } @ApiModelProperty(required = true, value = "the public pool author email") + @JsonView({View.Persistence.class, View.Public.class}) public String getPpAuthorMail() { return ppAuthorMail; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPpAuthorMail(final String ppAuthorMail) { this.ppAuthorMail = ppAuthorMail; } @ApiModelProperty(required = true, value = "the public pool university") + @JsonView({View.Persistence.class, View.Public.class}) public String getPpUniversity() { return ppUniversity; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPpUniversity(final String ppUniversity) { this.ppUniversity = ppUniversity; } @ApiModelProperty(required = true, value = "the public pool logo") + @JsonView({View.Persistence.class, View.Public.class}) public String getPpLogo() { return ppLogo; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPpLogo(final String ppLogo) { this.ppLogo = ppLogo; } @ApiModelProperty(required = true, value = "used to display subject") + @JsonView({View.Persistence.class, View.Public.class}) public String getPpSubject() { return ppSubject; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPpSubject(final String ppSubject) { this.ppSubject = ppSubject; } @ApiModelProperty(required = true, value = "the public pool license") + @JsonView({View.Persistence.class, View.Public.class}) public String getPpLicense() { return ppLicense; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPpLicense(final String ppLicense) { this.ppLicense = ppLicense; } @ApiModelProperty(required = true, value = "the public pool description") + @JsonView({View.Persistence.class, View.Public.class}) public String getPpDescription() { return ppDescription; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPpDescription(final String ppDescription) { this.ppDescription = ppDescription; } @ApiModelProperty(required = true, value = "the public pool faculty") + @JsonView({View.Persistence.class, View.Public.class}) public String getPpFaculty() { return ppFaculty; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPpFaculty(final String ppFaculty) { this.ppFaculty = ppFaculty; } @ApiModelProperty(required = true, value = "the public pool level") + @JsonView({View.Persistence.class, View.Public.class}) public String getPpLevel() { return ppLevel; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPpLevel(final String ppLevel) { this.ppLevel = ppLevel; } @ApiModelProperty(required = true, value = "the session type") + @JsonView({View.Persistence.class, View.Public.class}) public String getSessionType() { return sessionType; } + @JsonView({View.Persistence.class, View.Public.class}) public void setSessionType(final String sessionType) { this.sessionType = sessionType; } @ApiModelProperty(required = true, value = "the feedback lock status") + @JsonView({View.Persistence.class, View.Public.class}) public boolean getFeedbackLock() { return feedbackLock; } + @JsonView({View.Persistence.class, View.Public.class}) public void setFeedbackLock(Boolean lock) { this.feedbackLock = lock; } @ApiModelProperty(required = true, value = "the flashcard flip condition") + @JsonView({View.Persistence.class, View.Public.class}) public boolean getFlipFlashcards() { return flipFlashcards; } + @JsonView({View.Persistence.class, View.Public.class}) public void setFlipFlashcards(Boolean flip) { this.flipFlashcards = flip; } @Override public String toString() { - return "Session [keyword=" + keyword + ", type=" + type + ", creator=" + creator + "]"; + return "Session [keyword=" + keyword + ", type=" + getType() + ", creator=" + creator + "]"; } @Override diff --git a/src/main/java/de/thm/arsnova/entities/SessionFeature.java b/src/main/java/de/thm/arsnova/entities/SessionFeature.java index fd3ac40f56f552fff63c4fe2ac34bfb5ef02fb26..e04390add673beb3c89c54e8a85bde3cd7d1dc2e 100644 --- a/src/main/java/de/thm/arsnova/entities/SessionFeature.java +++ b/src/main/java/de/thm/arsnova/entities/SessionFeature.java @@ -17,6 +17,8 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -72,143 +74,177 @@ public class SessionFeature implements Serializable { public SessionFeature() { } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isLecture() { return lecture; } + @JsonView({View.Persistence.class, View.Public.class}) public void setLecture(boolean lecture) { this.lecture = lecture; } @ApiModelProperty(required = true, value = "jitt") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isJitt() { return jitt; } + @JsonView({View.Persistence.class, View.Public.class}) public void setJitt(boolean jitt) { this.jitt = jitt; } @ApiModelProperty(required = true, value = "feedback") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isFeedback() { return feedback; } + @JsonView({View.Persistence.class, View.Public.class}) public void setFeedback(boolean feedback) { this.feedback = feedback; } @ApiModelProperty(required = true, value = "interposed") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isInterposed() { return interposed; } + @JsonView({View.Persistence.class, View.Public.class}) public void setInterposed(boolean interposed) { this.interposed = interposed; } @ApiModelProperty(required = true, value = "peer instruction") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isPi() { return pi; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPi(boolean pi) { this.pi = pi; } @ApiModelProperty(required = true, value = "learning progress") + @JsonView({View.Persistence.class, View.Public.class}) public boolean isLearningProgress() { return learningProgress; } + @JsonView({View.Persistence.class, View.Public.class}) public void setLearningProgress(boolean learningProgress) { this.learningProgress = learningProgress; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isCustom() { return custom; } + @JsonView({View.Persistence.class, View.Public.class}) public void setCustom(boolean custom) { this.custom = custom; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isClicker() { return clicker; } + @JsonView({View.Persistence.class, View.Public.class}) public void setClicker(boolean clicker) { this.clicker = clicker; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isPeerGrading() { return peerGrading; } + @JsonView({View.Persistence.class, View.Public.class}) public void setPeerGrading(boolean peerGrading) { this.peerGrading = peerGrading; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isFlashcardFeature() { return flashcardFeature; } + @JsonView({View.Persistence.class, View.Public.class}) public void setFlashcardFeature(boolean flashcardFeature) { this.flashcardFeature = flashcardFeature; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isFlashcard() { return flashcard; } + @JsonView({View.Persistence.class, View.Public.class}) public void setFlashcard(boolean flashcard) { this.flashcard = flashcard; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isTotal() { return total; } + @JsonView({View.Persistence.class, View.Public.class}) public void setTotal(boolean total) { this.total = total; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isLiveFeedback() { return liveFeedback; } + @JsonView({View.Persistence.class, View.Public.class}) public void setLiveFeedback(boolean liveFeedback) { this.liveFeedback = liveFeedback; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isInterposedFeedback() { return interposedFeedback; } + @JsonView({View.Persistence.class, View.Public.class}) public void setInterposedFeedback(boolean interposedFeedback) { this.interposedFeedback = interposedFeedback; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isLiveClicker() { return liveClicker; } + @JsonView({View.Persistence.class, View.Public.class}) public void setLiveClicker(boolean liveClicker) { this.liveClicker = liveClicker; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isTwitterWall() { return twitterWall; } + @JsonView({View.Persistence.class, View.Public.class}) public void setTwitterWall(boolean twitterWall) { this.twitterWall = twitterWall; } + @JsonView({View.Persistence.class, View.Public.class}) public boolean isSlides() { return slides; } + @JsonView({View.Persistence.class, View.Public.class}) public void setSlides(boolean slides) { this.slides = slides; } diff --git a/src/main/java/de/thm/arsnova/entities/SessionInfo.java b/src/main/java/de/thm/arsnova/entities/SessionInfo.java index e34227862f8f815e53779cd5cd7ef8fd3223ab9c..4c498982e38f7e65f88e2c1702f8b6f7722b6996 100644 --- a/src/main/java/de/thm/arsnova/entities/SessionInfo.java +++ b/src/main/java/de/thm/arsnova/entities/SessionInfo.java @@ -17,6 +17,8 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -42,8 +44,8 @@ public class SessionInfo { private int numQuestions; private int numAnswers; - private int numInterposed; - private int numUnredInterposed; + private int numComments; + private int numUnreadComments; private int numUnanswered; public SessionInfo(Session session) { @@ -69,6 +71,7 @@ public class SessionInfo { } @ApiModelProperty(required = true, value = "the name") + @JsonView(View.Public.class) public String getName() { return name; } @@ -78,6 +81,7 @@ public class SessionInfo { } @ApiModelProperty(required = true, value = "the short name") + @JsonView(View.Public.class) public String getShortName() { return shortName; } @@ -87,6 +91,7 @@ public class SessionInfo { } @ApiModelProperty(required = true, value = "the keyword") + @JsonView(View.Public.class) public String getKeyword() { return keyword; } @@ -96,6 +101,7 @@ public class SessionInfo { } @ApiModelProperty(required = true, value = "true for active") + @JsonView(View.Public.class) public boolean isActive() { return active; } @@ -105,6 +111,7 @@ public class SessionInfo { } @ApiModelProperty(required = true, value = "the source the course comes from (example: moodle)") + @JsonView(View.Public.class) public String getCourseType() { return courseType; } @@ -114,6 +121,7 @@ public class SessionInfo { } @ApiModelProperty(required = true, value = "the session type") + @JsonView(View.Public.class) public String getSessionType() { return sessionType; } @@ -123,6 +131,7 @@ public class SessionInfo { } @ApiModelProperty(required = true, value = "used to display level") + @JsonView(View.Public.class) public String getPpLevel() { return ppLevel; } @@ -132,6 +141,7 @@ public class SessionInfo { } @ApiModelProperty(required = true, value = "the public pool subject") + @JsonView(View.Public.class) public String getPpSubject() { return ppSubject; } @@ -141,6 +151,7 @@ public class SessionInfo { } @ApiModelProperty(required = true, value = "the number of questions") + @JsonView(View.Public.class) public int getNumQuestions() { return numQuestions; } @@ -150,6 +161,7 @@ public class SessionInfo { } @ApiModelProperty(required = true, value = "the number of answers") + @JsonView(View.Public.class) public int getNumAnswers() { return numAnswers; } @@ -158,16 +170,20 @@ public class SessionInfo { this.numAnswers = numAnswers; } - @ApiModelProperty(required = true, value = "used to display interposed number") + /* Still named "Interposed" instead of "Comments" here for compatibilty reasons. */ + @ApiModelProperty(required = true, value = "used to display comment number") + @JsonView(View.Public.class) public int getNumInterposed() { - return numInterposed; + return numComments; } - public void setNumInterposed(int numInterposed) { - this.numInterposed = numInterposed; + /* Still named "Interposed" instead of "Comments" here for compatibilty reasons. */ + public void setNumInterposed(int numComments) { + this.numComments = numComments; } @ApiModelProperty(required = true, value = "the number of unanswered questions") + @JsonView(View.Public.class) public int getNumUnanswered() { return numUnanswered; } @@ -177,6 +193,7 @@ public class SessionInfo { } @ApiModelProperty(required = true, value = "the creation timestamp") + @JsonView(View.Public.class) public long getCreationTime() { return creationTime; } @@ -185,13 +202,16 @@ public class SessionInfo { this.creationTime = creationTime; } - @ApiModelProperty(required = true, value = "the number of unread interposed questions") + /* Still named "Interposed" instead of "Comments" here for compatibilty reasons. */ + @ApiModelProperty(required = true, value = "the number of unread comments") + @JsonView(View.Public.class) public int getNumUnredInterposed() { - return numUnredInterposed; + return numUnreadComments; } - public void setNumUnredInterposed(int numUnredInterposed) { - this.numUnredInterposed = numUnredInterposed; + /* Still named "Interposed" instead of "Comments" here for compatibilty reasons. */ + public void setNumUnredInterposed(int numUnreadComments) { + this.numUnreadComments = numUnreadComments; } @Override diff --git a/src/main/java/de/thm/arsnova/entities/Statistics.java b/src/main/java/de/thm/arsnova/entities/Statistics.java index 49f9179730fea33687d094bc932a939409f442d0..c0635f0200b85b0b6baa50939d88e651d847a5de 100644 --- a/src/main/java/de/thm/arsnova/entities/Statistics.java +++ b/src/main/java/de/thm/arsnova/entities/Statistics.java @@ -17,6 +17,8 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -40,6 +42,7 @@ public class Statistics { private int flashcards; @ApiModelProperty(required = true, value = "the number of answers") + @JsonView(View.Public.class) public int getAnswers() { return answers; } @@ -49,6 +52,7 @@ public class Statistics { } @ApiModelProperty(required = true, value = "the number of lecture questions") + @JsonView(View.Public.class) public int getLectureQuestions() { return lectureQuestions; } @@ -58,6 +62,7 @@ public class Statistics { } @ApiModelProperty(required = true, value = "the number of prepartion uestions") + @JsonView(View.Public.class) public int getPreparationQuestions() { return preparationQuestions; } @@ -67,11 +72,13 @@ public class Statistics { } @ApiModelProperty(required = true, value = "the total number of questions") + @JsonView(View.Public.class) public int getQuestions() { return getLectureQuestions() + getPreparationQuestions(); } @ApiModelProperty(required = true, value = "the number of open sessions") + @JsonView(View.Public.class) public int getOpenSessions() { return openSessions; } @@ -81,6 +88,7 @@ public class Statistics { } @ApiModelProperty(required = true, value = "the number of closed Sessions") + @JsonView(View.Public.class) public int getClosedSessions() { return closedSessions; } @@ -90,11 +98,13 @@ public class Statistics { } @ApiModelProperty(required = true, value = "the total number of Sessions") + @JsonView(View.Public.class) public int getSessions() { return getOpenSessions() + getClosedSessions(); } @ApiModelProperty(required = true, value = "used to display Active Users") + @JsonView(View.Public.class) public int getActiveUsers() { return activeUsers; } @@ -104,6 +114,7 @@ public class Statistics { } @ApiModelProperty(required = true, value = "the number of users that are logged") + @JsonView(View.Public.class) public int getLoggedinUsers() { return loggedinUsers; } @@ -113,6 +124,7 @@ public class Statistics { } @ApiModelProperty(required = true, value = "the number of interposed Questions") + @JsonView(View.Public.class) public int getInterposedQuestions() { return interposedQuestions; } @@ -122,6 +134,7 @@ public class Statistics { } @ApiModelProperty(required = true, value = "the number of flashcards") + @JsonView(View.Public.class) public int getFlashcards() { return flashcards; } @@ -131,6 +144,7 @@ public class Statistics { } @ApiModelProperty(required = true, value = "the number of creators") + @JsonView(View.Public.class) public int getCreators() { return creators; } @@ -140,6 +154,7 @@ public class Statistics { } @ApiModelProperty(required = true, value = "the number of concept Questions") + @JsonView(View.Public.class) public int getConceptQuestions() { return conceptQuestions; } @@ -149,6 +164,7 @@ public class Statistics { } @ApiModelProperty(required = true, value = "the number of active Students") + @JsonView(View.Public.class) public int getActiveStudents() { return activeStudents; } diff --git a/src/main/java/de/thm/arsnova/entities/User.java b/src/main/java/de/thm/arsnova/entities/User.java index c3ed9bf78650a2236a06631794b71ddf6204900a..9d4729d44887ce9c8024b57300c31618db6ed1e0 100644 --- a/src/main/java/de/thm/arsnova/entities/User.java +++ b/src/main/java/de/thm/arsnova/entities/User.java @@ -17,6 +17,8 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import de.thm.arsnova.services.UserSessionService; import org.jasig.cas.client.authentication.AttributePrincipal; import org.pac4j.oauth.profile.facebook.FacebookProfile; @@ -76,6 +78,7 @@ public class User implements Serializable { setType(LDAP); } + @JsonView(View.Public.class) public String getUsername() { return username; } @@ -84,6 +87,7 @@ public class User implements Serializable { this.username = username; } + @JsonView(View.Public.class) public String getType() { return type; } @@ -108,6 +112,7 @@ public class User implements Serializable { this.isAdmin = a; } + @JsonView(View.Public.class) public boolean isAdmin() { return this.isAdmin; } diff --git a/src/main/java/de/thm/arsnova/entities/VisitedSession.java b/src/main/java/de/thm/arsnova/entities/VisitedSession.java index 166496fc043f76de916dcb52bdc8f80aaf85c00c..3566b5f942d3636f62293f97daca6cb86c2f54ca 100644 --- a/src/main/java/de/thm/arsnova/entities/VisitedSession.java +++ b/src/main/java/de/thm/arsnova/entities/VisitedSession.java @@ -17,11 +17,14 @@ */ package de.thm.arsnova.entities; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; + /** * A session a user has visited previously. */ public class VisitedSession { - private String _id; + private String id; private String name; private String keyword; @@ -29,38 +32,44 @@ public class VisitedSession { } public VisitedSession(Session s) { - this._id = s.get_id(); + this.id = s.getId(); this.name = s.getName(); this.keyword = s.getKeyword(); } - public String get_id() { - return _id; + @JsonView({View.Persistence.class, View.Public.class}) + public String getId() { + return id; } - public void set_id(String _id) { - this._id = _id; + @JsonView({View.Persistence.class, View.Public.class}) + public void setId(final String id) { + this.id = id; } + @JsonView({View.Persistence.class, View.Public.class}) public String getName() { return name; } + @JsonView({View.Persistence.class, View.Public.class}) public void setName(String name) { this.name = name; } + @JsonView({View.Persistence.class, View.Public.class}) public String getKeyword() { return keyword; } + @JsonView({View.Persistence.class, View.Public.class}) public void setKeyword(String keyword) { this.keyword = keyword; } @Override public String toString() { - return "VisitedSession [_id=" + _id + ", name=" + name + ", keyword=" + return "VisitedSession [id=" + id + ", name=" + name + ", keyword=" + keyword + "]"; } } diff --git a/src/main/java/de/thm/arsnova/entities/serialization/CouchDbDocumentMixIn.java b/src/main/java/de/thm/arsnova/entities/serialization/CouchDbDocumentMixIn.java new file mode 100644 index 0000000000000000000000000000000000000000..6928dad0589ea1452f69ec7f1c7ea9fe383639f2 --- /dev/null +++ b/src/main/java/de/thm/arsnova/entities/serialization/CouchDbDocumentMixIn.java @@ -0,0 +1,42 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.entities.serialization; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import de.thm.arsnova.entities.Entity; + +@JsonIgnoreProperties(value = {"type"}, allowGetters = true) +public abstract class CouchDbDocumentMixIn { + @JsonProperty("_id") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + abstract String getId(); + + @JsonProperty("_id") abstract void setId(String id); + + @JsonProperty("_rev") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + abstract String getRevision(); + + @JsonProperty("_rev") abstract String setRevision(String rev); + + @JsonSerialize(converter = CouchDbTypeFieldConverter.class) + abstract Class<? extends Entity> getType(); +} diff --git a/src/main/java/de/thm/arsnova/entities/serialization/CouchDbDocumentModule.java b/src/main/java/de/thm/arsnova/entities/serialization/CouchDbDocumentModule.java new file mode 100644 index 0000000000000000000000000000000000000000..1579458a207c33d2e98d12d71da3b337bc7d96e5 --- /dev/null +++ b/src/main/java/de/thm/arsnova/entities/serialization/CouchDbDocumentModule.java @@ -0,0 +1,32 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.entities.serialization; + +import com.fasterxml.jackson.databind.module.SimpleModule; +import de.thm.arsnova.entities.Entity; + +public class CouchDbDocumentModule extends SimpleModule { + public CouchDbDocumentModule() { + super("CouchDbDocumentModule"); + } + + @Override + public void setupModule(SetupContext context) { + context.setMixInAnnotations(Entity.class, CouchDbDocumentMixIn.class); + } +} diff --git a/src/main/java/de/thm/arsnova/entities/serialization/CouchDbObjectMapperFactory.java b/src/main/java/de/thm/arsnova/entities/serialization/CouchDbObjectMapperFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..51f822800d8729015f5ffbe2a7e24f6c5576f509 --- /dev/null +++ b/src/main/java/de/thm/arsnova/entities/serialization/CouchDbObjectMapperFactory.java @@ -0,0 +1,34 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.entities.serialization; + +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.ektorp.CouchDbConnector; +import org.ektorp.impl.StdObjectMapperFactory; + +public class CouchDbObjectMapperFactory extends StdObjectMapperFactory { + public ObjectMapper createObjectMapper(CouchDbConnector connector) { + ObjectMapper om = super.createObjectMapper(connector); + om.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false); + om.setConfig(om.getSerializationConfig().withView(View.Persistence.class)); + om.registerModule(new CouchDbDocumentModule()); + + return om; + } +} diff --git a/src/main/java/de/thm/arsnova/entities/serialization/CouchDbTypeFieldConverter.java b/src/main/java/de/thm/arsnova/entities/serialization/CouchDbTypeFieldConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..5b2199fef199ea1c1b1e454a658c35d9770074e9 --- /dev/null +++ b/src/main/java/de/thm/arsnova/entities/serialization/CouchDbTypeFieldConverter.java @@ -0,0 +1,64 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.entities.serialization; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.type.TypeFactory; +import com.fasterxml.jackson.databind.util.Converter; +import de.thm.arsnova.entities.Answer; +import de.thm.arsnova.entities.Comment; +import de.thm.arsnova.entities.DbUser; +import de.thm.arsnova.entities.Entity; +import de.thm.arsnova.entities.LogEntry; +import de.thm.arsnova.entities.Motd; +import de.thm.arsnova.entities.Content; +import de.thm.arsnova.entities.MotdList; +import de.thm.arsnova.entities.Session; + +import java.util.HashMap; +import java.util.Map; + +public class CouchDbTypeFieldConverter implements Converter<Class<? extends Entity>, String> { + private static final Map<Class<? extends Entity>, String> typeMapping = new HashMap<>(); + + { + typeMapping.put(LogEntry.class, "log"); + typeMapping.put(DbUser.class, "userdetails"); + typeMapping.put(Motd.class, "motd"); + typeMapping.put(MotdList.class, "motdlist"); + typeMapping.put(Session.class, "session"); + typeMapping.put(Comment.class, "interposed_question"); + typeMapping.put(Content.class, "skill_question"); + typeMapping.put(Answer.class, "skill_question_answer"); + } + + @Override + public String convert(Class<? extends Entity> aClass) { + return typeMapping.get(aClass); + } + + @Override + public JavaType getInputType(TypeFactory typeFactory) { + return typeFactory.constructGeneralizedType(typeFactory.constructType(Class.class), Entity.class); + } + + @Override + public JavaType getOutputType(TypeFactory typeFactory) { + return typeFactory.constructType(String.class); + } +} diff --git a/src/main/java/de/thm/arsnova/entities/serialization/View.java b/src/main/java/de/thm/arsnova/entities/serialization/View.java new file mode 100644 index 0000000000000000000000000000000000000000..b2bcb7a60286d28b97dcf5022cdd4cafacdf1d6a --- /dev/null +++ b/src/main/java/de/thm/arsnova/entities/serialization/View.java @@ -0,0 +1,23 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.entities.serialization; + +public class View { + public interface Public {} + public interface Persistence {} +} diff --git a/src/main/java/de/thm/arsnova/entities/transport/Answer.java b/src/main/java/de/thm/arsnova/entities/transport/Answer.java index 4eaa7ba714a836492edb4a3e6777af3663e11917..de074d3e1cc30029d1ee9a60c315aa69319931fc 100644 --- a/src/main/java/de/thm/arsnova/entities/transport/Answer.java +++ b/src/main/java/de/thm/arsnova/entities/transport/Answer.java @@ -18,8 +18,10 @@ package de.thm.arsnova.entities.transport; import com.fasterxml.jackson.annotation.JsonInclude; -import de.thm.arsnova.entities.Question; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.User; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -59,6 +61,7 @@ public class Answer implements Serializable { } @ApiModelProperty(required = true, value = "used to display text answer") + @JsonView(View.Public.class) public String getAnswerText() { return answerText; } @@ -68,6 +71,7 @@ public class Answer implements Serializable { } @ApiModelProperty(required = true, value = "used to display subject answer") + @JsonView(View.Public.class) public String getAnswerSubject() { return answerSubject; } @@ -76,6 +80,7 @@ public class Answer implements Serializable { this.answerSubject = answerSubject; } + @JsonView(View.Public.class) public final String getAnswerTextRaw() { return this.answerTextRaw; } @@ -84,6 +89,7 @@ public class Answer implements Serializable { this.answerTextRaw = answerTextRaw; } + @JsonView(View.Public.class) public final String getAnswerSubjectRaw() { return this.answerSubjectRaw; } @@ -92,6 +98,7 @@ public class Answer implements Serializable { this.answerSubjectRaw = answerSubjectRaw; } + @JsonView(View.Public.class) public final double getFreeTextScore() { return this.freeTextScore; } @@ -110,6 +117,7 @@ public class Answer implements Serializable { } @ApiModelProperty(required = true, value = "abstention") + @JsonView(View.Public.class) public boolean isAbstention() { return abstention; } @@ -118,34 +126,35 @@ public class Answer implements Serializable { this.abstention = abstention; } - public de.thm.arsnova.entities.Answer generateAnswerEntity(final User user, final Question question) { + public de.thm.arsnova.entities.Answer generateAnswerEntity(final User user, final Content content) { // rewrite all fields so that no manipulated data gets written // only answerText, answerSubject, and abstention are allowed de.thm.arsnova.entities.Answer theAnswer = new de.thm.arsnova.entities.Answer(); theAnswer.setAnswerSubject(this.getAnswerSubject()); theAnswer.setAnswerText(this.getAnswerText()); theAnswer.setAnswerTextRaw(this.getAnswerTextRaw()); - theAnswer.setSessionId(question.getSessionId()); + theAnswer.setSessionId(content.getSessionId()); theAnswer.setUser(user.getUsername()); - theAnswer.setQuestionId(question.get_id()); + theAnswer.setQuestionId(content.getId()); theAnswer.setTimestamp(new Date().getTime()); - theAnswer.setQuestionVariant(question.getQuestionVariant()); + theAnswer.setQuestionVariant(content.getQuestionVariant()); theAnswer.setAbstention(this.isAbstention()); // calculate learning progress value after all properties are set - theAnswer.setQuestionValue(question.calculateValue(theAnswer)); + theAnswer.setQuestionValue(content.calculateValue(theAnswer)); theAnswer.setAnswerImage(this.getAnswerImage()); theAnswer.setSuccessfulFreeTextAnswer(this.isSuccessfulFreeTextAnswer()); - if ("freetext".equals(question.getQuestionType())) { + if ("freetext".equals(content.getQuestionType())) { theAnswer.setPiRound(0); } else { - theAnswer.setPiRound(question.getPiRound()); + theAnswer.setPiRound(content.getPiRound()); } return theAnswer; } @ApiModelProperty(required = true, value = "used to display image answer") + @JsonView(View.Public.class) public String getAnswerImage() { return answerImage; } diff --git a/src/main/java/de/thm/arsnova/entities/transport/AnswerQueueElement.java b/src/main/java/de/thm/arsnova/entities/transport/AnswerQueueElement.java index 92b8b4950ba24b3407beca5e3a8e2648f7d25300..694de60fcd8d8791394a7f47e4aeadedd93521ed 100644 --- a/src/main/java/de/thm/arsnova/entities/transport/AnswerQueueElement.java +++ b/src/main/java/de/thm/arsnova/entities/transport/AnswerQueueElement.java @@ -18,7 +18,7 @@ package de.thm.arsnova.entities.transport; import de.thm.arsnova.entities.Answer; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; @@ -30,15 +30,15 @@ public class AnswerQueueElement { private final Session session; - private final Question question; + private final Content content; private final Answer answer; private final User user; - public AnswerQueueElement(Session session, Question question, Answer answer, User user) { + public AnswerQueueElement(Session session, Content content, Answer answer, User user) { this.session = session; - this.question = question; + this.content = content; this.answer = answer; this.user = user; } @@ -47,8 +47,8 @@ public class AnswerQueueElement { return session; } - public Question getQuestion() { - return question; + public Content getQuestion() { + return content; } public Answer getAnswer() { diff --git a/src/main/java/de/thm/arsnova/entities/transport/InterposedQuestion.java b/src/main/java/de/thm/arsnova/entities/transport/Comment.java similarity index 68% rename from src/main/java/de/thm/arsnova/entities/transport/InterposedQuestion.java rename to src/main/java/de/thm/arsnova/entities/transport/Comment.java index 86d3115267712ba2ea96c93e73b5c6fb2a540c30..e3006b9913bdb7aa80aa5faa074a303a61b64411 100644 --- a/src/main/java/de/thm/arsnova/entities/transport/InterposedQuestion.java +++ b/src/main/java/de/thm/arsnova/entities/transport/Comment.java @@ -17,6 +17,8 @@ */ package de.thm.arsnova.entities.transport; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -24,10 +26,10 @@ import java.util.ArrayList; import java.util.List; /** - * A question a student is asking. Also known as feedback or audience question. + * A question a student is asking. Also known as comment, feedback or audience question. */ -@ApiModel(value = "audiencequestion/{questionId}", description = "the Interposed Question API") -public class InterposedQuestion { +@ApiModel(value = "audiencequestion/{questionId}", description = "the comment API") +public class Comment { private String id; private String subject; @@ -35,25 +37,26 @@ public class InterposedQuestion { private long timestamp; private boolean read; - public static List<InterposedQuestion> fromList(List<de.thm.arsnova.entities.InterposedQuestion> questions) { - ArrayList<InterposedQuestion> interposedQuestions = new ArrayList<>(); - for (de.thm.arsnova.entities.InterposedQuestion question : questions) { - interposedQuestions.add(new InterposedQuestion(question)); + public static List<Comment> fromList(List<de.thm.arsnova.entities.Comment> comments) { + ArrayList<Comment> transportComments = new ArrayList<>(); + for (de.thm.arsnova.entities.Comment comment : comments) { + transportComments.add(new Comment(comment)); } - return interposedQuestions; + return transportComments; } - public InterposedQuestion(de.thm.arsnova.entities.InterposedQuestion question) { - this.id = question.get_id(); - this.subject = question.getSubject(); - this.text = question.getText(); - this.timestamp = question.getTimestamp(); - this.read = question.isRead(); + public Comment(de.thm.arsnova.entities.Comment comment) { + this.id = comment.getId(); + this.subject = comment.getSubject(); + this.text = comment.getText(); + this.timestamp = comment.getTimestamp(); + this.read = comment.isRead(); } - public InterposedQuestion() { } + public Comment() { } @ApiModelProperty(required = true, value = "used to display Id") + @JsonView(View.Public.class) public String getId() { return id; } @@ -63,6 +66,7 @@ public class InterposedQuestion { } @ApiModelProperty(required = true, value = "used to display Subject") + @JsonView(View.Public.class) public String getSubject() { return subject; } @@ -72,6 +76,7 @@ public class InterposedQuestion { } @ApiModelProperty(required = true, value = "used to display Text") + @JsonView(View.Public.class) public String getText() { return text; } @@ -81,6 +86,7 @@ public class InterposedQuestion { } @ApiModelProperty(required = true, value = "used to display Timetamp") + @JsonView(View.Public.class) public long getTimestamp() { return timestamp; } @@ -90,6 +96,7 @@ public class InterposedQuestion { } @ApiModelProperty(required = true, value = "is read") + @JsonView(View.Public.class) public boolean isRead() { return read; } diff --git a/src/main/java/de/thm/arsnova/entities/transport/ImportExportSession.java b/src/main/java/de/thm/arsnova/entities/transport/ImportExportSession.java index 02a01e975c8818aa1cbdac2b65a06c85ac535c55..1ef60232f0e80f12197be2720cdb27886dfa4191 100644 --- a/src/main/java/de/thm/arsnova/entities/transport/ImportExportSession.java +++ b/src/main/java/de/thm/arsnova/entities/transport/ImportExportSession.java @@ -17,12 +17,14 @@ */ package de.thm.arsnova.entities.transport; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Motd; -import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.SessionFeature; import de.thm.arsnova.entities.SessionInfo; import de.thm.arsnova.entities.User; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -38,9 +40,9 @@ public class ImportExportSession { private ImportExportSesssion session; - private List<ImportExportQuestion> questions; + private List<ImportExportContent> questions; - private List<InterposedQuestion> feedbackQuestions; + private List<Comment> feedbackQuestions; private List<Motd> motds; @@ -56,6 +58,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display session") + @JsonView(View.Public.class) public ImportExportSesssion getSession() { return session; } @@ -65,23 +68,26 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display questions") - public List<ImportExportQuestion> getQuestions() { + @JsonView(View.Public.class) + public List<ImportExportContent> getQuestions() { return questions; } - public void setQuestions(List<ImportExportQuestion> questions) { + public void setQuestions(List<ImportExportContent> questions) { this.questions = questions; } @ApiModelProperty(required = true, value = "used to display questions feedback") - public List<InterposedQuestion> getFeedbackQuestions() { + @JsonView(View.Public.class) + public List<Comment> getFeedbackQuestions() { return feedbackQuestions; } - public void setFeedbackQuestions(List<InterposedQuestion> feedbackQuestions) { + public void setFeedbackQuestions(List<Comment> feedbackQuestions) { this.feedbackQuestions = feedbackQuestions; } + @JsonView(View.Public.class) public List<Motd> getMotds() { return motds; } @@ -90,6 +96,7 @@ public class ImportExportSession { this.motds = mL; } + @JsonView(View.Public.class) public SessionFeature getSessionFeature() { return sessionFeature; } @@ -98,6 +105,7 @@ public class ImportExportSession { sessionFeature = sF; } + @JsonView(View.Public.class) public SessionInfo getSessionInfo() { return sessionInfo; } @@ -118,8 +126,8 @@ public class ImportExportSession { session = iesession; } - public void addQuestionWithAnswers(Question q, List<Answer> aL) { - ImportExportQuestion ieq = new ImportExportQuestion(q); + public void addQuestionWithAnswers(Content q, List<Answer> aL) { + ImportExportContent ieq = new ImportExportContent(q); ieq.setAnswers(aL); questions.add(ieq); } @@ -145,22 +153,20 @@ public class ImportExportSession { s.setPpSubject(session.getPublicPool().getPpSubject()); s.setPpUniversity(session.getPublicPool().getPpUniversity()); // other fields - s.setType("session"); s.setCreator(user.getUsername()); s.setCreationTime(new Date().getTime()); return s; } - public static class ImportExportQuestion extends Question { + public static class ImportExportContent extends Content { private List<Answer> answers; - public ImportExportQuestion() { + public ImportExportContent() { } - public ImportExportQuestion(Question q) { - setType(q.getType()); + public ImportExportContent(Content q) { setQuestionType(q.getQuestionType()); setQuestionVariant(q.getQuestionVariant()); setSubject(q.getSubject()); @@ -217,6 +223,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = " used to display answers") + @JsonView(View.Public.class) public List<Answer> getAnswers() { return answers; } @@ -241,6 +248,7 @@ public class ImportExportSession { private SessionFeature sessionFeature; @ApiModelProperty(required = true, value = "used to display short name") + @JsonView(View.Public.class) public String getName() { return name; } @@ -250,6 +258,7 @@ public class ImportExportSession { } @ApiModelProperty(required = false, value = "used to identify public pool sessions") + @JsonView(View.Public.class) public String getSessionType() { return sessionType; } @@ -259,6 +268,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display short name") + @JsonView(View.Public.class) public String getShortName() { return shortName; } @@ -268,6 +278,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "active") + @JsonView(View.Public.class) public boolean isActive() { return active; } @@ -277,6 +288,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display public pool") + @JsonView(View.Public.class) public PublicPool getPublicPool() { return publicPool; } @@ -285,6 +297,7 @@ public class ImportExportSession { this.publicPool = publicPool; } + @JsonView(View.Public.class) public SessionFeature getSessionFeature() { return this.sessionFeature; } @@ -333,6 +346,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display author name") + @JsonView(View.Public.class) public String getPpAuthorName() { return ppAuthorName; } @@ -342,6 +356,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display author mail") + @JsonView(View.Public.class) public String getPpAuthorMail() { return ppAuthorMail; } @@ -351,6 +366,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display university") + @JsonView(View.Public.class) public String getPpUniversity() { return ppUniversity; } @@ -360,6 +376,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display logo") + @JsonView(View.Public.class) public String getPpLogo() { return ppLogo; } @@ -369,6 +386,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display subject") + @JsonView(View.Public.class) public String getPpSubject() { return ppSubject; } @@ -378,6 +396,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display license") + @JsonView(View.Public.class) public String getPpLicense() { return ppLicense; } @@ -387,6 +406,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display level") + @JsonView(View.Public.class) public String getPpLevel() { return ppLevel; } @@ -396,6 +416,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display description") + @JsonView(View.Public.class) public String getPpDescription() { return ppDescription; } @@ -405,6 +426,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display faculty") + @JsonView(View.Public.class) public String getPpFaculty() { return ppFaculty; } @@ -414,6 +436,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display name") + @JsonView(View.Public.class) public String getName() { return name; } @@ -423,6 +446,7 @@ public class ImportExportSession { } @ApiModelProperty(required = true, value = "used to display short name") + @JsonView(View.Public.class) public String getShortName() { return shortName; } diff --git a/src/main/java/de/thm/arsnova/entities/transport/LearningProgressValues.java b/src/main/java/de/thm/arsnova/entities/transport/LearningProgressValues.java index 041ee4d54c7bcf6e214eeb7da4d5cbb304909f38..686a733d09775ba257e97c8a11970bb105942ba4 100644 --- a/src/main/java/de/thm/arsnova/entities/transport/LearningProgressValues.java +++ b/src/main/java/de/thm/arsnova/entities/transport/LearningProgressValues.java @@ -17,6 +17,8 @@ */ package de.thm.arsnova.entities.transport; +import com.fasterxml.jackson.annotation.JsonView; +import de.thm.arsnova.entities.serialization.View; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -39,6 +41,7 @@ public class LearningProgressValues { private int numUsers; @ApiModelProperty(required = true, value = "used to display course progress") + @JsonView(View.Public.class) public int getCourseProgress() { return courseProgress; } @@ -48,6 +51,7 @@ public class LearningProgressValues { } @ApiModelProperty(required = true, value = "used to display my progress") + @JsonView(View.Public.class) public int getMyProgress() { return myProgress; } @@ -57,6 +61,7 @@ public class LearningProgressValues { } @ApiModelProperty(required = true, value = "used to display questions number") + @JsonView(View.Public.class) public int getNumQuestions() { return numQuestions; } @@ -65,6 +70,7 @@ public class LearningProgressValues { this.numQuestions = numQuestions; } + @JsonView(View.Public.class) public int getNumerator() { return numerator; } @@ -73,6 +79,7 @@ public class LearningProgressValues { this.numerator = numerator; } + @JsonView(View.Public.class) public int getDenominator() { return denominator; } @@ -82,6 +89,7 @@ public class LearningProgressValues { } @ApiModelProperty(required = true, value = "used to display user number") + @JsonView(View.Public.class) public int getNumUsers() { return numUsers; } diff --git a/src/main/java/de/thm/arsnova/events/DeleteAnswerEvent.java b/src/main/java/de/thm/arsnova/events/DeleteAnswerEvent.java index 8d400e92823539cab79c6ca09ed484e772604b02..55970e207457824eff5d3ba23772ed3e24ad4e68 100644 --- a/src/main/java/de/thm/arsnova/events/DeleteAnswerEvent.java +++ b/src/main/java/de/thm/arsnova/events/DeleteAnswerEvent.java @@ -17,7 +17,7 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; /** @@ -27,11 +27,11 @@ public class DeleteAnswerEvent extends SessionEvent { private static final long serialVersionUID = 1L; - private final Question question; + private final Content content; - public DeleteAnswerEvent(Object source, Session session, Question question) { + public DeleteAnswerEvent(Object source, Session session, Content content) { super(source, session); - this.question = question; + this.content = content; } @Override @@ -39,7 +39,7 @@ public class DeleteAnswerEvent extends SessionEvent { visitor.visit(this); } - public Question getQuestion() { - return question; + public Content getQuestion() { + return content; } } diff --git a/src/main/java/de/thm/arsnova/events/DeleteInterposedQuestionEvent.java b/src/main/java/de/thm/arsnova/events/DeleteCommentEvent.java similarity index 71% rename from src/main/java/de/thm/arsnova/events/DeleteInterposedQuestionEvent.java rename to src/main/java/de/thm/arsnova/events/DeleteCommentEvent.java index fb0b6ce0283f88f1242e3445a195fc026c4364c4..a22b545aa4a81d7f0e80d9059531537943f3a3fd 100644 --- a/src/main/java/de/thm/arsnova/events/DeleteInterposedQuestionEvent.java +++ b/src/main/java/de/thm/arsnova/events/DeleteCommentEvent.java @@ -17,21 +17,21 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.InterposedQuestion; +import de.thm.arsnova.entities.Comment; import de.thm.arsnova.entities.Session; /** - * Fires whenever an interposed question is deleted. + * Fires whenever an comment is deleted. */ -public class DeleteInterposedQuestionEvent extends SessionEvent { +public class DeleteCommentEvent extends SessionEvent { private static final long serialVersionUID = 1L; - private final InterposedQuestion question; + private final Comment comment; - public DeleteInterposedQuestionEvent(Object source, Session session, InterposedQuestion question) { + public DeleteCommentEvent(Object source, Session session, Comment comment) { super(source, session); - this.question = question; + this.comment = comment; } @Override @@ -39,8 +39,8 @@ public class DeleteInterposedQuestionEvent extends SessionEvent { visitor.visit(this); } - public InterposedQuestion getQuestion() { - return question; + public Comment getQuestion() { + return comment; } } diff --git a/src/main/java/de/thm/arsnova/events/DeleteQuestionEvent.java b/src/main/java/de/thm/arsnova/events/DeleteQuestionEvent.java index 2f3cfd71b2518377694c7c3e160a3ee4dcc373cf..0204ee5b7d5ca8bdd101a0cf41ddb7c005da0800 100644 --- a/src/main/java/de/thm/arsnova/events/DeleteQuestionEvent.java +++ b/src/main/java/de/thm/arsnova/events/DeleteQuestionEvent.java @@ -17,25 +17,25 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; /** - * Fires whenever a question is deleted. + * Fires whenever a content is deleted. */ public class DeleteQuestionEvent extends SessionEvent { private static final long serialVersionUID = 1L; - private final Question question; + private final Content content; - public DeleteQuestionEvent(Object source, Session session, Question question) { + public DeleteQuestionEvent(Object source, Session session, Content content) { super(source, session); - this.question = question; + this.content = content; } - public Question getQuestion() { - return this.question; + public Content getQuestion() { + return this.content; } @Override diff --git a/src/main/java/de/thm/arsnova/events/DeleteSessionEvent.java b/src/main/java/de/thm/arsnova/events/DeleteSessionEvent.java index 7daf6d22a51e66a6153e487d5622019714e1e19d..3bffe73bb4639cc8c8509a3fad8fabf74dada234 100644 --- a/src/main/java/de/thm/arsnova/events/DeleteSessionEvent.java +++ b/src/main/java/de/thm/arsnova/events/DeleteSessionEvent.java @@ -20,7 +20,7 @@ package de.thm.arsnova.events; import de.thm.arsnova.entities.Session; /** - * Fires whenever a session is deleted. Note that this implies that all related data such as interposed questions, + * Fires whenever a session is deleted. Note that this implies that all related data such as comments, * lecturer questions, and answers are deleted as well, even though those events are not fired. */ public class DeleteSessionEvent extends SessionEvent { diff --git a/src/main/java/de/thm/arsnova/events/LockQuestionEvent.java b/src/main/java/de/thm/arsnova/events/LockQuestionEvent.java index 389b3ea97b08bb76af634a26036283c2344c46d1..8371c8b01c59573a2f7b6320421623a1dba4ff26 100644 --- a/src/main/java/de/thm/arsnova/events/LockQuestionEvent.java +++ b/src/main/java/de/thm/arsnova/events/LockQuestionEvent.java @@ -17,25 +17,25 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; /** - * Fires whenever a question is disabled, i.e., it is hidden from students. + * Fires whenever a content is disabled, i.e., it is hidden from students. */ public class LockQuestionEvent extends SessionEvent { private static final long serialVersionUID = 1L; - private final Question question; + private final Content content; - public LockQuestionEvent(Object source, Session session, Question question) { + public LockQuestionEvent(Object source, Session session, Content content) { super(source, session); - this.question = question; + this.content = content; } - public Question getQuestion() { - return this.question; + public Content getQuestion() { + return this.content; } @Override diff --git a/src/main/java/de/thm/arsnova/events/LockQuestionsEvent.java b/src/main/java/de/thm/arsnova/events/LockQuestionsEvent.java index 9ee93fa3b232f2f4ec517dc5ffa304256553f2a2..2981f4e18f5610a5799167146c1036e0cb286be2 100644 --- a/src/main/java/de/thm/arsnova/events/LockQuestionsEvent.java +++ b/src/main/java/de/thm/arsnova/events/LockQuestionsEvent.java @@ -17,27 +17,27 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; import java.util.List; /** - * Fires whenever a set of questions are disabled, i.e., they are hidden from students. + * Fires whenever a set of contents are disabled, i.e., they are hidden from students. */ public class LockQuestionsEvent extends SessionEvent { private static final long serialVersionUID = 1L; - private List<Question> questions; + private List<Content> contents; - public LockQuestionsEvent(Object source, Session session, List<Question> questions) { + public LockQuestionsEvent(Object source, Session session, List<Content> contents) { super(source, session); - this.questions = questions; + this.contents = contents; } - public List<Question> getQuestions() { - return this.questions; + public List<Content> getQuestions() { + return this.contents; } @Override diff --git a/src/main/java/de/thm/arsnova/events/LockVoteEvent.java b/src/main/java/de/thm/arsnova/events/LockVoteEvent.java index b33922f4f25bdc75a5df1a520fffce1e14c0b548..e3e9f000fd8fafa074572445ce493510a1a06d68 100644 --- a/src/main/java/de/thm/arsnova/events/LockVoteEvent.java +++ b/src/main/java/de/thm/arsnova/events/LockVoteEvent.java @@ -17,36 +17,36 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; import java.util.HashMap; import java.util.Map; /** - * Fires whenever voting on a question is disabled. + * Fires whenever voting on a content is disabled. */ public class LockVoteEvent extends SessionEvent { private static final long serialVersionUID = 1L; - private final Question question; + private final Content content; - public LockVoteEvent(Object source, Session session, Question question) { + public LockVoteEvent(Object source, Session session, Content content) { super(source, session); - this.question = question; + this.content = content; } public String getQuestionId() { - return this.question.get_id(); + return this.content.getId(); } public String getQuestionVariant() { - return this.question.getQuestionVariant(); + return this.content.getQuestionVariant(); } public Boolean getVotingDisabled() { - return this.question.isVotingDisabled(); + return this.content.isVotingDisabled(); } public Map<String, Object> getVotingAdmission() { diff --git a/src/main/java/de/thm/arsnova/events/LockVotesEvent.java b/src/main/java/de/thm/arsnova/events/LockVotesEvent.java index fb5acd7c16e404db66a3a716ba48f52f7461253e..6f8043c18ae279962546d180e4191bde2d5e193e 100644 --- a/src/main/java/de/thm/arsnova/events/LockVotesEvent.java +++ b/src/main/java/de/thm/arsnova/events/LockVotesEvent.java @@ -17,27 +17,27 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; import java.util.List; /** - * Fires whenever voting of multiple questions is disabled. + * Fires whenever voting of multiple contents is disabled. */ public class LockVotesEvent extends SessionEvent { private static final long serialVersionUID = 1L; - private List<Question> questions; + private List<Content> contents; - public LockVotesEvent(Object source, Session session, List<Question> questions) { + public LockVotesEvent(Object source, Session session, List<Content> contents) { super(source, session); - this.questions = questions; + this.contents = contents; } - public List<Question> getQuestions() { - return this.questions; + public List<Content> getQuestions() { + return this.contents; } @Override diff --git a/src/main/java/de/thm/arsnova/events/NewAnswerEvent.java b/src/main/java/de/thm/arsnova/events/NewAnswerEvent.java index c2fd68a5237b2e0e73fc1ec9535405989d4ca579..9172a5b5a5b307be3ed77a881ee0a868872674f7 100644 --- a/src/main/java/de/thm/arsnova/events/NewAnswerEvent.java +++ b/src/main/java/de/thm/arsnova/events/NewAnswerEvent.java @@ -18,7 +18,7 @@ package de.thm.arsnova.events; import de.thm.arsnova.entities.Answer; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; @@ -33,13 +33,13 @@ public class NewAnswerEvent extends SessionEvent { private final User user; - private final Question question; + private final Content content; - public NewAnswerEvent(Object source, Session session, Answer answer, User user, Question question) { + public NewAnswerEvent(Object source, Session session, Answer answer, User user, Content content) { super(source, session); this.answer = answer; this.user = user; - this.question = question; + this.content = content; } @Override @@ -55,7 +55,7 @@ public class NewAnswerEvent extends SessionEvent { return user; } - public Question getQuestion() { - return question; + public Content getContent() { + return content; } } diff --git a/src/main/java/de/thm/arsnova/events/NewInterposedQuestionEvent.java b/src/main/java/de/thm/arsnova/events/NewCommentEvent.java similarity index 70% rename from src/main/java/de/thm/arsnova/events/NewInterposedQuestionEvent.java rename to src/main/java/de/thm/arsnova/events/NewCommentEvent.java index 3983c70177bd98d08a3a44a66af0535709e6530e..8330cbcd5b40c4d6ecf4b7cd2a1ad3c2e0dd0865 100644 --- a/src/main/java/de/thm/arsnova/events/NewInterposedQuestionEvent.java +++ b/src/main/java/de/thm/arsnova/events/NewCommentEvent.java @@ -17,25 +17,25 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.InterposedQuestion; +import de.thm.arsnova.entities.Comment; import de.thm.arsnova.entities.Session; /** - * Fires whenever a new interposed (aka. feedback or audience) question is added. + * Fires whenever a new comment is added. */ -public class NewInterposedQuestionEvent extends SessionEvent { +public class NewCommentEvent extends SessionEvent { private static final long serialVersionUID = 1L; - private final InterposedQuestion question; + private final Comment comment; - public NewInterposedQuestionEvent(Object source, Session session, InterposedQuestion question) { + public NewCommentEvent(Object source, Session session, Comment comment) { super(source, session); - this.question = question; + this.comment = comment; } - public InterposedQuestion getQuestion() { - return question; + public Comment getQuestion() { + return comment; } @Override diff --git a/src/main/java/de/thm/arsnova/events/NewQuestionEvent.java b/src/main/java/de/thm/arsnova/events/NewQuestionEvent.java index 6ac4be12e84ad0c21b835f08f434aa72c9dfa11d..1a9e1616ec35819e5825b14b6ad1fb5609e82f92 100644 --- a/src/main/java/de/thm/arsnova/events/NewQuestionEvent.java +++ b/src/main/java/de/thm/arsnova/events/NewQuestionEvent.java @@ -17,25 +17,25 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; /** - * Fires whenever a new question is added. + * Fires whenever a new content is added. */ public class NewQuestionEvent extends SessionEvent { private static final long serialVersionUID = 1L; - private final Question question; + private final Content content; - public NewQuestionEvent(Object source, Session session, Question question) { + public NewQuestionEvent(Object source, Session session, Content content) { super(source, session); - this.question = question; + this.content = content; } - public Question getQuestion() { - return question; + public Content getQuestion() { + return content; } @Override diff --git a/src/main/java/de/thm/arsnova/events/NovaEventVisitor.java b/src/main/java/de/thm/arsnova/events/NovaEventVisitor.java index 0152851ce50df48bb3063bc115449059e911d6c1..f02c32154ca8897d5f95251bb59a895387b72c76 100644 --- a/src/main/java/de/thm/arsnova/events/NovaEventVisitor.java +++ b/src/main/java/de/thm/arsnova/events/NovaEventVisitor.java @@ -22,9 +22,9 @@ package de.thm.arsnova.events; */ public interface NovaEventVisitor { - void visit(NewInterposedQuestionEvent newInterposedQuestionEvent); + void visit(NewCommentEvent newCommentEvent); - void visit(DeleteInterposedQuestionEvent deleteInterposedQuestionEvent); + void visit(DeleteCommentEvent deleteCommentEvent); void visit(NewQuestionEvent newQuestionEvent); @@ -81,6 +81,6 @@ public interface NovaEventVisitor { void visit(FeatureChangeEvent featureChangeEvent); void visit(LockFeedbackEvent lockFeedbackEvent); - + void visit(FlipFlashcardsEvent flipFlashcardsEvent); } diff --git a/src/main/java/de/thm/arsnova/events/PiRoundCancelEvent.java b/src/main/java/de/thm/arsnova/events/PiRoundCancelEvent.java index 86ff69ae311e784a077d40aa8eda3ba44627f341..64b51aea8780c1a4d5b68efd667b21f5eecc3e4d 100644 --- a/src/main/java/de/thm/arsnova/events/PiRoundCancelEvent.java +++ b/src/main/java/de/thm/arsnova/events/PiRoundCancelEvent.java @@ -17,7 +17,7 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; /** @@ -27,8 +27,8 @@ public class PiRoundCancelEvent extends PiRoundEndEvent { private static final long serialVersionUID = 1L; - public PiRoundCancelEvent(Object source, Session session, Question question) { - super(source, session, question); + public PiRoundCancelEvent(Object source, Session session, Content content) { + super(source, session, content); } @Override diff --git a/src/main/java/de/thm/arsnova/events/PiRoundDelayedStartEvent.java b/src/main/java/de/thm/arsnova/events/PiRoundDelayedStartEvent.java index 6fdc587e38791ae1c56c54e2fa329841dd93b9f2..dfaed2bb4fbf87b73fc27b9dae220de29b3bf29f 100644 --- a/src/main/java/de/thm/arsnova/events/PiRoundDelayedStartEvent.java +++ b/src/main/java/de/thm/arsnova/events/PiRoundDelayedStartEvent.java @@ -17,7 +17,7 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; import java.util.HashMap; @@ -36,13 +36,13 @@ public class PiRoundDelayedStartEvent extends SessionEvent { private final String questionVariant; private int piRound; - public PiRoundDelayedStartEvent(Object source, Session session, Question question) { + public PiRoundDelayedStartEvent(Object source, Session session, Content content) { super(source, session); - this.questionId = question.get_id(); - this.startTime = question.getPiRoundStartTime(); - this.endTime = question.getPiRoundEndTime(); - this.questionVariant = question.getQuestionVariant(); - this.piRound = question.getPiRound(); + this.questionId = content.getId(); + this.startTime = content.getPiRoundStartTime(); + this.endTime = content.getPiRoundEndTime(); + this.questionVariant = content.getQuestionVariant(); + this.piRound = content.getPiRound(); } @Override diff --git a/src/main/java/de/thm/arsnova/events/PiRoundEndEvent.java b/src/main/java/de/thm/arsnova/events/PiRoundEndEvent.java index b87d12590bdd7e3667d7998367b86cbb4514b07d..335345185364f88e718c0f19d4b50c55520390f4 100644 --- a/src/main/java/de/thm/arsnova/events/PiRoundEndEvent.java +++ b/src/main/java/de/thm/arsnova/events/PiRoundEndEvent.java @@ -17,7 +17,7 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; import java.util.HashMap; @@ -33,10 +33,10 @@ public class PiRoundEndEvent extends SessionEvent { private final String questionId; private final String questionVariant; - public PiRoundEndEvent(Object source, Session session, Question question) { + public PiRoundEndEvent(Object source, Session session, Content content) { super(source, session); - questionId = question.get_id(); - questionVariant = question.getQuestionVariant(); + questionId = content.getId(); + questionVariant = content.getQuestionVariant(); } @Override diff --git a/src/main/java/de/thm/arsnova/events/PiRoundResetEvent.java b/src/main/java/de/thm/arsnova/events/PiRoundResetEvent.java index 429aeb274c1b26fecbc1f779734316a9d2835ca1..96bd50df0d30637ea3c4fc3b87c65cba5b0bee70 100644 --- a/src/main/java/de/thm/arsnova/events/PiRoundResetEvent.java +++ b/src/main/java/de/thm/arsnova/events/PiRoundResetEvent.java @@ -17,7 +17,7 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; import java.util.HashMap; @@ -33,10 +33,10 @@ public class PiRoundResetEvent extends SessionEvent { private final String questionId; private final String questionVariant; - public PiRoundResetEvent(Object source, Session session, Question question) { + public PiRoundResetEvent(Object source, Session session, Content content) { super(source, session); - questionId = question.get_id(); - questionVariant = question.getQuestionVariant(); + questionId = content.getId(); + questionVariant = content.getQuestionVariant(); } @Override diff --git a/src/main/java/de/thm/arsnova/events/UnlockQuestionEvent.java b/src/main/java/de/thm/arsnova/events/UnlockQuestionEvent.java index d04c709e8af2b89761f7bcf4d2de508558096f82..1d8e24c61ae3661a45b6d42b3f92d464df9f71e2 100644 --- a/src/main/java/de/thm/arsnova/events/UnlockQuestionEvent.java +++ b/src/main/java/de/thm/arsnova/events/UnlockQuestionEvent.java @@ -17,25 +17,25 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; /** - * Fires whenever a question is enabled, i.e., it becomes visible to students. + * Fires whenever a content is enabled, i.e., it becomes visible to students. */ public class UnlockQuestionEvent extends SessionEvent { private static final long serialVersionUID = 1L; - private final Question question; + private final Content content; - public UnlockQuestionEvent(Object source, Session session, Question question) { + public UnlockQuestionEvent(Object source, Session session, Content content) { super(source, session); - this.question = question; + this.content = content; } - public Question getQuestion() { - return this.question; + public Content getQuestion() { + return this.content; } @Override diff --git a/src/main/java/de/thm/arsnova/events/UnlockQuestionsEvent.java b/src/main/java/de/thm/arsnova/events/UnlockQuestionsEvent.java index b760b4ce5f4fb73e528432b782f9fbe3687d6cf0..153b702a6fbdd1177eb6fbb5fa920c8d99319215 100644 --- a/src/main/java/de/thm/arsnova/events/UnlockQuestionsEvent.java +++ b/src/main/java/de/thm/arsnova/events/UnlockQuestionsEvent.java @@ -17,27 +17,27 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; import java.util.List; /** - * Fires whenever a set of questions are enabled, i.e., they become visible to students. + * Fires whenever a set of contents are enabled, i.e., they become visible to students. */ public class UnlockQuestionsEvent extends SessionEvent { private static final long serialVersionUID = 1L; - private List<Question> questions; + private List<Content> contents; - public UnlockQuestionsEvent(Object source, Session session, List<Question> questions) { + public UnlockQuestionsEvent(Object source, Session session, List<Content> contents) { super(source, session); - this.questions = questions; + this.contents = contents; } - public List<Question> getQuestions() { - return this.questions; + public List<Content> getQuestions() { + return this.contents; } @Override diff --git a/src/main/java/de/thm/arsnova/events/UnlockVoteEvent.java b/src/main/java/de/thm/arsnova/events/UnlockVoteEvent.java index 69a1f45717e077339a28267700a43a8e6123703a..00bf47a4d8781e8368cc33fcd14a0c1f122314b3 100644 --- a/src/main/java/de/thm/arsnova/events/UnlockVoteEvent.java +++ b/src/main/java/de/thm/arsnova/events/UnlockVoteEvent.java @@ -17,36 +17,36 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; import java.util.HashMap; import java.util.Map; /** - * Fires whenever voting on a question is enabled. + * Fires whenever voting on a content is enabled. */ public class UnlockVoteEvent extends SessionEvent { private static final long serialVersionUID = 1L; - private final Question question; + private final Content content; - public UnlockVoteEvent(Object source, Session session, Question question) { + public UnlockVoteEvent(Object source, Session session, Content content) { super(source, session); - this.question = question; + this.content = content; } public String getQuestionId() { - return this.question.get_id(); + return this.content.getId(); } public String getQuestionVariant() { - return this.question.getQuestionVariant(); + return this.content.getQuestionVariant(); } public Boolean getVotingDisabled() { - return this.question.isVotingDisabled(); + return this.content.isVotingDisabled(); } public Map<String, Object> getVotingAdmission() { diff --git a/src/main/java/de/thm/arsnova/events/UnlockVotesEvent.java b/src/main/java/de/thm/arsnova/events/UnlockVotesEvent.java index c489aefcf5d8b22ab91e8d649985201e1f85a4cf..2dd3d4671849ac43cccf7a457ddc2e43a63feec7 100644 --- a/src/main/java/de/thm/arsnova/events/UnlockVotesEvent.java +++ b/src/main/java/de/thm/arsnova/events/UnlockVotesEvent.java @@ -17,27 +17,27 @@ */ package de.thm.arsnova.events; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; import java.util.List; /** - * Fires whenever voting of multiple questions is enabled. + * Fires whenever voting of multiple contents is enabled. */ public class UnlockVotesEvent extends SessionEvent { private static final long serialVersionUID = 1L; - private List<Question> questions; + private List<Content> contents; - public UnlockVotesEvent(Object source, Session session, List<Question> questions) { + public UnlockVotesEvent(Object source, Session session, List<Content> contents) { super(source, session); - this.questions = questions; + this.contents = contents; } - public List<Question> getQuestions() { - return this.questions; + public List<Content> getQuestions() { + return this.contents; } @Override diff --git a/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java b/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..27693fa2b45a20be0526e70078d2d278f40422aa --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java @@ -0,0 +1,49 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.persistance; + +import de.thm.arsnova.entities.Answer; +import de.thm.arsnova.entities.Content; +import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.User; + +import java.util.List; + +public interface AnswerRepository { + Answer get(String id); + Answer getMyAnswer(User me, String questionId, int piRound); + List<Answer> getAnswers(Content content, int piRound); + List<Answer> getAnswers(Content content); + List<Answer> getAllAnswers(Content content); + int getAnswerCount(Content content, int piRound); + int getTotalAnswerCountByQuestion(Content content); + int getAbstentionAnswerCount(String questionId); + List<Answer> getFreetextAnswers(String questionId, final int start, final int limit); + List<Answer> getMyAnswers(User me, Session session); + int getTotalAnswerCount(String sessionKey); + int deleteAnswers(Content content); + Answer saveAnswer(Answer answer, User user, Content content, Session session); + Answer updateAnswer(Answer answer); + void deleteAnswer(String answerId); + int countLectureQuestionAnswers(Session session); + int countPreparationQuestionAnswers(Session session); + int deleteAllQuestionsAnswers(Session session); + int deleteAllPreparationAnswers(Session session); + int deleteAllLectureAnswers(Session session); + int[] deleteAllAnswersWithQuestions(List<Content> contents); +} diff --git a/src/main/java/de/thm/arsnova/persistance/CommentRepository.java b/src/main/java/de/thm/arsnova/persistance/CommentRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..c5f8bc350a907609195cda995c01401349a8f136 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/CommentRepository.java @@ -0,0 +1,22 @@ +package de.thm.arsnova.persistance; + +import de.thm.arsnova.entities.Comment; +import de.thm.arsnova.entities.CommentReadingCount; +import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.User; + +import java.util.List; + +public interface CommentRepository { + int getInterposedCount(String sessionKey); + CommentReadingCount getInterposedReadingCount(Session session); + CommentReadingCount getInterposedReadingCount(Session session, User user); + List<Comment> getInterposedQuestions(Session session, final int start, final int limit); + List<Comment> getInterposedQuestions(Session session, User user, final int start, final int limit); + Comment getInterposedQuestion(String commentId); + Comment saveQuestion(Session session, Comment comment, User user); + void markInterposedQuestionAsRead(Comment comment); + void deleteInterposedQuestion(Comment comment); + int deleteAllInterposedQuestions(Session session); + int deleteAllInterposedQuestions(Session session, User user); +} diff --git a/src/main/java/de/thm/arsnova/persistance/ContentRepository.java b/src/main/java/de/thm/arsnova/persistance/ContentRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..9c49aa7892f774b4eed4b6bb99b99bb6b70de7a2 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/ContentRepository.java @@ -0,0 +1,44 @@ +package de.thm.arsnova.persistance; + +import de.thm.arsnova.entities.Content; +import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.User; + +import java.util.List; + +public interface ContentRepository { + List<Content> getQuestions(Object... keys); + Content getQuestion(String id); + Content saveQuestion(Session session, Content content); + List<Content> getSkillQuestionsForUsers(Session session); + List<Content> getSkillQuestionsForTeachers(Session session); + int getSkillQuestionCount(Session session); + List<String> getQuestionIds(Session session, User user); + int deleteQuestionWithAnswers(Content content); + int[] deleteAllQuestionsWithAnswers(Session session); + List<String> getUnAnsweredQuestionIds(Session session, User user); + Content updateQuestion(Content content); + List<Content> getLectureQuestionsForUsers(Session session); + List<Content> getLectureQuestionsForTeachers(Session session); + List<Content> getFlashcardsForUsers(Session session); + List<Content> getFlashcardsForTeachers(Session session); + List<Content> getPreparationQuestionsForUsers(Session session); + List<Content> getPreparationQuestionsForTeachers(Session session); + List<Content> getAllSkillQuestions(Session session); + int getLectureQuestionCount(Session session); + int getFlashcardCount(Session session); + int getPreparationQuestionCount(Session session); + void publishQuestions(Session session, boolean publish, List<Content> contents); + List<Content> publishAllQuestions(Session session, boolean publish); + List<String> getQuestionIdsBySubject(Session session, String questionVariant, String subject); + List<Content> getQuestionsByIds(List<String> ids, Session session); + void resetQuestionsRoundState(Session session, List<Content> contents); + void setVotingAdmissions(Session session, boolean disableVoting, List<Content> contents); + List<Content> setVotingAdmissionForAllQuestions(Session session, boolean disableVoting); + int[] deleteAllLectureQuestionsWithAnswers(Session session); + int[] deleteAllFlashcardsWithAnswers(Session session); + int[] deleteAllPreparationQuestionsWithAnswers(Session session); + List<String> getSubjects(Session session, String questionVariant); + List<String> getUnAnsweredLectureQuestionIds(Session session, User user); + List<String> getUnAnsweredPreparationQuestionIds(Session session, User user); +} diff --git a/src/main/java/de/thm/arsnova/persistance/LogEntryRepository.java b/src/main/java/de/thm/arsnova/persistance/LogEntryRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..7ec30eb51c5b8407680b629b72a2f99f4f76c54a --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/LogEntryRepository.java @@ -0,0 +1,90 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.persistance; + +import de.thm.arsnova.entities.LogEntry; + +import java.util.HashMap; +import java.util.Map; + +public interface LogEntryRepository { + /** + * Logs an event to the database. Arbitrary data can be attached as payload. Database logging should only be used + * if the logged data is later analyzed by the backend itself. Otherwise use the default logging mechanisms. + * + * @param event type of the event + * @param level severity of the event + * @param payload arbitrary logging data + */ + void create(String event, LogEntry.LogLevel level, Map<String, Object> payload); + + /** + * Logs an event to the database. Arbitrary data can be attached as payload. Database logging should only be used + * if the logged data is later analyzed by the backend itself. Otherwise use the default logging mechanisms. + * + * @param event type of the event + * @param payload arbitrary logging data + * @param level severity of the event + */ + default void log(String event, Map<String, Object> payload, LogEntry.LogLevel level) { + create(event, level, payload); + } + + /** + * Logs an event of informational severity to the database. Arbitrary data can be attached as payload. Database + * logging should only be used if the logged data is later analyzed by the backend itself. Otherwise use the default + * logging mechanisms. + * + * @param event type of the event + * @param payload arbitrary logging data + */ + default void log(String event, Map<String, Object> payload) { + create(event, LogEntry.LogLevel.INFO, payload); + } + + /** + * Logs an event to the database. Arbitrary data can be attached as payload. Database logging should only be used + * if the logged data is later analyzed by the backend itself. Otherwise use the default logging mechanisms. + * + * @param event type of the event + * @param level severity of the event + * @param rawPayload key/value pairs of arbitrary logging data + */ + default void log(String event, LogEntry.LogLevel level, Object... rawPayload) { + if (rawPayload.length % 2 != 0) { + throw new IllegalArgumentException(""); + } + Map<String, Object> payload = new HashMap<>(); + for (int i = 0; i < rawPayload.length; i += 2) { + payload.put((String) rawPayload[i], rawPayload[i + 1]); + } + create(event, level, payload); + } + + /** + * Logs an event of informational severity to the database. Arbitrary data can be attached as payload. Database + * logging should only be used if the logged data is later analyzed by the backend itself. Otherwise use the default + * logging mechanisms. + * + * @param event type of the event + * @param rawPayload key/value pairs of arbitrary logging data + */ + default void log(String event, Object... rawPayload) { + log(event, LogEntry.LogLevel.INFO, rawPayload); + } +} diff --git a/src/main/java/de/thm/arsnova/persistance/MotdListRepository.java b/src/main/java/de/thm/arsnova/persistance/MotdListRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..a044fe7ed6b637ea6c6bc0c487908f4082c42609 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/MotdListRepository.java @@ -0,0 +1,8 @@ +package de.thm.arsnova.persistance; + +import de.thm.arsnova.entities.MotdList; + +public interface MotdListRepository { + MotdList getMotdListForUser(final String username); + MotdList createOrUpdateMotdList(MotdList motdlist); +} diff --git a/src/main/java/de/thm/arsnova/persistance/MotdRepository.java b/src/main/java/de/thm/arsnova/persistance/MotdRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..9c783165d8affbbfc057223ca76a87dbf17fb61f --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/MotdRepository.java @@ -0,0 +1,34 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.persistance; + +import de.thm.arsnova.entities.Motd; + +import java.util.List; + +public interface MotdRepository { + List<Motd> getAdminMotds(); + List<Motd> getMotdsForAll(); + List<Motd> getMotdsForLoggedIn(); + List<Motd> getMotdsForTutors(); + List<Motd> getMotdsForStudents(); + List<Motd> getMotdsForSession(final String sessionkey); + Motd getMotdByKey(String key); + Motd createOrUpdateMotd(Motd motd); + boolean deleteMotd(Motd motd); +} diff --git a/src/main/java/de/thm/arsnova/persistance/SessionRepository.java b/src/main/java/de/thm/arsnova/persistance/SessionRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..4c57f848416dd1cfb37f665758689e344839ecd1 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/SessionRepository.java @@ -0,0 +1,59 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.persistance; + +import de.thm.arsnova.connector.model.Course; +import de.thm.arsnova.entities.LoggedIn; +import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.SessionInfo; +import de.thm.arsnova.entities.User; +import de.thm.arsnova.entities.transport.ImportExportSession; + +import java.util.List; + +public interface SessionRepository { + Session getSessionFromId(String sessionId); + Session getSessionFromKeyword(String keyword); + Session saveSession(User user, Session session); + Session updateSession(Session session); + + /** + * Deletes a session and related data. + * + * @param session the session for deletion + */ + int[] deleteSession(Session session); + + Session changeSessionCreator(Session session, String newCreator); + int[] deleteInactiveGuestSessions(long lastActivityBefore); + List<Session> getMySessions(User user, final int start, final int limit); + List<Session> getSessionsForUsername(String username, final int start, final int limit); + List<Session> getPublicPoolSessions(); + List<Session> getMyPublicPoolSessions(User user); + boolean sessionKeyAvailable(String keyword); + Session updateSessionOwnerActivity(Session session); + List<Session> getVisitedSessionsForUsername(String username, final int start, final int limit); + List<SessionInfo> getMySessionsInfo(User user, final int start, final int limit); + List<SessionInfo> getPublicPoolSessionsInfo(); + List<SessionInfo> getMyPublicPoolSessionsInfo(final User user); + List<SessionInfo> getMyVisitedSessionsInfo(User currentUser, final int start, final int limit); + List<Session> getCourseSessions(List<Course> courses); + SessionInfo importSession(User user, ImportExportSession importSession); + ImportExportSession exportSession(String sessionkey, Boolean withAnswer, Boolean withFeedbackQuestions); + LoggedIn registerAsOnlineUser(final User user, final Session session); +} diff --git a/src/main/java/de/thm/arsnova/persistance/SessionStatisticsRepository.java b/src/main/java/de/thm/arsnova/persistance/SessionStatisticsRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..193da0333f4aaf143065f69a12eaef674b1a75c3 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/SessionStatisticsRepository.java @@ -0,0 +1,8 @@ +package de.thm.arsnova.persistance; + +import de.thm.arsnova.domain.CourseScore; +import de.thm.arsnova.entities.Session; + +public interface SessionStatisticsRepository { + CourseScore getLearningProgress(Session session); +} diff --git a/src/main/java/de/thm/arsnova/persistance/StatisticsRepository.java b/src/main/java/de/thm/arsnova/persistance/StatisticsRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..ea334f0d6932f2770a65c3f24aa71c7418a73847 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/StatisticsRepository.java @@ -0,0 +1,7 @@ +package de.thm.arsnova.persistance; + +import de.thm.arsnova.entities.Statistics; + +public interface StatisticsRepository { + Statistics getStatistics(); +} diff --git a/src/main/java/de/thm/arsnova/persistance/UserRepository.java b/src/main/java/de/thm/arsnova/persistance/UserRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..7807f66aa3beb3583f2d7b6a22746ce158f4f327 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/UserRepository.java @@ -0,0 +1,27 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.persistance; + +import de.thm.arsnova.entities.DbUser; + +public interface UserRepository { + DbUser findUserByUsername(String username); + DbUser createOrUpdateUser(DbUser user); + boolean deleteUser(DbUser user); + int deleteInactiveUsers(long lastActivityBefore); +} diff --git a/src/main/java/de/thm/arsnova/persistance/VisitedSessionRepository.java b/src/main/java/de/thm/arsnova/persistance/VisitedSessionRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..ce0b2358155f8b539ecaec84f976c32b02577a84 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/VisitedSessionRepository.java @@ -0,0 +1,5 @@ +package de.thm.arsnova.persistance; + +public interface VisitedSessionRepository { + int deleteInactiveGuestVisitedSessionLists(long lastActivityBefore); +} diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..4ebb3273e4c7ba6deac9ecf6ecd9090d860d52d8 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java @@ -0,0 +1,385 @@ +package de.thm.arsnova.persistance.couchdb; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Lists; +import de.thm.arsnova.entities.Answer; +import de.thm.arsnova.entities.Content; +import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.User; +import de.thm.arsnova.entities.transport.AnswerQueueElement; +import de.thm.arsnova.events.NewAnswerEvent; +import de.thm.arsnova.exceptions.NotFoundException; +import de.thm.arsnova.persistance.AnswerRepository; +import de.thm.arsnova.persistance.ContentRepository; +import de.thm.arsnova.persistance.LogEntryRepository; +import de.thm.arsnova.persistance.SessionRepository; +import org.ektorp.BulkDeleteDocument; +import org.ektorp.ComplexKey; +import org.ektorp.CouchDbConnector; +import org.ektorp.DbAccessException; +import org.ektorp.DocumentOperationResult; +import org.ektorp.UpdateConflictException; +import org.ektorp.ViewResult; +import org.ektorp.support.CouchDbRepositorySupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.scheduling.annotation.Scheduled; + +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class CouchDbAnswerRepository extends CouchDbRepositorySupport<Answer> implements AnswerRepository, ApplicationEventPublisherAware { + private static final int BULK_PARTITION_SIZE = 500; + private static final Logger logger = LoggerFactory.getLogger(CouchDbAnswerRepository.class); + + private final Queue<AnswerQueueElement> answerQueue = new ConcurrentLinkedQueue<>(); + + @Autowired + private LogEntryRepository dbLogger; + + @Autowired + private SessionRepository sessionRepository; + + @Autowired + private ContentRepository contentRepository; + + private ApplicationEventPublisher publisher; + + public CouchDbAnswerRepository(Class<Answer> type, CouchDbConnector db, boolean createIfNotExists) { + super(type, db, createIfNotExists); + } + + @Scheduled(fixedDelay = 5000) + public void flushAnswerQueue() { + if (answerQueue.isEmpty()) { + // no need to send an empty bulk request. + return; + } + + final List<Answer> answerList = new ArrayList<>(); + final List<AnswerQueueElement> elements = new ArrayList<>(); + AnswerQueueElement entry; + while ((entry = this.answerQueue.poll()) != null) { + final Answer answer = entry.getAnswer(); + answerList.add(answer); + elements.add(entry); + } + try { + db.executeBulk(answerList); + + // Send NewAnswerEvents ... + for (AnswerQueueElement e : elements) { + this.publisher.publishEvent(new NewAnswerEvent(this, e.getSession(), e.getAnswer(), e.getUser(), e.getQuestion())); + } + } catch (DbAccessException e) { + logger.error("Could not bulk save answers from queue.", e); + } + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { + this.publisher = publisher; + } + + @CacheEvict("answers") + @Override + public int deleteAnswers(final Content content) { + try { + final ViewResult result = db.queryView(createQuery("by_questionid") + .key(content.getId())); + final List<List<ViewResult.Row>> partitions = Lists.partition(result.getRows(), BULK_PARTITION_SIZE); + + int count = 0; + for (List<ViewResult.Row> partition: partitions) { + List<BulkDeleteDocument> answersToDelete = new ArrayList<>(); + for (final ViewResult.Row a : partition) { + final BulkDeleteDocument d = new BulkDeleteDocument(a.getId(), a.getValueAsNode().get("_rev").asText()); + answersToDelete.add(d); + } + List<DocumentOperationResult> errors = db.executeBulk(answersToDelete); + count += partition.size() - errors.size(); + if (errors.size() > 0) { + logger.error("Could not bulk delete {} of {} answers.", errors.size(), partition.size()); + } + } + dbLogger.log("delete", "type", "answer", "answerCount", count); + + return count; + } catch (final DbAccessException e) { + logger.error("Could not delete answers for content {}.", content.getId(), e); + } + + return 0; + } + + @Override + public Answer getMyAnswer(final User me, final String questionId, final int piRound) { + final List<Answer> answerList = queryView("by_questionid_user_piround", + ComplexKey.of(questionId, me.getUsername(), piRound)); + return answerList.isEmpty() ? null : answerList.get(0); + } + + @Override + public List<Answer> getAnswers(final Content content, final int piRound) { + final String questionId = content.getId(); + final ViewResult result = db.queryView(createQuery("by_questionid_piround_text_subject") + .group(true) + .startKey(ComplexKey.of(questionId, piRound)) + .endKey(ComplexKey.of(questionId, piRound, ComplexKey.emptyObject()))); + final int abstentionCount = getAbstentionAnswerCount(questionId); + + List<Answer> answers = new ArrayList<>(); + for (final ViewResult.Row d : result) { + final Answer a = new Answer(); + a.setAnswerCount(d.getValueAsInt()); + a.setAbstentionCount(abstentionCount); + a.setQuestionId(d.getKeyAsNode().get(0).asText()); + a.setPiRound(piRound); + final JsonNode answerTextNode = d.getKeyAsNode().get(3); + a.setAnswerText(answerTextNode.isNull() ? null : answerTextNode.asText()); + answers.add(a); + } + + return answers; + } + + @Override + public List<Answer> getAllAnswers(final Content content) { + final String questionId = content.getId(); + final ViewResult result = db.queryView(createQuery("by_questionid_piround_text_subject") + .group(true) + .startKey(ComplexKey.of(questionId)) + .endKey(ComplexKey.of(questionId, ComplexKey.emptyObject()))); + final int abstentionCount = getAbstentionAnswerCount(questionId); + + final List<Answer> answers = new ArrayList<>(); + for (final ViewResult.Row d : result.getRows()) { + final Answer a = new Answer(); + a.setAnswerCount(d.getValueAsInt()); + a.setAbstentionCount(abstentionCount); + a.setQuestionId(d.getKeyAsNode().get(0).asText()); + final JsonNode answerTextNode = d.getKeyAsNode().get(3); + final JsonNode answerSubjectNode = d.getKeyAsNode().get(4); + final boolean successfulFreeTextAnswer = d.getKeyAsNode().get(5).asBoolean(); + a.setAnswerText(answerTextNode.isNull() ? null : answerTextNode.asText()); + a.setAnswerSubject(answerSubjectNode.isNull() ? null : answerSubjectNode.asText()); + a.setSuccessfulFreeTextAnswer(successfulFreeTextAnswer); + answers.add(a); + } + + return answers; + } + + @Cacheable("answers") + @Override + public List<Answer> getAnswers(final Content content) { + return this.getAnswers(content, content.getPiRound()); + } + + @Override + public int getAbstentionAnswerCount(final String questionId) { + final ViewResult result = db.queryView(createQuery("by_questionid_piround_text_subject") + //.group(true) + .startKey(ComplexKey.of(questionId)) + .endKey(ComplexKey.of(questionId, ComplexKey.emptyObject()))); + + return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt(); + } + + @Override + public int getAnswerCount(final Content content, final int piRound) { + final ViewResult result = db.queryView(createQuery("by_questionid_piround_text_subject") + //.group(true) + .startKey(ComplexKey.of(content.getId(), piRound)) + .endKey(ComplexKey.of(content.getId(), piRound, ComplexKey.emptyObject()))); + + return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt(); + } + + @Override + public int getTotalAnswerCountByQuestion(final Content content) { + final ViewResult result = db.queryView(createQuery("by_questionid_piround_text_subject") + //.group(true) + .startKey(ComplexKey.of(content.getId())) + .endKey(ComplexKey.of(content.getId(), ComplexKey.emptyObject()))); + + return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt(); + } + + @Override + public List<Answer> getFreetextAnswers(final String questionId, final int start, final int limit) { + final int qSkip = start > 0 ? start : -1; + final int qLimit = limit > 0 ? limit : -1; + + final List<Answer> answers = db.queryView(createQuery("by_questionid_timestamp") + .skip(qSkip) + .limit(qLimit) + //.includeDocs(true) + .startKey(ComplexKey.of(questionId)) + .endKey(ComplexKey.of(questionId, ComplexKey.emptyObject())) + .descending(true), + Answer.class); + + return answers; + } + + @Override + public List<Answer> getMyAnswers(final User me, final Session s) { + return queryView("by_user_sessionid", ComplexKey.of(me.getUsername(), s.getId())); + } + + @Override + public int getTotalAnswerCount(final String sessionKey) { + final Session s = sessionRepository.getSessionFromKeyword(sessionKey); + if (s == null) { + throw new NotFoundException(); + } + final ViewResult result = db.queryView(createQuery("by_sessionid_variant").key(s.getId())); + + return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt(); + } + + @CacheEvict(value = "answers", key = "#content") + @Override + public Answer saveAnswer(final Answer answer, final User user, final Content content, final Session session) { + db.create(answer); + this.answerQueue.offer(new AnswerQueueElement(session, content, answer, user)); + + return answer; + } + + /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ + @CacheEvict(value = "answers", allEntries = true) + @Override + public Answer updateAnswer(final Answer answer) { + try { + update(answer); + return answer; + } catch (final UpdateConflictException e) { + logger.error("Could not update answer {}.", answer, e); + } + + return null; + } + + /* TODO: Only evict cache entry for the answer's session. This requires some refactoring. */ + @CacheEvict(value = "answers", allEntries = true) + @Override + public void deleteAnswer(final String answerId) { + try { + /* TODO: use id and rev instead of loading the answer */ + db.delete(get(answerId)); + dbLogger.log("delete", "type", "answer"); + } catch (final DbAccessException e) { + logger.error("Could not delete answer {}.", answerId, e); + } + } + + @Override + public int countLectureQuestionAnswers(final Session session) { + return countQuestionVariantAnswers(session, "lecture"); + } + + @Override + public int countPreparationQuestionAnswers(final Session session) { + return countQuestionVariantAnswers(session, "preparation"); + } + + private int countQuestionVariantAnswers(final Session session, final String variant) { + final ViewResult result = db.queryView(createQuery("by_sessionid_variant") + .key(ComplexKey.of(session.getId(), variant))); + + return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt(); + } + + /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ + @CacheEvict(value = "answers", allEntries = true) + @Override + public int deleteAllQuestionsAnswers(final Session session) { + final List<Content> contents = contentRepository.getQuestions(session.getId()); + contentRepository.resetQuestionsRoundState(session, contents); + + return deleteAllAnswersForQuestions(contents); + } + + /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ + @CacheEvict(value = "answers", allEntries = true) + @Override + public int deleteAllPreparationAnswers(final Session session) { + final List<Content> contents = contentRepository.getQuestions(session.getId(), "preparation"); + contentRepository.resetQuestionsRoundState(session, contents); + + return deleteAllAnswersForQuestions(contents); + } + + /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ + @CacheEvict(value = "answers", allEntries = true) + @Override + public int deleteAllLectureAnswers(final Session session) { + final List<Content> contents = contentRepository.getQuestions(session.getId(), "lecture"); + contentRepository.resetQuestionsRoundState(session, contents); + + return deleteAllAnswersForQuestions(contents); + } + + public int deleteAllAnswersForQuestions(List<Content> contents) { + List<String> questionIds = new ArrayList<>(); + for (Content q : contents) { + questionIds.add(q.getId()); + } + final ViewResult result = db.queryView(createQuery("by_questionid") + .keys(questionIds)); + final List<BulkDeleteDocument> allAnswers = new ArrayList<>(); + for (ViewResult.Row a : result.getRows()) { + final BulkDeleteDocument d = new BulkDeleteDocument(a.getId(), a.getValueAsNode().get("_rev").asText()); + allAnswers.add(d); + } + try { + List<DocumentOperationResult> errors = db.executeBulk(allAnswers); + + return allAnswers.size() - errors.size(); + } catch (DbAccessException e) { + logger.error("Could not bulk delete answers.", e); + } + + return 0; + } + + public int[] deleteAllAnswersWithQuestions(List<Content> contents) { + List<String> questionIds = new ArrayList<>(); + final List<BulkDeleteDocument> allQuestions = new ArrayList<>(); + for (Content q : contents) { + final BulkDeleteDocument d = new BulkDeleteDocument(q.getId(), q.getRevision()); + questionIds.add(q.getId()); + allQuestions.add(d); + } + + final ViewResult result = db.queryView(createQuery("by_questionid") + .key(questionIds)); + final List<BulkDeleteDocument> allAnswers = new ArrayList<>(); + for (ViewResult.Row a : result.getRows()) { + final BulkDeleteDocument d = new BulkDeleteDocument(a.getId(), a.getValueAsNode().get("_rev").asText()); + allAnswers.add(d); + } + + try { + List<BulkDeleteDocument> deleteList = new ArrayList<>(allAnswers); + deleteList.addAll(allQuestions); + List<DocumentOperationResult> errors = db.executeBulk(deleteList); + + /* TODO: subtract errors from count */ + return new int[] {allQuestions.size(), allAnswers.size()}; + } catch (DbAccessException e) { + logger.error("Could not bulk delete contents and answers.", e); + } + + return new int[] {0, 0}; + } +} diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..ff1deeda8a5fb73646a7053d5a7536c952753ca6 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java @@ -0,0 +1,239 @@ +package de.thm.arsnova.persistance.couchdb; + +import com.fasterxml.jackson.databind.JsonNode; +import de.thm.arsnova.entities.Comment; +import de.thm.arsnova.entities.CommentReadingCount; +import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.User; +import de.thm.arsnova.exceptions.NotFoundException; +import de.thm.arsnova.persistance.CommentRepository; +import de.thm.arsnova.persistance.LogEntryRepository; +import de.thm.arsnova.persistance.SessionRepository; +import org.ektorp.ComplexKey; +import org.ektorp.CouchDbConnector; +import org.ektorp.DocumentNotFoundException; +import org.ektorp.UpdateConflictException; +import org.ektorp.ViewResult; +import org.ektorp.support.CouchDbRepositorySupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +public class CouchDbCommentRepository extends CouchDbRepositorySupport<Comment> implements CommentRepository { + private static final Logger logger = LoggerFactory.getLogger(CouchDbCommentRepository.class); + + @Autowired + private LogEntryRepository dbLogger; + + @Autowired + private SessionRepository sessionRepository; + + public CouchDbCommentRepository(Class<Comment> type, CouchDbConnector db, boolean createIfNotExists) { + super(type, db, createIfNotExists); + } + + @Override + public int getInterposedCount(final String sessionKey) { + final Session s = sessionRepository.getSessionFromKeyword(sessionKey); + if (s == null) { + throw new NotFoundException(); + } + + final ViewResult result = db.queryView(createQuery("by_sessionid").key(s.getId()).group(true)); + if (result.isEmpty()) { + return 0; + } + + return result.getRows().get(0).getValueAsInt(); + } + + @Override + public CommentReadingCount getInterposedReadingCount(final Session session) { + final ViewResult result = db.queryView(createQuery("by_sessionid_read") + .startKey(ComplexKey.of(session.getId())) + .endKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject())) + .group(true)); + return getInterposedReadingCount(result); + } + + @Override + public CommentReadingCount getInterposedReadingCount(final Session session, final User user) { + final ViewResult result = db.queryView(createQuery("by_sessionid_creator_read") + .startKey(ComplexKey.of(session.getId(), user.getUsername())) + .endKey(ComplexKey.of(session.getId(), user.getUsername(), ComplexKey.emptyObject())) + .group(true)); + return getInterposedReadingCount(result); + } + + private CommentReadingCount getInterposedReadingCount(final ViewResult viewResult) { + if (viewResult.isEmpty()) { + return new CommentReadingCount(); + } + // A complete result looks like this. Note that the second row is optional, and that the first one may be + // 'unread' or 'read', i.e., results may be switched around or only one result may be present. + // count = {"rows":[ + // {"key":["cecebabb21b096e592d81f9c1322b877","Guestc9350cf4a3","read"],"value":1}, + // {"key":["cecebabb21b096e592d81f9c1322b877","Guestc9350cf4a3","unread"],"value":1} + // ]} + int read = 0, unread = 0; + boolean isRead = false; + final ViewResult.Row fst = viewResult.getRows().get(0); + final ViewResult.Row snd = viewResult.getRows().size() > 1 ? viewResult.getRows().get(1) : null; + + final JsonNode fstkey = fst.getKeyAsNode(); + if (fstkey.size() == 2) { + isRead = fstkey.get(1).asBoolean(); + } else if (fstkey.size() == 3) { + isRead = fstkey.get(2).asBoolean(); + } + if (isRead) { + read = fst.getValueAsInt(); + } else { + unread = fst.getValueAsInt(); + } + + if (snd != null) { + final JsonNode sndkey = snd.getKeyAsNode(); + if (sndkey.size() == 2) { + isRead = sndkey.get(1).asBoolean(); + } else { + isRead = sndkey.get(2).asBoolean(); + } + if (isRead) { + read = snd.getValueAsInt(); + } else { + unread = snd.getValueAsInt(); + } + } + return new CommentReadingCount(read, unread); + } + + @Override + public List<Comment> getInterposedQuestions(final Session session, final int start, final int limit) { + final int qSkip = start > 0 ? start : -1; + final int qLimit = limit > 0 ? limit : -1; + + final List<Comment> comments = db.queryView(createQuery("by_sessionid_timestamp") + .skip(qSkip) + .limit(qLimit) + .descending(true) + .startKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject())) + .endKey(ComplexKey.of(session.getId())) + .includeDocs(true), + Comment.class); +// for (Comment comment : comments) { +// comment.setSessionId(session.getKeyword()); +// } + + return comments; + } + + @Override + public List<Comment> getInterposedQuestions(final Session session, final User user, final int start, final int limit) { + final int qSkip = start > 0 ? start : -1; + final int qLimit = limit > 0 ? limit : -1; + + final List<Comment> comments = db.queryView(createQuery("by_sessionid_creator_timestamp") + .skip(qSkip) + .limit(qLimit) + .descending(true) + .startKey(ComplexKey.of(session.getId(), user.getUsername(), ComplexKey.emptyObject())) + .endKey(ComplexKey.of(session.getId(), user.getUsername())) + .includeDocs(true), + Comment.class); +// for (Comment comment : comments) { +// comment.setSessionId(session.getKeyword()); +// } + + return comments; + } + + @Override + public Comment getInterposedQuestion(final String commentId) { + try { + final Comment comment = get(commentId); + /* TODO: Refactor code so the next line can be removed */ + //comment.setSessionId(sessionRepository.getSessionFromKeyword(comment.getSessionId()).getId()); + return comment; + } catch (final DocumentNotFoundException e) { + logger.error("Could not load comment {}.", commentId, e); + } + return null; + } + + @Override + public Comment saveQuestion(final Session session, final Comment comment, User user) { + comment.setSessionId(session.getId()); + comment.setCreator(user.getUsername()); + comment.setRead(false); + if (comment.getTimestamp() == 0) { + comment.setTimestamp(System.currentTimeMillis()); + } + try { + db.create(comment); + + return comment; + } catch (final IllegalArgumentException e) { + logger.error("Could not save comment {}.", comment, e); + } + + return null; + } + + @Override + public void markInterposedQuestionAsRead(final Comment comment) { + try { + comment.setRead(true); + db.update(comment); + } catch (final UpdateConflictException e) { + logger.error("Could not mark comment as read {}.", comment.getId(), e); + } + } + + @Override + public void deleteInterposedQuestion(final Comment comment) { + try { + db.delete(comment.getId(), comment.getRevision()); + dbLogger.log("delete", "type", "comment"); + } catch (final UpdateConflictException e) { + logger.error("Could not delete comment {}.", comment.getId(), e); + } + } + + @Override + public int deleteAllInterposedQuestions(final Session session) { + final ViewResult result = db.queryView(createQuery("by_sessionid").key(session.getId())); + + return deleteAllInterposedQuestions(session, result); + } + + @Override + public int deleteAllInterposedQuestions(final Session session, final User user) { + final ViewResult result = db.queryView(createQuery("by_sessionid_creator_read") + .startKey(ComplexKey.of(session.getId(), user.getUsername())) + .endKey(ComplexKey.of(session.getId(), user.getUsername(), ComplexKey.emptyObject()))); + + return deleteAllInterposedQuestions(session, result); + } + + private int deleteAllInterposedQuestions(final Session session, final ViewResult comments) { + if (comments.isEmpty()) { + return 0; + } + /* TODO: use bulk delete */ + for (final ViewResult.Row row : comments.getRows()) { + try { + db.delete(row.getId(), row.getValueAsNode().get("rev").asText()); + } catch (final UpdateConflictException e) { + logger.error("Could not delete all comments {}.", session, e); + } + } + + /* This does account for failed deletions */ + dbLogger.log("delete", "type", "comment", "commentCount", comments.getSize()); + + return comments.getSize(); + } +} diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..1e91fb34eb68dff249749afbdc38ec0e405eff10 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java @@ -0,0 +1,514 @@ +package de.thm.arsnova.persistance.couchdb; + +import de.thm.arsnova.entities.Content; +import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.User; +import de.thm.arsnova.persistance.AnswerRepository; +import de.thm.arsnova.persistance.ContentRepository; +import de.thm.arsnova.persistance.LogEntryRepository; +import org.ektorp.ComplexKey; +import org.ektorp.CouchDbConnector; +import org.ektorp.DbAccessException; +import org.ektorp.DocumentNotFoundException; +import org.ektorp.UpdateConflictException; +import org.ektorp.ViewQuery; +import org.ektorp.ViewResult; +import org.ektorp.support.CouchDbRepositorySupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class CouchDbContentRepository extends CouchDbRepositorySupport<Content> implements ContentRepository { + private static final Logger logger = LoggerFactory.getLogger(CouchDbContentRepository.class); + + @Autowired + private LogEntryRepository dbLogger; + + @Autowired + private AnswerRepository answerRepository; + + public CouchDbContentRepository(Class<Content> type, CouchDbConnector db, boolean createIfNotExists) { + super(type, db, createIfNotExists); + } + + @Cacheable("skillquestions") + @Override + public List<Content> getSkillQuestionsForUsers(final Session session) { + final List<Content> contents = new ArrayList<>(); + final List<Content> questions1 = getQuestions(session.getId(), "lecture", true); + final List<Content> questions2 = getQuestions(session.getId(), "preparation", true); + final List<Content> questions3 = getQuestions(session.getId(), "flashcard", true); + contents.addAll(questions1); + contents.addAll(questions2); + contents.addAll(questions3); + + return contents; + } + + @Cacheable("skillquestions") + @Override + public List<Content> getSkillQuestionsForTeachers(final Session session) { + return getQuestions(new Object[] {session.getId()}, session); + } + + @Override + public int getSkillQuestionCount(final Session session) { + final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(session.getId())) + .endKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject()))); + + return result.getSize(); + } + + @Caching(evict = {@CacheEvict(value = "skillquestions", key = "#session"), + @CacheEvict(value = "lecturequestions", key = "#session", condition = "#content.getQuestionVariant().equals('lecture')"), + @CacheEvict(value = "preparationquestions", key = "#session", condition = "#content.getQuestionVariant().equals('preparation')"), + @CacheEvict(value = "flashcardquestions", key = "#session", condition = "#content.getQuestionVariant().equals('flashcard')") }, + put = {@CachePut(value = "questions", key = "#content.id")}) + @Override + public Content saveQuestion(final Session session, final Content content) { + content.setSessionId(session.getId()); + try { + db.create(content); + + return content; + } catch (final IllegalArgumentException e) { + logger.error("Could not save content {}.", content, e); + } + + return null; + } + + /* TODO: Only evict cache entry for the content's session. This requires some refactoring. */ + @Caching(evict = {@CacheEvict(value = "skillquestions", allEntries = true), + @CacheEvict(value = "lecturequestions", allEntries = true, condition = "#content.getQuestionVariant().equals('lecture')"), + @CacheEvict(value = "preparationquestions", allEntries = true, condition = "#content.getQuestionVariant().equals('preparation')"), + @CacheEvict(value = "flashcardquestions", allEntries = true, condition = "#content.getQuestionVariant().equals('flashcard')") }, + put = {@CachePut(value = "questions", key = "#content.id")}) + @Override + public Content updateQuestion(final Content content) { + try { + /* TODO: Make sure that sessionId is valid before so the content does not need to be retrieved. */ + final Content oldContent = get(content.getId()); + content.setId(oldContent.getId()); + content.setRevision(oldContent.getRevision()); + content.updateRoundManagementState(); + update(content); + + return content; + } catch (final UpdateConflictException e) { + logger.error("Could not update content {}.", content, e); + } + + return null; + } + + @Cacheable("questions") + @Override + public Content getQuestion(final String id) { + try { + final Content content = get(id); + content.updateRoundManagementState(); + //content.setSessionKeyword(sessionRepository.getSessionFromId(content.getSessionId()).getKeyword()); + + return content; + } catch (final DocumentNotFoundException e) { + logger.error("Could not get question {}.", id, e); + } + + return null; + } + + @Override + public List<String> getQuestionIds(final Session session, final User user) { + return collectQuestionIds(db.queryView(createQuery("by_sessionid_variant_active").key(session.getId()))); + } + + /* TODO: Only evict cache entry for the content's session. This requires some refactoring. */ + @Caching(evict = { @CacheEvict(value = "questions", key = "#content.id"), + @CacheEvict(value = "skillquestions", allEntries = true), + @CacheEvict(value = "lecturequestions", allEntries = true, condition = "#content.getQuestionVariant().equals('lecture')"), + @CacheEvict(value = "preparationquestions", allEntries = true, condition = "#content.getQuestionVariant().equals('preparation')"), + @CacheEvict(value = "flashcardquestions", allEntries = true, condition = "#content.getQuestionVariant().equals('flashcard')") }) + @Override + public int deleteQuestionWithAnswers(final Content content) { + try { + int count = answerRepository.deleteAnswers(content); + db.delete(content); + dbLogger.log("delete", "type", "content", "answerCount", count); + + return count; + } catch (final IllegalArgumentException e) { + logger.error("Could not delete content {}.", content.getId(), e); + } + + return 0; + } + + @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), + @CacheEvict(value = "skillquestions", key = "#session"), + @CacheEvict(value = "lecturequestions", key = "#session"), + @CacheEvict(value = "preparationquestions", key = "#session"), + @CacheEvict(value = "flashcardquestions", key = "#session") }) + @Override + public int[] deleteAllQuestionsWithAnswers(final Session session) { + final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(session.getId())) + .endKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject())) + .reduce(false)); + + return deleteAllQuestionDocumentsWithAnswers(result); + } + + private int[] deleteAllQuestionDocumentsWithAnswers(final ViewResult viewResult) { + List<Content> contents = new ArrayList<>(); + for (final ViewResult.Row row : viewResult.getRows()) { + final Content q = new Content(); + q.setId(row.getId()); + q.setRevision(row.getValueAsNode().get("_rev").asText()); + contents.add(q); + } + + int[] count = answerRepository.deleteAllAnswersWithQuestions(contents); + dbLogger.log("delete", "type", "question", "questionCount", count[0]); + dbLogger.log("delete", "type", "answer", "answerCount", count[1]); + + return count; + } + + @Override + public List<String> getUnAnsweredQuestionIds(final Session session, final User user) { + final ViewResult result = db.queryView(createQuery("questionid_by_user_sessionid_variant") + .designDocId("_design/Answer") + .startKey(ComplexKey.of(user.getUsername(), session.getId())) + .endKey(ComplexKey.of(user.getUsername(), session.getId(), ComplexKey.emptyObject()))); + List<String> answeredIds = new ArrayList<>(); + for (ViewResult.Row row : result.getRows()) { + answeredIds.add(row.getId()); + } + return collectUnansweredQuestionIds(getQuestionIds(session, user), answeredIds); + } + + @Override + public List<String> getUnAnsweredLectureQuestionIds(final Session session, final User user) { + final ViewResult result = db.queryView(createQuery("questionid_piround_by_user_sessionid_variant") + .designDocId("_design/Answer") + .key(ComplexKey.of(user.getUsername(), session.getId(), "lecture"))); + Map<String, Integer> answeredQuestions = new HashMap<>(); + for (ViewResult.Row row : result.getRows()) { + answeredQuestions.put(row.getId(), row.getKeyAsNode().get(2).asInt()); + } + + return collectUnansweredQuestionIdsByPiRound(getLectureQuestionsForUsers(session), answeredQuestions); + } + + @Override + public List<String> getUnAnsweredPreparationQuestionIds(final Session session, final User user) { + final ViewResult result = db.queryView(createQuery("questionid_piround_by_user_sessionid_variant") + .designDocId("_design/Answer") + .key(ComplexKey.of(user.getUsername(), session.getId(), "preparation"))); + Map<String, Integer> answeredQuestions = new HashMap<>(); + for (ViewResult.Row row : result.getRows()) { + answeredQuestions.put(row.getId(), row.getKeyAsNode().get(2).asInt()); + } + + return collectUnansweredQuestionIdsByPiRound(getPreparationQuestionsForUsers(session), answeredQuestions); + } + + @Cacheable("lecturequestions") + @Override + public List<Content> getLectureQuestionsForUsers(final Session session) { + return getQuestions(session.getId(), "lecture", true); + } + + @Override + public List<Content> getLectureQuestionsForTeachers(final Session session) { + return getQuestions(session.getId(), "lecture"); + } + + @Cacheable("flashcardquestions") + @Override + public List<Content> getFlashcardsForUsers(final Session session) { + return getQuestions(session.getId(), "flashcard", true); + } + + @Override + public List<Content> getFlashcardsForTeachers(final Session session) { + return getQuestions(session.getId(), "flashcard"); + } + + @Cacheable("preparationquestions") + @Override + public List<Content> getPreparationQuestionsForUsers(final Session session) { + return getQuestions(session.getId(), "preparation", true); + } + + @Override + public List<Content> getPreparationQuestionsForTeachers(final Session session) { + return getQuestions(session.getId(), "preparation"); + } + + @Override + public List<Content> getAllSkillQuestions(final Session session) { + return getQuestions(session.getId()); + } + + @Override + public List<Content> getQuestions(final Object... keys) { + Object[] endKeys = Arrays.copyOf(keys, keys.length + 1); + endKeys[keys.length] = ComplexKey.emptyObject(); + final List<Content> contents = db.queryView(createQuery("by_sessionid_variant_active") + .includeDocs(true) + .reduce(false) + .startKey(ComplexKey.of(keys)) + .endKey(ComplexKey.of(endKeys)), + Content.class); + for (Content content : contents) { + content.updateRoundManagementState(); + //content.setSessionKeyword(session.getKeyword()); + } + + return contents; + } + + @Override + public int getLectureQuestionCount(final Session session) { + /* TODO: reduce code duplication */ + final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(session.getId(), "lecture")) + .endKey(ComplexKey.of(session.getId(), "lecture", ComplexKey.emptyObject()))); + + return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt(); + } + + @Override + public int getFlashcardCount(final Session session) { + /* TODO: reduce code duplication */ + final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(session.getId(), "flashcard")) + .endKey(ComplexKey.of(session.getId(), "flashcard", ComplexKey.emptyObject()))); + + return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt(); + } + + @Override + public int getPreparationQuestionCount(final Session session) { + /* TODO: reduce code duplication */ + final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(session.getId(), "preparation")) + .endKey(ComplexKey.of(session.getId(), "preparation", ComplexKey.emptyObject()))); + + return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt(); + } + + /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ + @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), + @CacheEvict("skillquestions"), + @CacheEvict("lecturequestions"), + @CacheEvict(value = "answers", allEntries = true)}) + @Override + public int[] deleteAllLectureQuestionsWithAnswers(final Session session) { + final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(session.getId(), "lecture")) + .endKey(ComplexKey.of(session.getId(), "lecture", ComplexKey.emptyObject())) + .reduce(false)); + + return deleteAllQuestionDocumentsWithAnswers(result); + } + + /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ + @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), + @CacheEvict("skillquestions"), + @CacheEvict("flashcardquestions"), + @CacheEvict(value = "answers", allEntries = true)}) + @Override + public int[] deleteAllFlashcardsWithAnswers(final Session session) { + final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(session.getId(), "flashcard")) + .endKey(ComplexKey.of(session.getId(), "flashcard", ComplexKey.emptyObject())) + .reduce(false)); + + return deleteAllQuestionDocumentsWithAnswers(result); + } + + /* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */ + @Caching(evict = { @CacheEvict(value = "questions", allEntries = true), + @CacheEvict("skillquestions"), + @CacheEvict("preparationquestions"), + @CacheEvict(value = "answers", allEntries = true)}) + @Override + public int[] deleteAllPreparationQuestionsWithAnswers(final Session session) { + final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(session.getId(), "preparation")) + .endKey(ComplexKey.of(session.getId(), "preparation", ComplexKey.emptyObject())) + .reduce(false)); + + return deleteAllQuestionDocumentsWithAnswers(result); + } + + private List<String> collectUnansweredQuestionIds( + final List<String> questions, + final List<String> answeredQuestions + ) { + final List<String> unanswered = new ArrayList<>(); + for (final String questionId : questions) { + if (!answeredQuestions.contains(questionId)) { + unanswered.add(questionId); + } + } + return unanswered; + } + + private List<String> collectUnansweredQuestionIdsByPiRound( + final List<Content> contents, + final Map<String, Integer> answeredQuestions + ) { + final List<String> unanswered = new ArrayList<>(); + + for (final Content content : contents) { + if (!"slide".equals(content.getQuestionType()) && (!answeredQuestions.containsKey(content.getId()) + || (answeredQuestions.containsKey(content.getId()) && answeredQuestions.get(content.getId()) != content.getPiRound()))) { + unanswered.add(content.getId()); + } + } + + return unanswered; + } + + private List<String> collectQuestionIds(final ViewResult viewResult) { + final List<String> ids = new ArrayList<>(); + for (final ViewResult.Row row : viewResult.getRows()) { + ids.add(row.getId()); + } + return ids; + } + + @Override + public List<Content> publishAllQuestions(final Session session, final boolean publish) { + final List<Content> contents = db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(session.getId())) + .endKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject())), + Content.class); + /* FIXME: caching */ + publishQuestions(session, publish, contents); + + return contents; + } + + @Caching(evict = { @CacheEvict(value = "contents", allEntries = true), + @CacheEvict(value = "skillquestions", key = "#session"), + @CacheEvict(value = "lecturequestions", key = "#session"), + @CacheEvict(value = "preparationquestions", key = "#session"), + @CacheEvict(value = "flashcardquestions", key = "#session") }) + @Override + public void publishQuestions(final Session session, final boolean publish, List<Content> contents) { + for (final Content content : contents) { + content.setActive(publish); + } + try { + db.executeBulk(contents); + } catch (final DbAccessException e) { + logger.error("Could not bulk publish all contents.", e); + } + } + + @Override + public List<Content> setVotingAdmissionForAllQuestions(final Session session, final boolean disableVoting) { + final List<Content> contents = db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(session.getId())) + .endKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject())) + .includeDocs(true), + Content.class); + /* FIXME: caching */ + setVotingAdmissions(session, disableVoting, contents); + + return contents; + } + + @Caching(evict = { @CacheEvict(value = "contents", allEntries = true), + @CacheEvict(value = "skillquestions", key = "#session"), + @CacheEvict(value = "lecturequestions", key = "#session"), + @CacheEvict(value = "preparationquestions", key = "#session"), + @CacheEvict(value = "flashcardquestions", key = "#session") }) + @Override + public void setVotingAdmissions(final Session session, final boolean disableVoting, List<Content> contents) { + for (final Content q : contents) { + if (!"flashcard".equals(q.getQuestionType())) { + q.setVotingDisabled(disableVoting); + } + } + + try { + db.executeBulk(contents); + } catch (final DbAccessException e) { + logger.error("Could not bulk set voting admission for all contents.", e); + } + } + + /* TODO: remove if this method is no longer used */ + @Override + public List<String> getQuestionIdsBySubject(Session session, String questionVariant, String subject) { + final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(session.getId(), questionVariant, 1, subject)) + .endKey(ComplexKey.of(session.getId(), questionVariant, 1, subject, ComplexKey.emptyObject()))); + + List<String> qids = new ArrayList<>(); + + for (final ViewResult.Row row : result.getRows()) { + final String s = row.getId(); + qids.add(s); + } + + return qids; + } + + @Override + public List<Content> getQuestionsByIds(List<String> ids, final Session session) { + return db.queryView(new ViewQuery().allDocs().keys(ids).includeDocs(true), Content.class); + } + + @Override + public List<String> getSubjects(Session session, String questionVariant) { + final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active") + .startKey(ComplexKey.of(session.getId(), questionVariant)) + .endKey(ComplexKey.of(session.getId(), questionVariant, ComplexKey.emptyObject()))); + + Set<String> uniqueSubjects = new HashSet<>(); + + for (final ViewResult.Row row : result.getRows()) { + uniqueSubjects.add(row.getKeyAsNode().get(3).asText()); + } + + return new ArrayList<>(uniqueSubjects); + } + + @Caching(evict = { @CacheEvict(value = "contents", allEntries = true), + @CacheEvict(value = "skillquestions", key = "#session"), + @CacheEvict(value = "lecturequestions", key = "#session"), + @CacheEvict(value = "preparationquestions", key = "#session"), + @CacheEvict(value = "flashcardquestions", key = "#session") }) + @Override + public void resetQuestionsRoundState(final Session session, List<Content> contents) { + for (final Content q : contents) { + q.setSessionId(session.getId()); + q.resetQuestionState(); + } + try { + db.executeBulk(contents); + } catch (final DbAccessException e) { + logger.error("Could not bulk reset all contents round state.", e); + } + } +} diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbLogEntryRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbLogEntryRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..45f802a232fcdb7875e54584c7cad17a1ad7f9b7 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbLogEntryRepository.java @@ -0,0 +1,45 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.persistance.couchdb; + +import de.thm.arsnova.entities.LogEntry; +import de.thm.arsnova.persistance.LogEntryRepository; +import org.ektorp.CouchDbConnector; +import org.ektorp.support.CouchDbRepositorySupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +public class CouchDbLogEntryRepository extends CouchDbRepositorySupport<LogEntry> implements LogEntryRepository { + private static final Logger logger = LoggerFactory.getLogger(CouchDbLogEntryRepository.class); + + public CouchDbLogEntryRepository(Class<LogEntry> type, CouchDbConnector db, boolean createIfNotExists) { + super(type, db, createIfNotExists); + } + + @Override + public void create(String event, LogEntry.LogLevel level, Map<String, Object> payload) { + LogEntry log = new LogEntry(event, level.ordinal(), payload); + try { + db.create(log); + } catch (final IllegalArgumentException e) { + logger.error("Logging of '{}' event to database failed.", event, e); + } + } +} diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdListRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdListRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..05bef30ab55e18cd69d2b6576bb54399c302c312 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdListRepository.java @@ -0,0 +1,46 @@ +package de.thm.arsnova.persistance.couchdb; + +import de.thm.arsnova.entities.MotdList; +import de.thm.arsnova.persistance.MotdListRepository; +import org.ektorp.CouchDbConnector; +import org.ektorp.DbAccessException; +import org.ektorp.support.CouchDbRepositorySupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; + +import java.util.List; + +public class CouchDbMotdListRepository extends CouchDbRepositorySupport<MotdList> implements MotdListRepository { + private static final Logger logger = LoggerFactory.getLogger(CouchDbMotdListRepository.class); + + public CouchDbMotdListRepository(Class<MotdList> type, CouchDbConnector db, boolean createIfNotExists) { + super(type, db, createIfNotExists); + } + + @Override + @Cacheable(cacheNames = "motdlist", key = "#p0") + public MotdList getMotdListForUser(final String username) { + List<MotdList> motdListList = queryView("by_username", username); + return motdListList.isEmpty() ? new MotdList() : motdListList.get(0); + } + + @Override + @CachePut(cacheNames = "motdlist", key = "#p0.username") + public MotdList createOrUpdateMotdList(MotdList motdlist) { + try { + if (motdlist.getId() != null) { + update(motdlist); + } else { + db.create(motdlist); + } + + return motdlist; + } catch (DbAccessException e) { + logger.error("Could not save MotD list {}.", motdlist, e); + } + + return null; + } +} diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..4aafdb0685fe201d4ff4479cc9cda8a517e1308c --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java @@ -0,0 +1,121 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.persistance.couchdb; + +import de.thm.arsnova.entities.Motd; +import de.thm.arsnova.persistance.MotdRepository; +import de.thm.arsnova.services.ISessionService; +import org.ektorp.CouchDbConnector; +import org.ektorp.support.CouchDbRepositorySupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; + +import java.util.ArrayList; +import java.util.List; + +public class CouchDbMotdRepository extends CouchDbRepositorySupport<Motd> implements MotdRepository { + private static final Logger logger = LoggerFactory.getLogger(CouchDbMotdRepository.class); + + @Autowired + private ISessionService sessionService; + + public CouchDbMotdRepository(Class<Motd> type, CouchDbConnector db, boolean createIfNotExists) { + super(type, db, createIfNotExists); + } + + @Override + public List<Motd> getAdminMotds() { + return getMotds("by_audience_for_global", null); + } + + @Override + @Cacheable(cacheNames = "motds", key = "'all'") + public List<Motd> getMotdsForAll() { + return getMotds("by_audience_for_global", "all"); + } + + @Override + @Cacheable(cacheNames = "motds", key = "'loggedIn'") + public List<Motd> getMotdsForLoggedIn() { + return getMotds("by_audience_for_global", "loggedIn"); + } + + @Override + @Cacheable(cacheNames = "motds", key = "'tutors'") + public List<Motd> getMotdsForTutors() { + final List<Motd> union = new ArrayList<>(); + union.addAll(getMotds("by_audience_for_global", "loggedIn")); + union.addAll(getMotds("by_audience_for_global", "tutors")); + + return union; + } + + @Override + @Cacheable(cacheNames = "motds", key = "'students'") + public List<Motd> getMotdsForStudents() { + final List<Motd> union = new ArrayList<>(); + union.addAll(getMotds("by_audience_for_global", "loggedIn")); + union.addAll(getMotds("by_audience_for_global", "students")); + + return union; + } + + @Override + @Cacheable(cacheNames = "motds", key = "('session').concat(#p0)") + public List<Motd> getMotdsForSession(final String sessionkey) { + return getMotds("by_sessionkey", sessionkey); + } + + private List<Motd> getMotds(String viewName, String key) { + return queryView(viewName, key); + } + + @Override + public Motd getMotdByKey(String key) { + List<Motd> motd = queryView("by_motdkey", key); + + return motd.get(0); + } + + @Override + @CacheEvict(cacheNames = "motds", key = "#p0.audience.concat(#p0.sessionkey)") + public Motd createOrUpdateMotd(Motd motd) { + String id = motd.getId(); + String rev = motd.getRevision(); + + if (null != id) { + Motd oldMotd = get(id); + motd.setMotdkey(oldMotd.getMotdkey()); + update(motd); + } else { + motd.setMotdkey(sessionService.generateKeyword()); + add(motd); + } + + return motd; + } + + @Override + @CacheEvict(cacheNames = "motds", key = "#p0.audience.concat(#p0.sessionkey)") + public boolean deleteMotd(Motd motd) { + return db.delete(motd) != null; + } +} diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..7689fe39c2fd6508fd987de2898ffd9f9c5fcf79 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java @@ -0,0 +1,666 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.persistance.couchdb; + +import de.thm.arsnova.connector.model.Course; +import de.thm.arsnova.entities.LoggedIn; +import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.SessionInfo; +import de.thm.arsnova.entities.User; +import de.thm.arsnova.entities.VisitedSession; +import de.thm.arsnova.entities.transport.Comment; +import de.thm.arsnova.entities.transport.ImportExportSession; +import de.thm.arsnova.exceptions.NotFoundException; +import de.thm.arsnova.persistance.LogEntryRepository; +import de.thm.arsnova.persistance.MotdRepository; +import de.thm.arsnova.persistance.SessionRepository; +import de.thm.arsnova.services.ISessionService; +import org.ektorp.ComplexKey; +import org.ektorp.CouchDbConnector; +import org.ektorp.DocumentNotFoundException; +import org.ektorp.UpdateConflictException; +import org.ektorp.ViewQuery; +import org.ektorp.ViewResult; +import org.ektorp.support.CouchDbRepositorySupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; + +import java.io.IOException; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session> implements SessionRepository { + private static final Logger logger = LoggerFactory.getLogger(CouchDbSessionRepository.class); + + @Autowired + private ISessionService sessionService; + + @Autowired + private LogEntryRepository dbLogger; + + @Autowired + private MotdRepository motdRepository; + + public CouchDbSessionRepository(Class<Session> type, CouchDbConnector db, boolean createIfNotExists) { + super(type, db, createIfNotExists); + } + + @Override + @Cacheable("sessions") + public Session getSessionFromKeyword(final String keyword) { + final List<Session> session = queryView("by_keyword", keyword); + + return !session.isEmpty() ? session.get(0) : null; + } + + @Override + @Cacheable("sessions") + public Session getSessionFromId(final String sessionId) { + return get(sessionId); + } + + @Override + @Caching(evict = @CacheEvict(cacheNames = "sessions", key = "#result.keyword")) + public Session saveSession(final User user, final Session session) { + session.setKeyword(sessionService.generateKeyword()); + session.setCreator(user.getUsername()); + session.setActive(true); + session.setFeedbackLock(false); + + try { + db.create(session); + } catch (final IllegalArgumentException e) { + logger.error("Could not save session to database.", e); + } + + return session.getId() != null ? session : null; + } + + @Override + public boolean sessionKeyAvailable(final String keyword) { + return getSessionFromKeyword(keyword) == null; + } + + private String getSessionKeyword(final String internalSessionId) throws IOException { + final Session session = get(internalSessionId); + if (session == null) { + logger.error("No session found for internal id {}.", internalSessionId); + + return null; + } + + return session.getKeyword(); + } + + @Override + @CachePut(value = "sessions") + public Session updateSessionOwnerActivity(final Session session) { + try { + /* Do not clutter CouchDB. Only update once every 3 hours. */ + if (session.getLastOwnerActivity() > System.currentTimeMillis() - 3 * 3600000) { + return session; + } + + session.setLastOwnerActivity(System.currentTimeMillis()); + update(session); + + return session; + } catch (final UpdateConflictException e) { + logger.error("Failed to update lastOwnerActivity for session {}.", session, e); + return session; + } + } + + @Override + public List<Session> getVisitedSessionsForUsername(String username, final int start, final int limit) { + final int qSkip = start > 0 ? start : -1; + final int qLimit = limit > 0 ? limit : -1; + + try { + ViewResult visitedSessionResult = db.queryView(createQuery("visited_sessions_by_user") + .designDocId("_design/LoggedIn").key(username)); + List<Session> visitedSessions = visitedSessionResult.getRows().stream().map(vs -> { + final Session s = new Session(); + s.setId(vs.getValueAsNode().get("_id").asText()); + s.setKeyword(vs.getValueAsNode().get("keyword").asText()); + s.setName(vs.getValueAsNode().get("name").asText()); + + return s; + }).collect(Collectors.toList()); + + if (visitedSessions.isEmpty()) { + return new ArrayList<>(); + } + + // Filter sessions that don't exist anymore, also filter my own sessions + final List<Session> result = new ArrayList<>(); + final List<Session> filteredSessions = new ArrayList<>(); + for (final Session s : visitedSessions) { + try { + /* FIXME: caching (getSessionFromKeyword) */ + final Session session = getSessionFromKeyword(s.getKeyword()); + if (session != null && !(session.getCreator().equals(username))) { + result.add(session); + } else { + filteredSessions.add(s); + } + } catch (final NotFoundException e) { + filteredSessions.add(s); + } + } + if (filteredSessions.isEmpty()) { + return result; + } + // Update document to remove sessions that don't exist anymore + List<VisitedSession> newVisitedSessions = new ArrayList<>(); + for (final Session s : result) { + newVisitedSessions.add(new VisitedSession(s)); + } + + try { + final LoggedIn loggedIn = db.get(LoggedIn.class, visitedSessionResult.getRows().get(0).getId()); + loggedIn.setVisitedSessions(newVisitedSessions); + db.update(loggedIn); + } catch (UpdateConflictException e) { + logger.error("Could not clean up LoggedIn document of {}.", username, e); + } + + return result; + } catch (DocumentNotFoundException e) { + return new ArrayList<>(); + } + } + + @Override + public List<SessionInfo> getMyVisitedSessionsInfo(final User user, final int start, final int limit) { + List<Session> sessions = getVisitedSessionsForUsername(user.getUsername(), start, limit); + if (sessions.isEmpty()) { + return new ArrayList<>(); + } + return this.getInfosForVisitedSessions(sessions, user); + } + + @Override + public List<Session> getCourseSessions(final List<Course> courses) { + return queryView("by_courseid", + ComplexKey.of(courses.stream().map(Course::getId).collect(Collectors.toList()))); + } + + @Override + @CachePut(value = "sessions") + public Session updateSession(final Session session) { + try { + update(session); + + return session; + } catch (final UpdateConflictException e) { + logger.error("Could not update session {}.", session, e); + } + + return null; + } + + @Override + @Caching(evict = { @CacheEvict("sessions"), @CacheEvict(cacheNames = "sessions", key = "#p0.keyword") }) + public Session changeSessionCreator(final Session session, final String newCreator) { + Session s = get(session.getId()); + s.setCreator(newCreator); + try { + update(s); + } catch (final UpdateConflictException e) { + logger.error("Could not update creator for session {}.", session, e); + } + + return s; + } + + @Override + @Caching(evict = { @CacheEvict("sessions"), @CacheEvict(cacheNames = "sessions", key = "#p0.keyword") }) + public int[] deleteSession(final Session session) { + /* FIXME: not yet migrated - move to service layer */ + throw new UnsupportedOperationException(); +// int[] count = new int[] {0, 0}; +// try { +// count = deleteAllQuestionsWithAnswers(session); +// remove(session); +// logger.debug("Deleted session document {} and related data.", session.getId()); +// dbLogger.log("delete", "type", "session", "id", session.getId()); +// } catch (final Exception e) { +// /* TODO: improve error handling */ +// logger.error("Could not delete session {}.", session, e); +// } +// +// return count; + } + + @Override + public int[] deleteInactiveGuestSessions(long lastActivityBefore) { + ViewResult result = db.queryView( + createQuery("by_lastactivity_for_guests").endKey(lastActivityBefore)); + int[] count = new int[3]; + + for (ViewResult.Row row : result.getRows()) { + Session s = new Session(); + s.setId(row.getId()); + s.setRevision(row.getValueAsNode().get("_rev").asText()); + int[] qaCount = deleteSession(s); + count[1] += qaCount[0]; + count[2] += qaCount[1]; + } + + if (!result.isEmpty()) { + logger.info("Deleted {} inactive guest sessions.", result.getSize()); + dbLogger.log("cleanup", "type", "session", "sessionCount", result.getSize(), "questionCount", count[1], "answerCount", count[2]); + } + count[0] = result.getSize(); + + return count; + } + + @Override + public SessionInfo importSession(User user, ImportExportSession importSession) { + /* FIXME: not yet migrated - move to service layer */ + throw new UnsupportedOperationException(); +// final Session session = this.saveSession(user, importSession.generateSessionEntity(user)); +// List<Document> questions = new ArrayList<>(); +// // We need to remember which answers belong to which question. +// // The answers need a questionId, so we first store the questions to get the IDs. +// // Then we update the answer objects and store them as well. +// Map<Document, ImportExportSession.ImportExportContent> mapping = new HashMap<>(); +// // Later, generate all answer documents +// List<Document> answers = new ArrayList<>(); +// // We can then push answers together with comments in one large bulk request +// List<Document> interposedQuestions = new ArrayList<>(); +// // Motds shouldn't be forgotten, too +// List<Document> motds = new ArrayList<>(); +// try { +// // add session id to all questions and generate documents +// for (ImportExportSession.ImportExportContent question : importSession.getQuestions()) { +// Document doc = toQuestionDocument(session, question); +// question.setSessionId(session.getId()); +// questions.add(doc); +// mapping.put(doc, question); +// } +// database.bulkSaveDocuments(questions.toArray(new Document[questions.size()])); +// +// // bulk import answers together with interposed questions +// for (Map.Entry<Document, ImportExportSession.ImportExportContent> entry : mapping.entrySet()) { +// final Document doc = entry.getKey(); +// final ImportExportSession.ImportExportContent question = entry.getValue(); +// question.setId(doc.getId()); +// question.setRevision(doc.getRev()); +// for (de.thm.arsnova.entities.transport.Answer answer : question.getAnswers()) { +// final Answer a = answer.generateAnswerEntity(user, question); +// final Document answerDoc = new Document(); +// answerDoc.put("type", "skill_question_answer"); +// answerDoc.put("sessionId", a.getSessionId()); +// answerDoc.put("questionId", a.getQuestionId()); +// answerDoc.put("answerSubject", a.getAnswerSubject()); +// answerDoc.put("questionVariant", a.getQuestionVariant()); +// answerDoc.put("questionValue", a.getQuestionValue()); +// answerDoc.put("answerText", a.getAnswerText()); +// answerDoc.put("answerTextRaw", a.getAnswerTextRaw()); +// answerDoc.put("timestamp", a.getTimestamp()); +// answerDoc.put("piRound", a.getPiRound()); +// answerDoc.put("abstention", a.isAbstention()); +// answerDoc.put("successfulFreeTextAnswer", a.isSuccessfulFreeTextAnswer()); +// // we do not store the user's name +// answerDoc.put("user", ""); +// answers.add(answerDoc); +// } +// } +// for (de.thm.arsnova.entities.transport.Comment i : importSession.getFeedbackQuestions()) { +// final Document q = new Document(); +// q.put("type", "interposed_question"); +// q.put("sessionId", session.getId()); +// q.put("subject", i.getSubject()); +// q.put("text", i.getText()); +// q.put("timestamp", i.getTimestamp()); +// q.put("read", i.isRead()); +// // we do not store the creator's name +// q.put("creator", ""); +// interposedQuestions.add(q); +// } +// for (Motd m : importSession.getMotds()) { +// final Document d = new Document(); +// d.put("type", "motd"); +// d.put("motdkey", m.getMotdkey()); +// d.put("title", m.getTitle()); +// d.put("text", m.getText()); +// d.put("audience", m.getAudience()); +// d.put("sessionkey", session.getKeyword()); +// d.put("startdate", String.valueOf(m.getStartdate().getTime())); +// d.put("enddate", String.valueOf(m.getEnddate().getTime())); +// motds.add(d); +// } +// List<Document> documents = new ArrayList<>(answers); +// database.bulkSaveDocuments(interposedQuestions.toArray(new Document[interposedQuestions.size()])); +// database.bulkSaveDocuments(motds.toArray(new Document[motds.size()])); +// database.bulkSaveDocuments(documents.toArray(new Document[documents.size()])); +// } catch (IOException e) { +// logger.error("Could not import session.", e); +// // Something went wrong, delete this session since we do not want a partial import +// this.deleteSession(session); +// return null; +// } +// return this.calculateSessionInfo(importSession, session); + } + + @Override + public ImportExportSession exportSession(String sessionkey, Boolean withAnswers, Boolean withFeedbackQuestions) { + /* FIXME: not yet migrated - move to service layer */ + throw new UnsupportedOperationException(); +// ImportExportSession importExportSession = new ImportExportSession(); +// Session session = getDatabaseDao().getSessionFromKeyword(sessionkey); +// importExportSession.setSessionFromSessionObject(session); +// List<Content> questionList = getDatabaseDao().getAllSkillQuestions(session); +// for (Content question : questionList) { +// List<de.thm.arsnova.entities.transport.Answer> answerList = new ArrayList<>(); +// if (withAnswers) { +// for (Answer a : this.getDatabaseDao().getAllAnswers(question)) { +// de.thm.arsnova.entities.transport.Answer transportAnswer = new de.thm.arsnova.entities.transport.Answer(a); +// answerList.add(transportAnswer); +// } +// // getAllAnswers does not grep for whole answer object so i need to add empty entries for abstentions +// int i = this.getDatabaseDao().getAbstentionAnswerCount(question.getId()); +// for (int b = 0; b < i; b++) { +// de.thm.arsnova.entities.transport.Answer ans = new de.thm.arsnova.entities.transport.Answer(); +// ans.setAnswerSubject(""); +// ans.setAnswerImage(""); +// ans.setAnswerText(""); +// ans.setAbstention(true); +// answerList.add(ans); +// } +// } +// importExportSession.addQuestionWithAnswers(question, answerList); +// } +// if (withFeedbackQuestions) { +// List<de.thm.arsnova.entities.transport.Comment> interposedQuestionList = new ArrayList<>(); +// for (Comment i : getDatabaseDao().getInterposedQuestions(session, 0, 0)) { +// de.thm.arsnova.entities.transport.Comment transportInterposedQuestion = new de.thm.arsnova.entities.transport.Comment(i); +// interposedQuestionList.add(transportInterposedQuestion); +// } +// importExportSession.setFeedbackQuestions(interposedQuestionList); +// } +// if (withAnswers) { +// importExportSession.setSessionInfo(this.calculateSessionInfo(importExportSession, session)); +// } +// importExportSession.setMotds(motdRepository.getMotdsForSession(session.getKeyword())); +// return importExportSession; + } + + private SessionInfo calculateSessionInfo(ImportExportSession importExportSession, Session session) { + int unreadComments = 0; + int numUnanswered = 0; + int numAnswers = 0; + for (Comment i : importExportSession.getFeedbackQuestions()) { + if (!i.isRead()) { + unreadComments++; + } + } + for (ImportExportSession.ImportExportContent question : importExportSession.getQuestions()) { + numAnswers += question.getAnswers().size(); + if (question.getAnswers().isEmpty()) { + numUnanswered++; + } + } + final SessionInfo info = new SessionInfo(session); + info.setNumQuestions(importExportSession.getQuestions().size()); + info.setNumUnanswered(numUnanswered); + info.setNumAnswers(numAnswers); + info.setNumInterposed(importExportSession.getFeedbackQuestions().size()); + info.setNumUnredInterposed(unreadComments); + return info; + } + + @Override + public List<Session> getMySessions(final User user, final int start, final int limit) { + return getSessionsForUsername(user.getUsername(), start, limit); + } + + @Override + public List<Session> getSessionsForUsername(String username, final int start, final int limit) { + final int qSkip = start > 0 ? start : -1; + final int qLimit = limit > 0 ? limit : -1; + + /* TODO: Only load IDs and check against cache for data. */ + List<Session> sessions = db.queryView( + createQuery("partial_by_sessiontype_creator_name") + .skip(qSkip) + .limit(qLimit) + .startKey(ComplexKey.of(null, username)) + .endKey(ComplexKey.of(null, username, ComplexKey.emptyObject())) + .includeDocs(true), + Session.class); + + return sessions; + } + + @Override + public List<Session> getPublicPoolSessions() { + // TODO replace with new view + return queryView("partial_by_ppsubject_name_for_publicpool"); + } + + @Override + public List<SessionInfo> getPublicPoolSessionsInfo() { + final List<Session> sessions = this.getPublicPoolSessions(); + return getInfosForSessions(sessions); + } + + @Override + public List<Session> getMyPublicPoolSessions(final User user) { + /* TODO: Only load IDs and check against cache for data. */ + return db.queryView( + createQuery("partial_by_sessiontype_creator_name") + .startKey(ComplexKey.of("public_pool", user.getUsername())) + .endKey(ComplexKey.of("public_pool", user.getUsername(), ComplexKey.emptyObject())) + .includeDocs(true), + Session.class); + } + + @Override + public List<SessionInfo> getMyPublicPoolSessionsInfo(final User user) { + final List<Session> sessions = this.getMyPublicPoolSessions(user); + if (sessions.isEmpty()) { + return new ArrayList<>(); + } + return getInfosForSessions(sessions); + } + + @Override + public List<SessionInfo> getMySessionsInfo(final User user, final int start, final int limit) { + final List<Session> sessions = this.getMySessions(user, start, limit); + if (sessions.isEmpty()) { + return new ArrayList<>(); + } + return getInfosForSessions(sessions); + } + + private List<SessionInfo> getInfosForSessions(final List<Session> sessions) { + List<String> sessionIds = sessions.stream().map(Session::getId).collect(Collectors.toList()); + final ViewQuery questionCountView = createQuery("by_sessionid").designDocId("_design/Content") + .group(true).keys(sessionIds); + final ViewQuery answerCountView = createQuery("by_sessionid").designDocId("_design/Answer") + .group(true).keys(sessionIds); + final ViewQuery commentCountView = createQuery("by_sessionid").designDocId("_design/Comment") + .group(true).keys(sessionIds); + final ViewQuery unreadCommentCountView = createQuery("by_sessionid_read").designDocId("_design/Comment") + .group(true).keys(sessions.stream().map(session -> ComplexKey.of(session.getId(), false)).collect(Collectors.toList())); + + return getSessionInfoData(sessions, questionCountView, answerCountView, commentCountView, unreadCommentCountView); + } + + private List<SessionInfo> getInfosForVisitedSessions(final List<Session> sessions, final User user) { + final ViewQuery answeredQuestionsView = createQuery("by_user_sessionid").designDocId("_design/Answer") + .keys(sessions.stream().map(session -> ComplexKey.of(user.getUsername(), session.getId())).collect(Collectors.toList())); + final ViewQuery questionIdsView = createQuery("by_sessionid").designDocId("_design/Content") + .keys(sessions.stream().map(Session::getId).collect(Collectors.toList())); + + return getVisitedSessionInfoData(sessions, answeredQuestionsView, questionIdsView); + } + + private List<SessionInfo> getVisitedSessionInfoData(List<Session> sessions, + ViewQuery answeredQuestionsView, ViewQuery questionIdsView) { + final Map<String, Set<String>> answeredQuestionsMap = new HashMap<>(); + final Map<String, Set<String>> questionIdMap = new HashMap<>(); + + // Maps a session ID to a set of question IDs of answered questions of that session + for (final ViewResult.Row row : db.queryView(answeredQuestionsView).getRows()) { + final String sessionId = row.getKey(); + final String questionId = row.getValue(); + Set<String> questionIdsInSession = answeredQuestionsMap.get(sessionId); + if (questionIdsInSession == null) { + questionIdsInSession = new HashSet<>(); + } + questionIdsInSession.add(questionId); + answeredQuestionsMap.put(sessionId, questionIdsInSession); + } + + // Maps a session ID to a set of question IDs of that session + for (final ViewResult.Row row : db.queryView(questionIdsView).getRows()) { + final String sessionId = row.getKey(); + final String questionId = row.getId(); + Set<String> questionIdsInSession = questionIdMap.get(sessionId); + if (questionIdsInSession == null) { + questionIdsInSession = new HashSet<>(); + } + questionIdsInSession.add(questionId); + questionIdMap.put(sessionId, questionIdsInSession); + } + + // For each session, count the question IDs that are not yet answered + Map<String, Integer> unansweredQuestionsCountMap = new HashMap<>(); + for (final Session s : sessions) { + if (!questionIdMap.containsKey(s.getId())) { + continue; + } + // Note: create a copy of the first set so that we don't modify the contents in the original set + Set<String> questionIdsInSession = new HashSet<>(questionIdMap.get(s.getId())); + Set<String> answeredQuestionIdsInSession = answeredQuestionsMap.get(s.getId()); + if (answeredQuestionIdsInSession == null) { + answeredQuestionIdsInSession = new HashSet<>(); + } + questionIdsInSession.removeAll(answeredQuestionIdsInSession); + unansweredQuestionsCountMap.put(s.getId(), questionIdsInSession.size()); + } + + List<SessionInfo> sessionInfos = new ArrayList<>(); + for (Session session : sessions) { + int numUnanswered = 0; + + if (unansweredQuestionsCountMap.containsKey(session.getId())) { + numUnanswered = unansweredQuestionsCountMap.get(session.getId()); + } + SessionInfo info = new SessionInfo(session); + info.setNumUnanswered(numUnanswered); + sessionInfos.add(info); + } + return sessionInfos; + } + + private List<SessionInfo> getSessionInfoData(final List<Session> sessions, + final ViewQuery questionCountView, + final ViewQuery answerCountView, + final ViewQuery commentCountView, + final ViewQuery unreadCommentCountView) { + Map<String, Integer> questionCountMap = db.queryView(questionCountView).getRows() + .stream().map(row -> new AbstractMap.SimpleImmutableEntry<>(row.getKey(), row.getValueAsInt())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + Map<String, Integer> answerCountMap = db.queryView(answerCountView).getRows() + .stream().map(row -> new AbstractMap.SimpleImmutableEntry<>(row.getKey(), row.getValueAsInt())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + Map<String, Integer> commentCountMap = db.queryView(commentCountView).getRows() + .stream().map(row -> new AbstractMap.SimpleImmutableEntry<>(row.getKey(), row.getValueAsInt())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + Map<String, Integer> unreadCommentCountMap = db.queryView(unreadCommentCountView).getRows() + .stream().map(row -> new AbstractMap.SimpleImmutableEntry<>(row.getKey(), row.getValueAsInt())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + List<SessionInfo> sessionInfos = new ArrayList<>(); + for (Session session : sessions) { + int numQuestions = 0; + int numAnswers = 0; + int numComments = 0; + int numUnreadComments = 0; + if (questionCountMap.containsKey(session.getId())) { + numQuestions = questionCountMap.get(session.getId()); + } + if (answerCountMap.containsKey(session.getId())) { + numAnswers = answerCountMap.get(session.getId()); + } + if (commentCountMap.containsKey(session.getId())) { + numComments = commentCountMap.get(session.getId()); + } + if (unreadCommentCountMap.containsKey(session.getId())) { + numUnreadComments = unreadCommentCountMap.get(session.getId()); + } + + SessionInfo info = new SessionInfo(session); + info.setNumQuestions(numQuestions); + info.setNumAnswers(numAnswers); + info.setNumInterposed(numComments); + info.setNumUnredInterposed(numUnreadComments); + sessionInfos.add(info); + } + return sessionInfos; + } + + @Override + public LoggedIn registerAsOnlineUser(final User user, final Session session) { + LoggedIn loggedIn = new LoggedIn(); + try { + List<LoggedIn> loggedInList = db.queryView(createQuery("all").designDocId("_design/LoggedIn").key(user.getUsername()), LoggedIn.class); + + if (!loggedInList.isEmpty()) { + loggedIn = loggedInList.get(0); + + /* Do not clutter CouchDB. Only update once every 3 hours per session. */ + if (loggedIn.getSessionId().equals(session.getId()) && loggedIn.getTimestamp() > System.currentTimeMillis() - 3 * 3600000) { + return loggedIn; + } + } + + loggedIn.setUser(user.getUsername()); + loggedIn.setSessionId(session.getId()); + loggedIn.addVisitedSession(session); + loggedIn.updateTimestamp(); + + if (loggedIn.getId() == null) { + db.create(loggedIn); + } else { + db.update(loggedIn); + } + } catch (final UpdateConflictException e) { + logger.error("Could not save LoggedIn document of {}.", user.getUsername(), e); + } + + return loggedIn; + } +} diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionStatisticsRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionStatisticsRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..be48429e26556dd2de2f14d224a704fd592c2148 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionStatisticsRepository.java @@ -0,0 +1,54 @@ +package de.thm.arsnova.persistance.couchdb; + +import com.fasterxml.jackson.databind.JsonNode; +import de.thm.arsnova.domain.CourseScore; +import de.thm.arsnova.entities.Session; +import de.thm.arsnova.persistance.SessionStatisticsRepository; +import org.ektorp.ComplexKey; +import org.ektorp.CouchDbConnector; +import org.ektorp.ViewResult; +import org.ektorp.support.CouchDbRepositorySupport; +import org.springframework.cache.annotation.Cacheable; + +public class CouchDbSessionStatisticsRepository extends CouchDbRepositorySupport implements SessionStatisticsRepository { + public CouchDbSessionStatisticsRepository(Class type, CouchDbConnector db, boolean createIfNotExists) { + super(type, db, "learning_progress", createIfNotExists); + } + + @Cacheable("learningprogress") + @Override + public CourseScore getLearningProgress(final Session session) { + final ViewResult maximumValueResult = db.queryView(createQuery("maximum_value_of_question") + .startKey(ComplexKey.of(session.getId())) + .endKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject()))); + final ViewResult answerSumResult = db.queryView(createQuery("question_value_achieved_for_user") + .startKey(ComplexKey.of(session.getId())) + .endKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject()))); + final CourseScore courseScore = new CourseScore(); + + // no results found + if (maximumValueResult.isEmpty() && answerSumResult.isEmpty()) { + return courseScore; + } + + // collect mapping (questionId -> max value) + for (ViewResult.Row row : maximumValueResult) { + final String questionId = row.getKeyAsNode().get(1).asText(); + final JsonNode value = row.getValueAsNode(); + final int questionScore = value.get("value").asInt(); + final String questionVariant = value.get("questionVariant").asText(); + final int piRound = value.get("piRound").asInt(); + courseScore.addQuestion(questionId, questionVariant, piRound, questionScore); + } + // collect mapping (questionId -> (user -> value)) + for (ViewResult.Row row : answerSumResult) { + final String username = row.getKeyAsNode().get(1).asText(); + final JsonNode value = row.getValueAsNode(); + final String questionId = value.get("questionId").asText(); + final int userscore = value.get("score").asInt(); + final int piRound = value.get("piRound").asInt(); + courseScore.addAnswer(questionId, piRound, username, userscore); + } + return courseScore; + } +} diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbStatisticsRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbStatisticsRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..20ed9067693b9c08dfa11ef22309197432dd5e58 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbStatisticsRepository.java @@ -0,0 +1,88 @@ +package de.thm.arsnova.persistance.couchdb; + +import de.thm.arsnova.entities.Statistics; +import de.thm.arsnova.persistance.StatisticsRepository; +import org.ektorp.CouchDbConnector; +import org.ektorp.DbAccessException; +import org.ektorp.ViewResult; +import org.ektorp.support.CouchDbRepositorySupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.annotation.Cacheable; + +import java.util.HashSet; +import java.util.Set; + +public class CouchDbStatisticsRepository extends CouchDbRepositorySupport implements StatisticsRepository { + private static final Logger logger = LoggerFactory.getLogger(CouchDbStatisticsRepository.class); + + public CouchDbStatisticsRepository(Class type, CouchDbConnector db, boolean createIfNotExists) { + super(type, db, "statistics", createIfNotExists); + } + + @Cacheable("statistics") + @Override + public Statistics getStatistics() { + final Statistics stats = new Statistics(); + try { + final ViewResult statsResult = db.queryView(createQuery("statistics").group(true)); + final ViewResult creatorResult = db.queryView(createQuery("unique_session_creators").group(true)); + final ViewResult studentUserResult = db.queryView(createQuery("active_student_users").group(true)); + + if (!statsResult.isEmpty()) { + for (ViewResult.Row row: statsResult.getRows()) { + final int value = row.getValueAsInt(); + switch (row.getKey()) { + case "openSessions": + stats.setOpenSessions(stats.getOpenSessions() + value); + break; + case "closedSessions": + stats.setClosedSessions(stats.getClosedSessions() + value); + break; + case "deletedSessions": + /* Deleted sessions are not exposed separately for now. */ + stats.setClosedSessions(stats.getClosedSessions() + value); + break; + case "answers": + stats.setAnswers(stats.getAnswers() + value); + break; + case "lectureQuestions": + stats.setLectureQuestions(stats.getLectureQuestions() + value); + break; + case "preparationQuestions": + stats.setPreparationQuestions(stats.getPreparationQuestions() + value); + break; + case "interposedQuestions": + stats.setInterposedQuestions(stats.getInterposedQuestions() + value); + break; + case "conceptQuestions": + stats.setConceptQuestions(stats.getConceptQuestions() + value); + break; + case "flashcards": + stats.setFlashcards(stats.getFlashcards() + value); + break; + } + } + } + if (!creatorResult.isEmpty()) { + Set<String> creators = new HashSet<>(); + for (ViewResult.Row row: statsResult.getRows()) { + creators.add(row.getKey()); + } + stats.setCreators(creators.size()); + } + if (!studentUserResult.isEmpty()) { + Set<String> students = new HashSet<>(); + for (ViewResult.Row row: statsResult.getRows()) { + students.add(row.getKey()); + } + stats.setActiveStudents(students.size()); + } + return stats; + } catch (final DbAccessException e) { + logger.error("Could not retrieve statistics.", e); + } + + return stats; + } +} diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbUserRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbUserRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..91d280d487187c15b1b466dd73522c569316f442 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbUserRepository.java @@ -0,0 +1,110 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2017 The ARSnova Team + * + * 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.persistance.couchdb; + +import com.google.common.collect.Lists; +import de.thm.arsnova.entities.DbUser; +import de.thm.arsnova.persistance.UserRepository; +import org.ektorp.BulkDeleteDocument; +import org.ektorp.CouchDbConnector; +import org.ektorp.DocumentOperationResult; +import org.ektorp.ViewQuery; +import org.ektorp.ViewResult; +import org.ektorp.support.CouchDbRepositorySupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +public class CouchDbUserRepository extends CouchDbRepositorySupport<DbUser> implements UserRepository { + private static final int BULK_PARTITION_SIZE = 500; + + private static final Logger logger = LoggerFactory.getLogger(CouchDbUserRepository.class); + + public CouchDbUserRepository(Class<DbUser> type, CouchDbConnector db, boolean createIfNotExists) { + super(type, db, createIfNotExists); + } + + private void log(Object... strings) { + /* TODO: method stub */ + } + + @Override + public DbUser createOrUpdateUser(final DbUser user) { + String id = user.getId(); + + if (null != id) { + db.update(user); + } + + db.create(user); + + return user; + } + + @Override + public DbUser findUserByUsername(String username) { + List<DbUser> users = queryView("by_username", username); + + return !users.isEmpty() ? users.get(0) : null; + } + + @Override + public boolean deleteUser(final DbUser user) { + if (db.delete(user) != null) { + log("delete", "type", "user", "id", user.getId()); + return true; + } else { + logger.error("Could not delete user {}", user.getId()); + return false; + } + } + + @Override + public int deleteInactiveUsers(long lastActivityBefore) { + ViewQuery q = createQuery("by_creation_for_inactive").endKey(lastActivityBefore); + List<ViewResult.Row> rows = db.queryView(q).getRows(); + + int count = 0; + final List<List<ViewResult.Row>> partitions = Lists.partition(rows, BULK_PARTITION_SIZE); + for (List<ViewResult.Row> partition: partitions) { + final List<BulkDeleteDocument> newDocs = new ArrayList<>(); + for (ViewResult.Row oldDoc : partition) { + final BulkDeleteDocument newDoc = new BulkDeleteDocument(oldDoc.getId(), oldDoc.getValue()); + newDocs.add(newDoc); + logger.debug("Marked user document {} for deletion.", oldDoc.getId()); + } + + if (newDocs.size() > 0) { + List<DocumentOperationResult> results = db.executeBulk(newDocs); + if (!results.isEmpty()) { + /* TODO: This condition should be improved so that it checks the operation results. */ + count += newDocs.size(); + } + } + } + + if (count > 0) { + logger.info("Deleted {} inactive users.", count); + log("cleanup", "type", "user", "count", count); + } + + return count; + } +} diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbVisitedSessionRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbVisitedSessionRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..e682387a7a177cec90a529bbced532dec7af4c3c --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbVisitedSessionRepository.java @@ -0,0 +1,70 @@ +package de.thm.arsnova.persistance.couchdb; + +import com.google.common.collect.Lists; +import de.thm.arsnova.entities.VisitedSession; +import de.thm.arsnova.persistance.LogEntryRepository; +import de.thm.arsnova.persistance.VisitedSessionRepository; +import org.ektorp.BulkDeleteDocument; +import org.ektorp.CouchDbConnector; +import org.ektorp.DbAccessException; +import org.ektorp.DocumentOperationResult; +import org.ektorp.ViewResult; +import org.ektorp.support.CouchDbRepositorySupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.List; + +public class CouchDbVisitedSessionRepository extends CouchDbRepositorySupport<VisitedSession> implements VisitedSessionRepository { + private static final int BULK_PARTITION_SIZE = 500; + + private static final Logger logger = LoggerFactory.getLogger(CouchDbVisitedSessionRepository.class); + + @Autowired + private LogEntryRepository dbLogger; + + public CouchDbVisitedSessionRepository(Class<VisitedSession> type, CouchDbConnector db, boolean createIfNotExists) { + super(type, db, createIfNotExists); + } + + @Override + public int deleteInactiveGuestVisitedSessionLists(long lastActivityBefore) { + try { + ViewResult result = db.queryView(createQuery("by_last_activity_for_guests").endKey(lastActivityBefore)); + + int count = 0; + List<List<ViewResult.Row>> partitions = Lists.partition(result.getRows(), BULK_PARTITION_SIZE); + for (List<ViewResult.Row> partition: partitions) { + final List<BulkDeleteDocument> newDocs = new ArrayList<>(); + for (final ViewResult.Row oldDoc : partition) { + final BulkDeleteDocument newDoc = new BulkDeleteDocument(oldDoc.getId(), oldDoc.getValueAsNode().get("_rev").asText()); + newDocs.add(newDoc); + logger.debug("Marked logged_in document {} for deletion.", oldDoc.getId()); + /* Use log type 'user' since effectively the user is deleted in case of guests */ + dbLogger.log("delete", "type", "user", "id", oldDoc.getId()); + } + + if (!newDocs.isEmpty()) { + List<DocumentOperationResult> results = db.executeBulk(newDocs); + count += newDocs.size() - results.size(); + if (!results.isEmpty()) { + logger.error("Could not bulk delete some visited session lists."); + } + } + } + + if (count > 0) { + logger.info("Deleted {} visited session lists of inactive users.", count); + dbLogger.log("cleanup", "type", "visitedsessions", "count", count); + } + + return count; + } catch (DbAccessException e) { + logger.error("Could not delete visited session lists of inactive users.", e); + } + + return 0; + } +} diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/InitializingCouchDbConnector.java b/src/main/java/de/thm/arsnova/persistance/couchdb/InitializingCouchDbConnector.java new file mode 100644 index 0000000000000000000000000000000000000000..b6675a48d380aa840ab07440b3d39d65fb4c6511 --- /dev/null +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/InitializingCouchDbConnector.java @@ -0,0 +1,85 @@ +package de.thm.arsnova.persistance.couchdb; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.ektorp.CouchDbInstance; +import org.ektorp.impl.ObjectMapperFactory; +import org.ektorp.impl.StdCouchDbConnector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.FileCopyUtils; + +import javax.script.Bindings; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +public class InitializingCouchDbConnector extends StdCouchDbConnector implements InitializingBean, ResourceLoaderAware { + private static final Logger logger = LoggerFactory.getLogger(InitializingCouchDbConnector.class); + private final List<Bindings> docs = new ArrayList<>(); + + private ResourceLoader resourceLoader; + + public InitializingCouchDbConnector(String databaseName, CouchDbInstance dbInstance) { + super(databaseName, dbInstance); + } + + public InitializingCouchDbConnector(String databaseName, CouchDbInstance dbi, ObjectMapperFactory om) { + super(databaseName, dbi, om); + } + + protected void loadDesignDocFiles() throws IOException, ScriptException { + final ScriptEngine engine = new ScriptEngineManager().getEngineByMimeType("application/javascript"); + engine.eval(new InputStreamReader(new ClassPathResource("couchdb/jsToJson.js").getInputStream())); + + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource[] resources = resolver.getResources("classpath:couchdb/*.design.js"); + for (Resource resource : resources) { + logger.debug("Loading CouchDB design doc: {}", resource.getFilename()); + String js = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream())); + /* Reset designDoc before parsing a new one. */ + engine.eval("var designDoc = null;" + js); + Bindings jsonObject = (Bindings) engine.eval("jsToJson(designDoc)"); + docs.add(jsonObject); + } + } + + protected void createDesignDocs() { + docs.forEach(doc -> { + if (logger.isDebugEnabled()) { + try { + logger.debug("Creating design doc:\n{}", objectMapper.writeValueAsString(doc)); + } catch (JsonProcessingException e) { + logger.warn("Failed to serialize design doc.", e); + } + } + String rev = getCurrentRevision((String) doc.get("_id")); + if (rev == null) { + create(doc); + } else { + doc.put("_rev", rev); + update(doc); + } + }); + } + + @Override + public void afterPropertiesSet() throws Exception { + loadDesignDocFiles(); + createDesignDocs(); + } + + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } +} diff --git a/src/main/java/de/thm/arsnova/security/ApplicationPermissionEvaluator.java b/src/main/java/de/thm/arsnova/security/ApplicationPermissionEvaluator.java index df5f6c9f83070c7e1648fca4c0dd125489683351..fd72edb9c147432e3fa754f924cea85caac97b32 100644 --- a/src/main/java/de/thm/arsnova/security/ApplicationPermissionEvaluator.java +++ b/src/main/java/de/thm/arsnova/security/ApplicationPermissionEvaluator.java @@ -17,12 +17,14 @@ */ package de.thm.arsnova.security; -import de.thm.arsnova.dao.IDatabaseDao; -import de.thm.arsnova.entities.InterposedQuestion; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Comment; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; import de.thm.arsnova.exceptions.UnauthorizedException; +import de.thm.arsnova.persistance.CommentRepository; +import de.thm.arsnova.persistance.ContentRepository; +import de.thm.arsnova.persistance.SessionRepository; import org.pac4j.oauth.profile.facebook.FacebookProfile; import org.pac4j.oauth.profile.google2.Google2Profile; import org.pac4j.oauth.profile.twitter.TwitterProfile; @@ -45,7 +47,13 @@ public class ApplicationPermissionEvaluator implements PermissionEvaluator { private String[] adminAccounts; @Autowired - private IDatabaseDao dao; + private SessionRepository sessionRepository; + + @Autowired + private CommentRepository commentRepository; + + @Autowired + private ContentRepository contentRepository; @Override public boolean hasPermission( @@ -80,12 +88,12 @@ public class ApplicationPermissionEvaluator implements PermissionEvaluator { && checkSessionPermission(username, targetId, permission)) { return true; } else if ( - "question".equals(targetType) + "content".equals(targetType) && checkQuestionPermission(username, targetId, permission) ) { return true; } else if ( - "interposedquestion".equals(targetType) + "comment".equals(targetType) && checkInterposedQuestionPermission(username, targetId, permission) ) { return true; @@ -104,9 +112,9 @@ public class ApplicationPermissionEvaluator implements PermissionEvaluator { final Object permission ) { if (permission instanceof String && ("owner".equals(permission) || "write".equals(permission))) { - return dao.getSessionFromKeyword(targetId.toString()).getCreator().equals(username); + return sessionRepository.getSessionFromKeyword(targetId.toString()).getCreator().equals(username); } else if (permission instanceof String && "read".equals(permission)) { - return dao.getSessionFromKeyword(targetId.toString()).isActive(); + return sessionRepository.getSessionFromKeyword(targetId.toString()).isActive(); } return false; } @@ -117,9 +125,9 @@ public class ApplicationPermissionEvaluator implements PermissionEvaluator { final Object permission ) { if (permission instanceof String && "owner".equals(permission)) { - final Question question = dao.getQuestion(targetId.toString()); - if (question != null) { - final Session session = dao.getSessionFromId(question.getSessionId()); + final Content content = contentRepository.getQuestion(targetId.toString()); + if (content != null) { + final Session session = sessionRepository.getSessionFromId(content.getSessionId()); return session != null && session.getCreator().equals(username); } @@ -133,14 +141,14 @@ public class ApplicationPermissionEvaluator implements PermissionEvaluator { final Object permission ) { if (permission instanceof String && "owner".equals(permission)) { - final InterposedQuestion question = dao.getInterposedQuestion(targetId.toString()); - if (question != null) { - // Does the creator want to delete his own question? - if (question.getCreator() != null && question.getCreator().equals(username)) { + final Comment comment = commentRepository.getInterposedQuestion(targetId.toString()); + if (comment != null) { + // Does the creator want to delete his own comment? + if (comment.getCreator() != null && comment.getCreator().equals(username)) { return true; } // Allow deletion if requested by session owner - final Session session = dao.getSessionFromKeyword(question.getSessionId()); + final Session session = sessionRepository.getSessionFromKeyword(comment.getSessionId()); return session != null && session.getCreator().equals(username); } diff --git a/src/main/java/de/thm/arsnova/security/DbUserDetailsService.java b/src/main/java/de/thm/arsnova/security/DbUserDetailsService.java index cb1000e97b2668b3071b7ded3a8b989974452481..8e3b308cb42e8dd9ff1cdd5e7fbc1ea593b6566d 100644 --- a/src/main/java/de/thm/arsnova/security/DbUserDetailsService.java +++ b/src/main/java/de/thm/arsnova/security/DbUserDetailsService.java @@ -17,8 +17,8 @@ */ package de.thm.arsnova.security; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.DbUser; +import de.thm.arsnova.persistance.UserRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -39,7 +39,7 @@ import java.util.List; @Service public class DbUserDetailsService implements UserDetailsService { @Autowired - private IDatabaseDao dao; + private UserRepository userRepository; private static final Logger logger = LoggerFactory .getLogger(DbUserDetailsService.class); @@ -48,7 +48,7 @@ public class DbUserDetailsService implements UserDetailsService { public UserDetails loadUserByUsername(String username) { String uid = username.toLowerCase(); logger.debug("Load user: " + uid); - DbUser dbUser = dao.getUser(uid); + DbUser dbUser = userRepository.findUserByUsername(uid); if (null == dbUser) { throw new UsernameNotFoundException("User does not exist."); } diff --git a/src/main/java/de/thm/arsnova/services/QuestionService.java b/src/main/java/de/thm/arsnova/services/ContentService.java similarity index 56% rename from src/main/java/de/thm/arsnova/services/QuestionService.java rename to src/main/java/de/thm/arsnova/services/ContentService.java index aa1e999c4db651d3d0345bb4aaad80e61c5e208d..2191d8cf7d1216bd621fbe8bb1ad194f8a0bffb9 100644 --- a/src/main/java/de/thm/arsnova/services/QuestionService.java +++ b/src/main/java/de/thm/arsnova/services/ContentService.java @@ -18,11 +18,10 @@ package de.thm.arsnova.services; import de.thm.arsnova.ImageUtils; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.Answer; -import de.thm.arsnova.entities.InterposedQuestion; -import de.thm.arsnova.entities.InterposedReadingCount; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Comment; +import de.thm.arsnova.entities.CommentReadingCount; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; import de.thm.arsnova.events.*; @@ -30,6 +29,10 @@ import de.thm.arsnova.exceptions.BadRequestException; import de.thm.arsnova.exceptions.ForbiddenException; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.exceptions.UnauthorizedException; +import de.thm.arsnova.persistance.AnswerRepository; +import de.thm.arsnova.persistance.CommentRepository; +import de.thm.arsnova.persistance.ContentRepository; +import de.thm.arsnova.persistance.SessionRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -48,16 +51,24 @@ import java.util.Timer; import java.util.TimerTask; /** - * Performs all question, interposed question, and answer related operations. + * Performs all question, comment, and answer related operations. */ @Service -public class QuestionService implements IQuestionService, ApplicationEventPublisherAware { +public class ContentService implements IContentService, ApplicationEventPublisherAware { + @Autowired + private IUserService userService; @Autowired - private IDatabaseDao databaseDao; + private SessionRepository sessionRepository; @Autowired - private IUserService userService; + private CommentRepository commentRepository; + + @Autowired + private ContentRepository contentRepository; + + @Autowired + private AnswerRepository answerRepository; @Autowired private ImageUtils imageUtils; @@ -67,57 +78,54 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis private ApplicationEventPublisher publisher; - private static final Logger logger = LoggerFactory.getLogger(QuestionService.class); + private static final Logger logger = LoggerFactory.getLogger(ContentService.class); private HashMap<String, Timer> timerList = new HashMap<>(); - public void setDatabaseDao(final IDatabaseDao databaseDao) { - this.databaseDao = databaseDao; - } - @Override @PreAuthorize("isAuthenticated()") - public List<Question> getSkillQuestions(final String sessionkey) { + public List<Content> getSkillQuestions(final String sessionkey) { final Session session = getSession(sessionkey); final User user = userService.getCurrentUser(); if (session.isCreator(user)) { - return databaseDao.getSkillQuestionsForTeachers(session); + return contentRepository.getSkillQuestionsForTeachers(session); } else { - return databaseDao.getSkillQuestionsForUsers(session); + return contentRepository.getSkillQuestionsForUsers(session); } } @Override @PreAuthorize("isAuthenticated()") public int getSkillQuestionCount(final String sessionkey) { - final Session session = databaseDao.getSessionFromKeyword(sessionkey); - return databaseDao.getSkillQuestionCount(session); + final Session session = sessionRepository.getSessionFromKeyword(sessionkey); + return contentRepository.getSkillQuestionCount(session); } + /* FIXME: #content.getSessionKeyword() cannot be checked since keyword is no longer set for content. */ @Override - @PreAuthorize("isAuthenticated() and hasPermission(#question.getSessionKeyword(), 'session', 'owner')") - public Question saveQuestion(final Question question) { - final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword()); - question.setSessionId(session.get_id()); - question.setTimestamp(System.currentTimeMillis() / 1000L); + @PreAuthorize("isAuthenticated() and hasPermission(#content.getSessionKeyword(), 'session', 'owner')") + public Content saveQuestion(final Content content) { + final Session session = sessionRepository.getSessionFromKeyword(content.getSessionKeyword()); + content.setSessionId(session.getId()); + content.setTimestamp(System.currentTimeMillis() / 1000L); - if ("freetext".equals(question.getQuestionType())) { - question.setPiRound(0); - } else if (question.getPiRound() < 1 || question.getPiRound() > 2) { - question.setPiRound(1); + if ("freetext".equals(content.getQuestionType())) { + content.setPiRound(0); + } else if (content.getPiRound() < 1 || content.getPiRound() > 2) { + content.setPiRound(1); } // convert imageurl to base64 if neccessary - if ("grid".equals(question.getQuestionType()) && !question.getImage().startsWith("http")) { + if ("grid".equals(content.getQuestionType()) && !content.getImage().startsWith("http")) { // base64 adds offset to filesize, formula taken from: http://en.wikipedia.org/wiki/Base64#MIME - final int fileSize = (int) ((question.getImage().length() - 814) / 1.37); + final int fileSize = (int) ((content.getImage().length() - 814) / 1.37); if (fileSize > uploadFileSizeByte) { logger.error("Could not save file. File is too large with {} Byte.", fileSize); throw new BadRequestException(); } } - final Question result = databaseDao.saveQuestion(session, question); + final Content result = contentRepository.saveQuestion(session, content); final NewQuestionEvent event = new NewQuestionEvent(this, session, result); this.publisher.publishEvent(event); @@ -127,12 +135,12 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override @PreAuthorize("isAuthenticated()") - public boolean saveQuestion(final InterposedQuestion question) { - final Session session = databaseDao.getSessionFromKeyword(question.getSessionId()); - final InterposedQuestion result = databaseDao.saveQuestion(session, question, userService.getCurrentUser()); + public boolean saveQuestion(final Comment comment) { + final Session session = sessionRepository.getSessionFromKeyword(comment.getSessionId()); + final Comment result = commentRepository.saveQuestion(session, comment, userService.getCurrentUser()); if (null != result) { - final NewInterposedQuestionEvent event = new NewInterposedQuestionEvent(this, session, result); + final NewCommentEvent event = new NewCommentEvent(this, session, result); this.publisher.publishEvent(event); return true; } @@ -141,8 +149,8 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override @PreAuthorize("isAuthenticated()") - public Question getQuestion(final String id) { - final Question result = databaseDao.getQuestion(id); + public Content getQuestion(final String id) { + final Content result = contentRepository.getQuestion(id); if (result == null) { return null; } @@ -155,20 +163,20 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis } @Override - @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'question', 'owner')") + @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')") public void deleteQuestion(final String questionId) { - final Question question = databaseDao.getQuestion(questionId); - if (question == null) { + final Content content = contentRepository.getQuestion(questionId); + if (content == null) { throw new NotFoundException(); } - final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword()); + final Session session = sessionRepository.getSessionFromId(content.getSessionId()); if (session == null) { throw new UnauthorizedException(); } - databaseDao.deleteQuestionWithAnswers(question); + contentRepository.deleteQuestionWithAnswers(content); - final DeleteQuestionEvent event = new DeleteQuestionEvent(this, session, question); + final DeleteQuestionEvent event = new DeleteQuestionEvent(this, session, content); this.publisher.publishEvent(event); } @@ -176,17 +184,17 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @PreAuthorize("isAuthenticated() and hasPermission(#sessionKeyword, 'session', 'owner')") public void deleteAllQuestions(final String sessionKeyword) { final Session session = getSessionWithAuthCheck(sessionKeyword); - databaseDao.deleteAllQuestionsWithAnswers(session); + contentRepository.deleteAllQuestionsWithAnswers(session); final DeleteAllQuestionsEvent event = new DeleteAllQuestionsEvent(this, session); this.publisher.publishEvent(event); } @Override - @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'question', 'owner')") + @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')") public void startNewPiRound(final String questionId, User user) { - final Question question = databaseDao.getQuestion(questionId); - final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword()); + final Content content = contentRepository.getQuestion(questionId); + final Session session = sessionRepository.getSessionFromId(content.getSessionId()); if (null == user) { user = userService.getCurrentUser(); @@ -194,57 +202,57 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis cancelDelayedPiRoundChange(questionId); - question.setPiRoundEndTime(0); - question.setVotingDisabled(true); - question.updateRoundManagementState(); - update(question, user); + content.setPiRoundEndTime(0); + content.setVotingDisabled(true); + content.updateRoundManagementState(); + update(content, user); - this.publisher.publishEvent(new PiRoundEndEvent(this, session, question)); + this.publisher.publishEvent(new PiRoundEndEvent(this, session, content)); } @Override - @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'question', 'owner')") + @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')") public void startNewPiRoundDelayed(final String questionId, final int time) { - final IQuestionService questionService = this; + final IContentService contentService = this; final User user = userService.getCurrentUser(); - final Question question = databaseDao.getQuestion(questionId); - final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword()); + final Content content = contentRepository.getQuestion(questionId); + final Session session = sessionRepository.getSessionFromId(content.getSessionId()); final Date date = new Date(); final Timer timer = new Timer(); final Date endDate = new Date(date.getTime() + (time * 1000)); - question.updateRoundStartVariables(date, endDate); - update(question); + content.updateRoundStartVariables(date, endDate); + update(content); - this.publisher.publishEvent(new PiRoundDelayedStartEvent(this, session, question)); + this.publisher.publishEvent(new PiRoundDelayedStartEvent(this, session, content)); timerList.put(questionId, timer); timer.schedule(new TimerTask() { @Override public void run() { - questionService.startNewPiRound(questionId, user); + contentService.startNewPiRound(questionId, user); } }, endDate); } @Override - @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'question', 'owner')") + @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')") public void cancelPiRoundChange(final String questionId) { - final Question question = databaseDao.getQuestion(questionId); - final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword()); + final Content content = contentRepository.getQuestion(questionId); + final Session session = sessionRepository.getSessionFromId(content.getSessionId()); cancelDelayedPiRoundChange(questionId); - question.resetRoundManagementState(); + content.resetRoundManagementState(); - if (0 == question.getPiRound() || 1 == question.getPiRound()) { - question.setPiRoundFinished(false); + if (0 == content.getPiRound() || 1 == content.getPiRound()) { + content.setPiRoundFinished(false); } else { - question.setPiRound(1); - question.setPiRoundFinished(true); + content.setPiRound(1); + content.setPiRoundFinished(true); } - update(question); - this.publisher.publishEvent(new PiRoundCancelEvent(this, session, question)); + update(content); + this.publisher.publishEvent(new PiRoundCancelEvent(this, session, content)); } @Override @@ -259,60 +267,60 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis } @Override - @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'question', 'owner')") + @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')") public void resetPiRoundState(final String questionId) { - final Question question = databaseDao.getQuestion(questionId); - final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword()); + final Content content = contentRepository.getQuestion(questionId); + final Session session = sessionRepository.getSessionFromId(content.getSessionId()); cancelDelayedPiRoundChange(questionId); - if ("freetext".equals(question.getQuestionType())) { - question.setPiRound(0); + if ("freetext".equals(content.getQuestionType())) { + content.setPiRound(0); } else { - question.setPiRound(1); + content.setPiRound(1); } - question.resetRoundManagementState(); - databaseDao.deleteAnswers(question); - update(question); - this.publisher.publishEvent(new PiRoundResetEvent(this, session, question)); + content.resetRoundManagementState(); + answerRepository.deleteAnswers(content); + update(content); + this.publisher.publishEvent(new PiRoundResetEvent(this, session, content)); } @Override - @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'question', 'owner')") + @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')") public void setVotingAdmission(final String questionId, final boolean disableVoting) { - final Question question = databaseDao.getQuestion(questionId); - final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword()); - question.setVotingDisabled(disableVoting); + final Content content = contentRepository.getQuestion(questionId); + final Session session = sessionRepository.getSessionFromId(content.getSessionId()); + content.setVotingDisabled(disableVoting); - if (!disableVoting && !question.isActive()) { - question.setActive(true); - update(question); + if (!disableVoting && !content.isActive()) { + content.setActive(true); + update(content); } else { - databaseDao.updateQuestion(question); + contentRepository.updateQuestion(content); } NovaEvent event; if (disableVoting) { - event = new LockVoteEvent(this, session, question); + event = new LockVoteEvent(this, session, content); } else { - event = new UnlockVoteEvent(this, session, question); + event = new UnlockVoteEvent(this, session, content); } this.publisher.publishEvent(event); } @Override @PreAuthorize("isAuthenticated()") - public void setVotingAdmissions(final String sessionkey, final boolean disableVoting, List<Question> questions) { + public void setVotingAdmissions(final String sessionkey, final boolean disableVoting, List<Content> contents) { final User user = getCurrentUser(); final Session session = getSession(sessionkey); if (!session.isCreator(user)) { throw new UnauthorizedException(); } - databaseDao.setVotingAdmissions(session, disableVoting, questions); + contentRepository.setVotingAdmissions(session, disableVoting, contents); NovaEvent event; if (disableVoting) { - event = new LockVotesEvent(this, session, questions); + event = new LockVotesEvent(this, session, contents); } else { - event = new UnlockVotesEvent(this, session, questions); + event = new UnlockVotesEvent(this, session, contents); } this.publisher.publishEvent(event); } @@ -325,19 +333,19 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis if (!session.isCreator(user)) { throw new UnauthorizedException(); } - final List<Question> questions = databaseDao.setVotingAdmissionForAllQuestions(session, disableVoting); + final List<Content> contents = contentRepository.setVotingAdmissionForAllQuestions(session, disableVoting); NovaEvent event; if (disableVoting) { - event = new LockVotesEvent(this, session, questions); + event = new LockVotesEvent(this, session, contents); } else { - event = new UnlockVotesEvent(this, session, questions); + event = new UnlockVotesEvent(this, session, contents); } this.publisher.publishEvent(event); } private Session getSessionWithAuthCheck(final String sessionKeyword) { final User user = userService.getCurrentUser(); - final Session session = databaseDao.getSessionFromKeyword(sessionKeyword); + final Session session = sessionRepository.getSessionFromKeyword(sessionKeyword); if (user == null || session == null || !session.isCreator(user)) { throw new UnauthorizedException(); } @@ -345,41 +353,41 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis } @Override - @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'interposedquestion', 'owner')") - public void deleteInterposedQuestion(final String questionId) { - final InterposedQuestion question = databaseDao.getInterposedQuestion(questionId); - if (question == null) { + @PreAuthorize("isAuthenticated() and hasPermission(#commentId, 'comment', 'owner')") + public void deleteInterposedQuestion(final String commentId) { + final Comment comment = commentRepository.getInterposedQuestion(commentId); + if (comment == null) { throw new NotFoundException(); } - databaseDao.deleteInterposedQuestion(question); + commentRepository.deleteInterposedQuestion(comment); - final Session session = databaseDao.getSessionFromKeyword(question.getSessionId()); - final DeleteInterposedQuestionEvent event = new DeleteInterposedQuestionEvent(this, session, question); + final Session session = sessionRepository.getSessionFromKeyword(comment.getSessionId()); + final DeleteCommentEvent event = new DeleteCommentEvent(this, session, comment); this.publisher.publishEvent(event); } @Override @PreAuthorize("isAuthenticated()") public void deleteAllInterposedQuestions(final String sessionKeyword) { - final Session session = databaseDao.getSessionFromKeyword(sessionKeyword); + final Session session = sessionRepository.getSessionFromKeyword(sessionKeyword); if (session == null) { throw new UnauthorizedException(); } final User user = getCurrentUser(); if (session.isCreator(user)) { - databaseDao.deleteAllInterposedQuestions(session); + commentRepository.deleteAllInterposedQuestions(session); } else { - databaseDao.deleteAllInterposedQuestions(session, user); + commentRepository.deleteAllInterposedQuestions(session, user); } } @Override - @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'question', 'owner')") + @PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')") public void deleteAnswers(final String questionId) { - final Question question = databaseDao.getQuestion(questionId); - question.resetQuestionState(); - databaseDao.updateQuestion(question); - databaseDao.deleteAnswers(question); + final Content content = contentRepository.getQuestion(questionId); + content.resetQuestionState(); + contentRepository.updateQuestion(content); + answerRepository.deleteAnswers(content); } @Override @@ -387,7 +395,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis public List<String> getUnAnsweredQuestionIds(final String sessionKey) { final User user = getCurrentUser(); final Session session = getSession(sessionKey); - return databaseDao.getUnAnsweredQuestionIds(session, user); + return contentRepository.getUnAnsweredQuestionIds(session, user); } private User getCurrentUser() { @@ -401,121 +409,121 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override @PreAuthorize("isAuthenticated()") public Answer getMyAnswer(final String questionId) { - final Question question = getQuestion(questionId); - if (question == null) { + final Content content = getQuestion(questionId); + if (content == null) { throw new NotFoundException(); } - return databaseDao.getMyAnswer(userService.getCurrentUser(), questionId, question.getPiRound()); + return answerRepository.getMyAnswer(userService.getCurrentUser(), questionId, content.getPiRound()); } @Override public void readFreetextAnswer(final String answerId, final User user) { - final Answer answer = databaseDao.getObjectFromId(answerId, Answer.class); + final Answer answer = answerRepository.get(answerId); if (answer == null) { throw new NotFoundException(); } if (answer.isRead()) { return; } - final Session session = databaseDao.getSessionFromId(answer.getSessionId()); + final Session session = sessionRepository.getSessionFromId(answer.getSessionId()); if (session.isCreator(user)) { answer.setRead(true); - databaseDao.updateAnswer(answer); + answerRepository.updateAnswer(answer); } } @Override @PreAuthorize("isAuthenticated()") public List<Answer> getAnswers(final String questionId, final int piRound, final int offset, final int limit) { - final Question question = databaseDao.getQuestion(questionId); - if (question == null) { + final Content content = contentRepository.getQuestion(questionId); + if (content == null) { throw new NotFoundException(); } - return "freetext".equals(question.getQuestionType()) + return "freetext".equals(content.getQuestionType()) ? getFreetextAnswers(questionId, offset, limit) - : databaseDao.getAnswers(question, piRound); + : answerRepository.getAnswers(content, piRound); } @Override @PreAuthorize("isAuthenticated()") public List<Answer> getAnswers(final String questionId, final int offset, final int limit) { - final Question question = getQuestion(questionId); - if (question == null) { + final Content content = getQuestion(questionId); + if (content == null) { throw new NotFoundException(); } - if ("freetext".equals(question.getQuestionType())) { + if ("freetext".equals(content.getQuestionType())) { return getFreetextAnswers(questionId, offset, limit); } else { - return databaseDao.getAnswers(question); + return answerRepository.getAnswers(content); } } @Override @PreAuthorize("isAuthenticated()") public List<Answer> getAllAnswers(final String questionId, final int offset, final int limit) { - final Question question = getQuestion(questionId); - if (question == null) { + final Content content = getQuestion(questionId); + if (content == null) { throw new NotFoundException(); } - if ("freetext".equals(question.getQuestionType())) { + if ("freetext".equals(content.getQuestionType())) { return getFreetextAnswers(questionId, offset, limit); } else { - return databaseDao.getAllAnswers(question); + return answerRepository.getAllAnswers(content); } } @Override @PreAuthorize("isAuthenticated()") public int getAnswerCount(final String questionId) { - final Question question = getQuestion(questionId); - if (question == null) { + final Content content = getQuestion(questionId); + if (content == null) { return 0; } - if ("freetext".equals(question.getQuestionType())) { - return databaseDao.getTotalAnswerCountByQuestion(question); + if ("freetext".equals(content.getQuestionType())) { + return answerRepository.getTotalAnswerCountByQuestion(content); } else { - return databaseDao.getAnswerCount(question, question.getPiRound()); + return answerRepository.getAnswerCount(content, content.getPiRound()); } } @Override @PreAuthorize("isAuthenticated()") public int getAnswerCount(final String questionId, final int piRound) { - final Question question = getQuestion(questionId); - if (question == null) { + final Content content = getQuestion(questionId); + if (content == null) { return 0; } - return databaseDao.getAnswerCount(question, piRound); + return answerRepository.getAnswerCount(content, piRound); } @Override @PreAuthorize("isAuthenticated()") public int getAbstentionAnswerCount(final String questionId) { - final Question question = getQuestion(questionId); - if (question == null) { + final Content content = getQuestion(questionId); + if (content == null) { return 0; } - return databaseDao.getAbstentionAnswerCount(questionId); + return answerRepository.getAbstentionAnswerCount(questionId); } @Override @PreAuthorize("isAuthenticated()") public int getTotalAnswerCountByQuestion(final String questionId) { - final Question question = getQuestion(questionId); - if (question == null) { + final Content content = getQuestion(questionId); + if (content == null) { return 0; } - return databaseDao.getTotalAnswerCountByQuestion(question); + return answerRepository.getTotalAnswerCountByQuestion(content); } @Override @PreAuthorize("isAuthenticated()") public List<Answer> getFreetextAnswers(final String questionId, final int offset, final int limit) { - final List<Answer> answers = databaseDao.getFreetextAnswers(questionId, offset, limit); + final List<Answer> answers = answerRepository.getFreetextAnswers(questionId, offset, limit); if (answers == null) { throw new NotFoundException(); } @@ -531,29 +539,29 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @PreAuthorize("isAuthenticated()") public List<Answer> getMyAnswers(final String sessionKey) { final Session session = getSession(sessionKey); - // Load questions first because we are only interested in answers of the latest piRound. - final List<Question> questions = databaseDao.getSkillQuestionsForUsers(session); - final Map<String, Question> questionIdToQuestion = new HashMap<>(); - for (final Question question : questions) { - questionIdToQuestion.put(question.get_id(), question); + // Load contents first because we are only interested in answers of the latest piRound. + final List<Content> contents = contentRepository.getSkillQuestionsForUsers(session); + final Map<String, Content> questionIdToQuestion = new HashMap<>(); + for (final Content content : contents) { + questionIdToQuestion.put(content.getId(), content); } /* filter answers by active piRound per question */ - final List<Answer> answers = databaseDao.getMyAnswers(userService.getCurrentUser(), session); + final List<Answer> answers = answerRepository.getMyAnswers(userService.getCurrentUser(), session); final List<Answer> filteredAnswers = new ArrayList<>(); for (final Answer answer : answers) { - final Question question = questionIdToQuestion.get(answer.getQuestionId()); - if (question == null) { - // Question is not present. Most likely it has been locked by the + final Content content = questionIdToQuestion.get(answer.getQuestionId()); + if (content == null) { + // Content is not present. Most likely it has been locked by the // Session's creator. Locked Questions do not appear in this list. continue; } - if (0 == answer.getPiRound() && !"freetext".equals(question.getQuestionType())) { + if (0 == answer.getPiRound() && !"freetext".equals(content.getQuestionType())) { answer.setPiRound(1); } - // discard all answers that aren't in the same piRound as the question - if (answer.getPiRound() == question.getPiRound()) { + // discard all answers that aren't in the same piRound as the content + if (answer.getPiRound() == content.getPiRound()) { filteredAnswers.add(answer); } } @@ -564,51 +572,51 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override @PreAuthorize("isAuthenticated()") public int getTotalAnswerCount(final String sessionKey) { - return databaseDao.getTotalAnswerCount(sessionKey); + return answerRepository.getTotalAnswerCount(sessionKey); } @Override @PreAuthorize("isAuthenticated()") public int getInterposedCount(final String sessionKey) { - return databaseDao.getInterposedCount(sessionKey); + return commentRepository.getInterposedCount(sessionKey); } @Override @PreAuthorize("isAuthenticated()") - public InterposedReadingCount getInterposedReadingCount(final String sessionKey, String username) { - final Session session = databaseDao.getSessionFromKeyword(sessionKey); + public CommentReadingCount getInterposedReadingCount(final String sessionKey, String username) { + final Session session = sessionRepository.getSessionFromKeyword(sessionKey); if (session == null) { throw new NotFoundException(); } if (username == null) { - return databaseDao.getInterposedReadingCount(session); + return commentRepository.getInterposedReadingCount(session); } else { User currentUser = userService.getCurrentUser(); if (!currentUser.getUsername().equals(username)) { throw new ForbiddenException(); } - return databaseDao.getInterposedReadingCount(session, currentUser); + return commentRepository.getInterposedReadingCount(session, currentUser); } } @Override @PreAuthorize("isAuthenticated()") - public List<InterposedQuestion> getInterposedQuestions(final String sessionKey, final int offset, final int limit) { + public List<Comment> getInterposedQuestions(final String sessionKey, final int offset, final int limit) { final Session session = this.getSession(sessionKey); final User user = getCurrentUser(); if (session.isCreator(user)) { - return databaseDao.getInterposedQuestions(session, offset, limit); + return commentRepository.getInterposedQuestions(session, offset, limit); } else { - return databaseDao.getInterposedQuestions(session, user, offset, limit); + return commentRepository.getInterposedQuestions(session, user, offset, limit); } } @Override @PreAuthorize("isAuthenticated()") - public InterposedQuestion readInterposedQuestion(final String questionId) { + public Comment readInterposedQuestion(final String commentId) { final User user = userService.getCurrentUser(); - return this.readInterposedQuestionInternal(questionId, user); + return this.readInterposedQuestionInternal(commentId, user); } /* @@ -616,53 +624,53 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis * TODO: Find a better way of doing this... */ @Override - public InterposedQuestion readInterposedQuestionInternal(final String questionId, User user) { - final InterposedQuestion question = databaseDao.getInterposedQuestion(questionId); - if (question == null) { + public Comment readInterposedQuestionInternal(final String commentId, User user) { + final Comment comment = commentRepository.getInterposedQuestion(commentId); + if (comment == null) { throw new NotFoundException(); } - final Session session = databaseDao.getSessionFromKeyword(question.getSessionId()); - if (!question.isCreator(user) && !session.isCreator(user)) { + final Session session = sessionRepository.getSessionFromId(comment.getSessionId()); + if (!comment.isCreator(user) && !session.isCreator(user)) { throw new UnauthorizedException(); } if (session.isCreator(user)) { - databaseDao.markInterposedQuestionAsRead(question); + commentRepository.markInterposedQuestionAsRead(comment); } - return question; + return comment; } @Override @PreAuthorize("isAuthenticated()") - public Question update(final Question question) { + public Content update(final Content content) { final User user = userService.getCurrentUser(); - return update(question, user); + return update(content, user); } @Override @PreAuthorize("isAuthenticated()") - public Question update(final Question question, User user) { - final Question oldQuestion = databaseDao.getQuestion(question.get_id()); - if (null == oldQuestion) { + public Content update(final Content content, User user) { + final Content oldContent = contentRepository.getQuestion(content.getId()); + if (null == oldContent) { throw new NotFoundException(); } - final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword()); + final Session session = sessionRepository.getSessionFromId(content.getSessionId()); if (user == null || session == null || !session.isCreator(user)) { throw new UnauthorizedException(); } - if ("freetext".equals(question.getQuestionType())) { - question.setPiRound(0); - } else if (question.getPiRound() < 1 || question.getPiRound() > 2) { - question.setPiRound(oldQuestion.getPiRound() > 0 ? oldQuestion.getPiRound() : 1); + if ("freetext".equals(content.getQuestionType())) { + content.setPiRound(0); + } else if (content.getPiRound() < 1 || content.getPiRound() > 2) { + content.setPiRound(oldContent.getPiRound() > 0 ? oldContent.getPiRound() : 1); } - final Question result = databaseDao.updateQuestion(question); + final Content result = contentRepository.updateQuestion(content); - if (!oldQuestion.isActive() && question.isActive()) { + if (!oldContent.isActive() && content.isActive()) { final UnlockQuestionEvent event = new UnlockQuestionEvent(this, session, result); this.publisher.publishEvent(event); - } else if (oldQuestion.isActive() && !question.isActive()) { + } else if (oldContent.isActive() && !content.isActive()) { final LockQuestionEvent event = new LockQuestionEvent(this, session, result); this.publisher.publishEvent(event); } @@ -673,26 +681,30 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @PreAuthorize("isAuthenticated()") public Answer saveAnswer(final String questionId, final de.thm.arsnova.entities.transport.Answer answer) { final User user = getCurrentUser(); - final Question question = getQuestion(questionId); - if (question == null) { + final Content content = getQuestion(questionId); + if (content == null) { throw new NotFoundException(); } + final Session session = sessionRepository.getSessionFromId(content.getSessionId()); - Answer theAnswer = answer.generateAnswerEntity(user, question); - if ("freetext".equals(question.getQuestionType())) { + Answer theAnswer = answer.generateAnswerEntity(user, content); + theAnswer.setUser(user.getUsername()); + theAnswer.setQuestionId(content.getId()); + theAnswer.setSessionId(session.getId()); + if ("freetext".equals(content.getQuestionType())) { imageUtils.generateThumbnailImage(theAnswer); - if (question.isFixedAnswer() && question.getText() != null) { + if (content.isFixedAnswer() && content.getText() != null) { theAnswer.setAnswerTextRaw(theAnswer.getAnswerText()); - if (question.isStrictMode()) { - question.checkTextStrictOptions(theAnswer); + if (content.isStrictMode()) { + content.checkTextStrictOptions(theAnswer); } - theAnswer.setQuestionValue(question.evaluateCorrectAnswerFixedText(theAnswer.getAnswerTextRaw())); - theAnswer.setSuccessfulFreeTextAnswer(question.isSuccessfulFreeTextAnswer(theAnswer.getAnswerTextRaw())); + theAnswer.setQuestionValue(content.evaluateCorrectAnswerFixedText(theAnswer.getAnswerTextRaw())); + theAnswer.setSuccessfulFreeTextAnswer(content.isSuccessfulFreeTextAnswer(theAnswer.getAnswerTextRaw())); } } - return databaseDao.saveAnswer(theAnswer, user, question, getSession(question.getSessionKeyword())); + return answerRepository.saveAnswer(theAnswer, user, content, session); } @Override @@ -704,14 +716,17 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis throw new UnauthorizedException(); } - final Question question = getQuestion(answer.getQuestionId()); - if ("freetext".equals(question.getQuestionType())) { + final Content content = getQuestion(answer.getQuestionId()); + if ("freetext".equals(content.getQuestionType())) { imageUtils.generateThumbnailImage(realAnswer); - question.checkTextStrictOptions(realAnswer); + content.checkTextStrictOptions(realAnswer); } - final Answer result = databaseDao.updateAnswer(realAnswer); - final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword()); - this.publisher.publishEvent(new NewAnswerEvent(this, session, result, user, question)); + final Session session = sessionRepository.getSessionFromId(content.getSessionId()); + answer.setUser(user.getUsername()); + answer.setQuestionId(content.getId()); + answer.setSessionId(session.getId()); + final Answer result = answerRepository.updateAnswer(realAnswer); + this.publisher.publishEvent(new NewAnswerEvent(this, session, result, user, content)); return result; } @@ -719,70 +734,70 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override @PreAuthorize("isAuthenticated()") public void deleteAnswer(final String questionId, final String answerId) { - final Question question = databaseDao.getQuestion(questionId); - if (question == null) { + final Content content = contentRepository.getQuestion(questionId); + if (content == null) { throw new NotFoundException(); } final User user = userService.getCurrentUser(); - final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword()); + final Session session = sessionRepository.getSessionFromId(content.getSessionId()); if (user == null || session == null || !session.isCreator(user)) { throw new UnauthorizedException(); } - databaseDao.deleteAnswer(answerId); + answerRepository.deleteAnswer(answerId); - this.publisher.publishEvent(new DeleteAnswerEvent(this, session, question)); + this.publisher.publishEvent(new DeleteAnswerEvent(this, session, content)); } @Override @PreAuthorize("isAuthenticated()") - public List<Question> getLectureQuestions(final String sessionkey) { + public List<Content> getLectureQuestions(final String sessionkey) { final Session session = getSession(sessionkey); final User user = userService.getCurrentUser(); if (session.isCreator(user)) { - return databaseDao.getLectureQuestionsForTeachers(session); + return contentRepository.getLectureQuestionsForTeachers(session); } else { - return databaseDao.getLectureQuestionsForUsers(session); + return contentRepository.getLectureQuestionsForUsers(session); } } @Override @PreAuthorize("isAuthenticated()") - public List<Question> getFlashcards(final String sessionkey) { + public List<Content> getFlashcards(final String sessionkey) { final Session session = getSession(sessionkey); final User user = userService.getCurrentUser(); if (session.isCreator(user)) { - return databaseDao.getFlashcardsForTeachers(session); + return contentRepository.getFlashcardsForTeachers(session); } else { - return databaseDao.getFlashcardsForUsers(session); + return contentRepository.getFlashcardsForUsers(session); } } @Override @PreAuthorize("isAuthenticated()") - public List<Question> getPreparationQuestions(final String sessionkey) { + public List<Content> getPreparationQuestions(final String sessionkey) { final Session session = getSession(sessionkey); final User user = userService.getCurrentUser(); if (session.isCreator(user)) { - return databaseDao.getPreparationQuestionsForTeachers(session); + return contentRepository.getPreparationQuestionsForTeachers(session); } else { - return databaseDao.getPreparationQuestionsForUsers(session); + return contentRepository.getPreparationQuestionsForUsers(session); } } @Override @PreAuthorize("isAuthenticated()") - public List<Question> replaceImageData(final List<Question> questions) { - for (Question q : questions) { + public List<Content> replaceImageData(final List<Content> contents) { + for (Content q : contents) { if (q.getImage() != null && q.getImage().startsWith("data:image/")) { q.setImage("true"); } } - return questions; + return contents; } private Session getSession(final String sessionkey) { - final Session session = databaseDao.getSessionFromKeyword(sessionkey); + final Session session = sessionRepository.getSessionFromKeyword(sessionkey); if (session == null) { throw new NotFoundException(); } @@ -792,19 +807,19 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override @PreAuthorize("isAuthenticated()") public int getLectureQuestionCount(final String sessionkey) { - return databaseDao.getLectureQuestionCount(getSession(sessionkey)); + return contentRepository.getLectureQuestionCount(getSession(sessionkey)); } @Override @PreAuthorize("isAuthenticated()") public int getFlashcardCount(final String sessionkey) { - return databaseDao.getFlashcardCount(getSession(sessionkey)); + return contentRepository.getFlashcardCount(getSession(sessionkey)); } @Override @PreAuthorize("isAuthenticated()") public int getPreparationQuestionCount(final String sessionkey) { - return databaseDao.getPreparationQuestionCount(getSession(sessionkey)); + return contentRepository.getPreparationQuestionCount(getSession(sessionkey)); } @Override @@ -819,21 +834,21 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis */ @Override public int countLectureQuestionAnswersInternal(final String sessionkey) { - return databaseDao.countLectureQuestionAnswers(getSession(sessionkey)); + return answerRepository.countLectureQuestionAnswers(getSession(sessionkey)); } @Override public Map<String, Object> getAnswerAndAbstentionCountInternal(final String questionId) { - final Question question = getQuestion(questionId); + final Content content = getQuestion(questionId); HashMap<String, Object> map = new HashMap<>(); - if (question == null) { + if (content == null) { return null; } map.put("_id", questionId); - map.put("answers", databaseDao.getAnswerCount(question, question.getPiRound())); - map.put("abstentions", databaseDao.getAbstentionAnswerCount(questionId)); + map.put("answers", answerRepository.getAnswerCount(content, content.getPiRound())); + map.put("abstentions", answerRepository.getAbstentionAnswerCount(questionId)); return map; } @@ -850,7 +865,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis */ @Override public int countPreparationQuestionAnswersInternal(final String sessionkey) { - return databaseDao.countPreparationQuestionAnswers(getSession(sessionkey)); + return answerRepository.countPreparationQuestionAnswers(getSession(sessionkey)); } /* @@ -859,28 +874,28 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis */ @Override public int countFlashcardsForUserInternal(final String sessionkey) { - return databaseDao.getFlashcardsForUsers(getSession(sessionkey)).size(); + return contentRepository.getFlashcardsForUsers(getSession(sessionkey)).size(); } @Override @PreAuthorize("isAuthenticated()") public void deleteLectureQuestions(final String sessionkey) { final Session session = getSessionWithAuthCheck(sessionkey); - databaseDao.deleteAllLectureQuestionsWithAnswers(session); + contentRepository.deleteAllLectureQuestionsWithAnswers(session); } @Override @PreAuthorize("isAuthenticated()") public void deleteFlashcards(final String sessionkey) { final Session session = getSessionWithAuthCheck(sessionkey); - databaseDao.deleteAllFlashcardsWithAnswers(session); + contentRepository.deleteAllFlashcardsWithAnswers(session); } @Override @PreAuthorize("isAuthenticated()") public void deletePreparationQuestions(final String sessionkey) { final Session session = getSessionWithAuthCheck(sessionkey); - databaseDao.deleteAllPreparationQuestionsWithAnswers(session); + contentRepository.deleteAllPreparationQuestionsWithAnswers(session); } @Override @@ -893,7 +908,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override public List<String> getUnAnsweredLectureQuestionIds(final String sessionkey, final User user) { final Session session = getSession(sessionkey); - return databaseDao.getUnAnsweredLectureQuestionIds(session, user); + return contentRepository.getUnAnsweredLectureQuestionIds(session, user); } @Override @@ -906,7 +921,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override public List<String> getUnAnsweredPreparationQuestionIds(final String sessionkey, final User user) { final Session session = getSession(sessionkey); - return databaseDao.getUnAnsweredPreparationQuestionIds(session, user); + return contentRepository.getUnAnsweredPreparationQuestionIds(session, user); } @Override @@ -917,30 +932,30 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis if (!session.isCreator(user)) { throw new UnauthorizedException(); } - final List<Question> questions = databaseDao.publishAllQuestions(session, publish); + final List<Content> contents = contentRepository.publishAllQuestions(session, publish); NovaEvent event; if (publish) { - event = new UnlockQuestionsEvent(this, session, questions); + event = new UnlockQuestionsEvent(this, session, contents); } else { - event = new LockQuestionsEvent(this, session, questions); + event = new LockQuestionsEvent(this, session, contents); } this.publisher.publishEvent(event); } @Override @PreAuthorize("isAuthenticated()") - public void publishQuestions(final String sessionkey, final boolean publish, List<Question> questions) { + public void publishQuestions(final String sessionkey, final boolean publish, List<Content> contents) { final User user = getCurrentUser(); final Session session = getSession(sessionkey); if (!session.isCreator(user)) { throw new UnauthorizedException(); } - databaseDao.publishQuestions(session, publish, questions); + contentRepository.publishQuestions(session, publish, contents); NovaEvent event; if (publish) { - event = new UnlockQuestionsEvent(this, session, questions); + event = new UnlockQuestionsEvent(this, session, contents); } else { - event = new LockQuestionsEvent(this, session, questions); + event = new LockQuestionsEvent(this, session, contents); } this.publisher.publishEvent(event); } @@ -953,7 +968,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis if (!session.isCreator(user)) { throw new UnauthorizedException(); } - databaseDao.deleteAllQuestionsAnswers(session); + answerRepository.deleteAllQuestionsAnswers(session); this.publisher.publishEvent(new DeleteAllQuestionsAnswersEvent(this, session)); } @@ -962,7 +977,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')") public void deleteAllPreparationAnswers(String sessionkey) { final Session session = getSession(sessionkey); - databaseDao.deleteAllPreparationAnswers(session); + answerRepository.deleteAllPreparationAnswers(session); this.publisher.publishEvent(new DeleteAllPreparationAnswersEvent(this, session)); } @@ -971,7 +986,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')") public void deleteAllLectureAnswers(String sessionkey) { final Session session = getSession(sessionkey); - databaseDao.deleteAllLectureAnswers(session); + answerRepository.deleteAllLectureAnswers(session); this.publisher.publishEvent(new DeleteAllLectureAnswersEvent(this, session)); } @@ -987,7 +1002,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis Answer answer = null; for (Answer a : answers) { - if (answerId.equals(a.get_id())) { + if (answerId.equals(a.getId())) { answer = a; break; } @@ -1002,8 +1017,8 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override public String getQuestionImage(String questionId) { - Question question = databaseDao.getQuestion(questionId); - String imageData = question.getImage(); + Content content = contentRepository.getQuestion(questionId); + String imageData = content.getImage(); if (imageData == null) { imageData = ""; @@ -1014,8 +1029,8 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override public String getQuestionFcImage(String questionId) { - Question question = databaseDao.getQuestion(questionId); - String imageData = question.getFcImage(); + Content content = contentRepository.getQuestion(questionId); + String imageData = content.getFcImage(); if (imageData == null) { imageData = ""; diff --git a/src/main/java/de/thm/arsnova/services/FeedbackService.java b/src/main/java/de/thm/arsnova/services/FeedbackService.java index 1c49f3d5f77e956eead8831775f872ff69d3452b..0a4a69599944a7f9d26df23104dcbda994fbf955 100644 --- a/src/main/java/de/thm/arsnova/services/FeedbackService.java +++ b/src/main/java/de/thm/arsnova/services/FeedbackService.java @@ -18,7 +18,6 @@ package de.thm.arsnova.services; import de.thm.arsnova.FeedbackStorage; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.Feedback; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; @@ -26,6 +25,7 @@ import de.thm.arsnova.events.DeleteFeedbackForSessionsEvent; import de.thm.arsnova.events.NewFeedbackEvent; import de.thm.arsnova.exceptions.NoContentException; import de.thm.arsnova.exceptions.NotFoundException; +import de.thm.arsnova.persistance.SessionRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; @@ -56,16 +56,12 @@ public class FeedbackService implements IFeedbackService, ApplicationEventPublis private int cleanupFeedbackDelay; @Autowired - private IDatabaseDao databaseDao; + private SessionRepository sessionRepository; private FeedbackStorage feedbackStorage; private ApplicationEventPublisher publisher; - public void setDatabaseDao(final IDatabaseDao newDatabaseDao) { - databaseDao = newDatabaseDao; - } - @PostConstruct public void init() { feedbackStorage = new FeedbackStorage(); @@ -110,7 +106,7 @@ public class FeedbackService implements IFeedbackService, ApplicationEventPublis @Override public void cleanFeedbackVotesInSession(final String keyword, final int cleanupFeedbackDelayInMins) { - final Session session = databaseDao.getSessionFromKeyword(keyword); + final Session session = sessionRepository.getSessionFromKeyword(keyword); List<User> affectedUsers = feedbackStorage.cleanFeedbackVotesInSession(session, cleanupFeedbackDelayInMins); Set<Session> sessionSet = new HashSet<>(); sessionSet.add(session); @@ -125,7 +121,7 @@ public class FeedbackService implements IFeedbackService, ApplicationEventPublis @Override public Feedback getFeedback(final String keyword) { - final Session session = databaseDao.getSessionFromKeyword(keyword); + final Session session = sessionRepository.getSessionFromKeyword(keyword); if (session == null) { throw new NotFoundException(); } @@ -142,7 +138,7 @@ public class FeedbackService implements IFeedbackService, ApplicationEventPublis @Override public double getAverageFeedback(final String sessionkey) { - final Session session = databaseDao.getSessionFromKeyword(sessionkey); + final Session session = sessionRepository.getSessionFromKeyword(sessionkey); if (session == null) { throw new NotFoundException(); } @@ -166,7 +162,7 @@ public class FeedbackService implements IFeedbackService, ApplicationEventPublis @Override public boolean saveFeedback(final String keyword, final int value, final User user) { - final Session session = databaseDao.getSessionFromKeyword(keyword); + final Session session = sessionRepository.getSessionFromKeyword(keyword); if (session == null) { throw new NotFoundException(); } @@ -178,7 +174,7 @@ public class FeedbackService implements IFeedbackService, ApplicationEventPublis @Override public Integer getMyFeedback(final String keyword, final User user) { - final Session session = databaseDao.getSessionFromKeyword(keyword); + final Session session = sessionRepository.getSessionFromKeyword(keyword); if (session == null) { throw new NotFoundException(); } diff --git a/src/main/java/de/thm/arsnova/services/IQuestionService.java b/src/main/java/de/thm/arsnova/services/IContentService.java similarity index 78% rename from src/main/java/de/thm/arsnova/services/IQuestionService.java rename to src/main/java/de/thm/arsnova/services/IContentService.java index f03a88e98776f14d0e3f60db8c175e33ab179801..9b4e4f97bf40433a750034b28a3ee53d03ee19ef 100644 --- a/src/main/java/de/thm/arsnova/services/IQuestionService.java +++ b/src/main/java/de/thm/arsnova/services/IContentService.java @@ -18,9 +18,9 @@ package de.thm.arsnova.services; import de.thm.arsnova.entities.Answer; -import de.thm.arsnova.entities.InterposedQuestion; -import de.thm.arsnova.entities.InterposedReadingCount; -import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Comment; +import de.thm.arsnova.entities.CommentReadingCount; +import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.User; import java.util.List; @@ -29,14 +29,14 @@ import java.util.Map; /** * The functionality the question service should provide. */ -public interface IQuestionService { - Question saveQuestion(Question question); +public interface IContentService { + Content saveQuestion(Content content); - boolean saveQuestion(InterposedQuestion question); + boolean saveQuestion(Comment comment); - Question getQuestion(String id); + Content getQuestion(String id); - List<Question> getSkillQuestions(String sessionkey); + List<Content> getSkillQuestions(String sessionkey); int getSkillQuestionCount(String sessionkey); @@ -80,17 +80,17 @@ public interface IQuestionService { int getInterposedCount(String sessionKey); - InterposedReadingCount getInterposedReadingCount(String sessionKey, String username); + CommentReadingCount getInterposedReadingCount(String sessionKey, String username); - List<InterposedQuestion> getInterposedQuestions(String sessionKey, int offset, int limit); + List<Comment> getInterposedQuestions(String sessionKey, int offset, int limit); - InterposedQuestion readInterposedQuestion(String questionId); + Comment readInterposedQuestion(String commentId); - InterposedQuestion readInterposedQuestionInternal(String questionId, User user); + Comment readInterposedQuestionInternal(String commentId, User user); - Question update(Question question); + Content update(Content content); - Question update(Question question, User user); + Content update(Content content, User user); void deleteAnswers(String questionId); @@ -100,13 +100,13 @@ public interface IQuestionService { void deleteAnswer(String questionId, String answerId); - void deleteInterposedQuestion(String questionId); + void deleteInterposedQuestion(String commentId); - List<Question> getLectureQuestions(String sessionkey); + List<Content> getLectureQuestions(String sessionkey); - List<Question> getFlashcards(String sessionkey); + List<Content> getFlashcards(String sessionkey); - List<Question> getPreparationQuestions(String sessionkey); + List<Content> getPreparationQuestions(String sessionkey); int getLectureQuestionCount(String sessionkey); @@ -144,7 +144,7 @@ public interface IQuestionService { void publishAll(String sessionkey, boolean publish); - void publishQuestions(String sessionkey, boolean publish, List<Question> questions); + void publishQuestions(String sessionkey, boolean publish, List<Content> contents); void deleteAllQuestionsAnswers(String sessionkey); @@ -158,7 +158,7 @@ public interface IQuestionService { void setVotingAdmission(String questionId, boolean disableVoting); - void setVotingAdmissions(String sessionkey, boolean disableVoting, List<Question> questions); + void setVotingAdmissions(String sessionkey, boolean disableVoting, List<Content> contents); void setVotingAdmissionForAllQuestions(String sessionkey, boolean disableVoting); @@ -166,6 +166,6 @@ public interface IQuestionService { String getQuestionFcImage(String questionId); - List<Question> replaceImageData(List<Question> questions); + List<Content> replaceImageData(List<Content> contents); } diff --git a/src/main/java/de/thm/arsnova/services/MotdService.java b/src/main/java/de/thm/arsnova/services/MotdService.java index ecda866383aadd4023b44847a43ed788ef50ff18..674525ec2afe51c9fd917f0d1a992710dc2eb9cf 100644 --- a/src/main/java/de/thm/arsnova/services/MotdService.java +++ b/src/main/java/de/thm/arsnova/services/MotdService.java @@ -17,12 +17,13 @@ */ package de.thm.arsnova.services; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.Motd; import de.thm.arsnova.entities.MotdList; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; import de.thm.arsnova.exceptions.BadRequestException; +import de.thm.arsnova.persistance.MotdListRepository; +import de.thm.arsnova.persistance.MotdRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @@ -37,48 +38,46 @@ import java.util.StringTokenizer; */ @Service public class MotdService implements IMotdService { - - @Autowired - private IDatabaseDao databaseDao; - @Autowired private IUserService userService; @Autowired private ISessionService sessionService; - public void setDatabaseDao(final IDatabaseDao databaseDao) { - this.databaseDao = databaseDao; - } + @Autowired + private MotdRepository motdRepository; + + @Autowired + private MotdListRepository motdListRepository; @Override @PreAuthorize("isAuthenticated()") public Motd getMotd(final String key) { - return databaseDao.getMotdByKey(key); + return motdRepository.getMotdByKey(key); } @Override @PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')") public List<Motd> getAdminMotds() { - return databaseDao.getAdminMotds(); + return motdRepository.getAdminMotds(); } @Override @PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')") public List<Motd> getAllSessionMotds(final String sessionkey) { - return databaseDao.getMotdsForSession(sessionkey); + return motdRepository.getMotdsForSession(sessionkey); } @Override public List<Motd> getCurrentMotds(final Date clientdate, final String audience, final String sessionkey) { final List<Motd> motds; switch (audience) { - case "all": motds = databaseDao.getMotdsForAll(); break; - case "loggedIn": motds = databaseDao.getMotdsForLoggedIn(); break; - case "students": motds = databaseDao.getMotdsForStudents(); break; - case "tutors": motds = databaseDao.getMotdsForTutors(); break; - case "session": motds = databaseDao.getMotdsForSession(sessionkey); break; - default: motds = databaseDao.getMotdsForAll(); break; + case "all": motds = motdRepository.getMotdsForAll(); break; + case "loggedIn": motds = motdRepository.getMotdsForLoggedIn(); break; + case "students": motds = motdRepository.getMotdsForStudents(); break; + case "tutors": motds = motdRepository.getMotdsForTutors(); break; + case "session": motds = motdRepository.getMotdsForSession(sessionkey); break; + default: motds = motdRepository.getMotdsForAll(); break; } return filterMotdsByDate(motds, clientdate); } @@ -124,7 +123,7 @@ public class MotdService implements IMotdService { @PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')") public Motd saveSessionMotd(final String sessionkey, final Motd motd) { Session session = sessionService.getSession(sessionkey); - motd.setSessionId(session.get_id()); + motd.setSessionId(session.getId()); return createOrUpdateMotd(motd); @@ -144,26 +143,26 @@ public class MotdService implements IMotdService { private Motd createOrUpdateMotd(final Motd motd) { if (motd.getMotdkey() != null) { - Motd oldMotd = databaseDao.getMotdByKey(motd.getMotdkey()); - if (!(motd.get_id().equals(oldMotd.get_id()) && motd.getSessionkey().equals(oldMotd.getSessionkey()) + Motd oldMotd = motdRepository.getMotdByKey(motd.getMotdkey()); + if (!(motd.getId().equals(oldMotd.getId()) && motd.getSessionkey().equals(oldMotd.getSessionkey()) && motd.getAudience().equals(oldMotd.getAudience()))) { throw new BadRequestException(); } } - return databaseDao.createOrUpdateMotd(motd); + return motdRepository.createOrUpdateMotd(motd); } @Override @PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')") public void deleteMotd(Motd motd) { - databaseDao.deleteMotd(motd); + motdRepository.deleteMotd(motd); } @Override @PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')") public void deleteSessionMotd(final String sessionkey, Motd motd) { - databaseDao.deleteMotd(motd); + motdRepository.deleteMotd(motd); } @Override @@ -171,7 +170,7 @@ public class MotdService implements IMotdService { public MotdList getMotdListForUser(final String username) { final User user = userService.getCurrentUser(); if (username.equals(user.getUsername()) && !"guest".equals(user.getType())) { - return databaseDao.getMotdListForUser(username); + return motdListRepository.getMotdListForUser(username); } return null; } @@ -181,7 +180,7 @@ public class MotdService implements IMotdService { public MotdList saveUserMotdList(MotdList motdList) { final User user = userService.getCurrentUser(); if (user.getUsername().equals(motdList.getUsername())) { - return databaseDao.createOrUpdateMotdList(motdList); + return motdListRepository.createOrUpdateMotdList(motdList); } return null; } @@ -191,7 +190,7 @@ public class MotdService implements IMotdService { public MotdList updateUserMotdList(MotdList motdList) { final User user = userService.getCurrentUser(); if (user.getUsername().equals(motdList.getUsername())) { - return databaseDao.createOrUpdateMotdList(motdList); + return motdListRepository.createOrUpdateMotdList(motdList); } return null; } diff --git a/src/main/java/de/thm/arsnova/services/SessionService.java b/src/main/java/de/thm/arsnova/services/SessionService.java index 77a22e5b3999fb7d0d945a06e1b53c07fc0097a3..0257be71d75230b9d3dcfc2a33ea491d571f3f06 100644 --- a/src/main/java/de/thm/arsnova/services/SessionService.java +++ b/src/main/java/de/thm/arsnova/services/SessionService.java @@ -20,7 +20,6 @@ package de.thm.arsnova.services; import de.thm.arsnova.ImageUtils; import de.thm.arsnova.connector.client.ConnectorClient; import de.thm.arsnova.connector.model.Course; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.domain.ILearningProgressFactory; import de.thm.arsnova.domain.LearningProgress; import de.thm.arsnova.entities.LearningProgressOptions; @@ -41,6 +40,8 @@ import de.thm.arsnova.exceptions.ForbiddenException; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.exceptions.PayloadTooLargeException; import de.thm.arsnova.exceptions.UnauthorizedException; +import de.thm.arsnova.persistance.SessionRepository; +import de.thm.arsnova.persistance.VisitedSessionRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -62,6 +63,9 @@ import java.util.UUID; @Service public class SessionService implements ISessionService, ApplicationEventPublisherAware { + @Autowired + private SessionRepository sessionRepository; + public static class SessionNameComparator implements Comparator<Session>, Serializable { private static final long serialVersionUID = 1L; @@ -101,7 +105,7 @@ public class SessionService implements ISessionService, ApplicationEventPublishe private static final long SESSION_INACTIVITY_CHECK_INTERVAL_MS = 30 * 60 * 1000L; @Autowired - private IDatabaseDao databaseDao; + private VisitedSessionRepository visitedSessionRepository; @Autowired private IUserService userService; @@ -134,7 +138,7 @@ public class SessionService implements ISessionService, ApplicationEventPublishe logger.info("Delete inactive sessions."); long unixTime = System.currentTimeMillis(); long lastActivityBefore = unixTime - guestSessionInactivityThresholdDays * 24 * 60 * 60 * 1000L; - databaseDao.deleteInactiveGuestSessions(lastActivityBefore); + sessionRepository.deleteInactiveGuestSessions(lastActivityBefore); } } @@ -144,19 +148,15 @@ public class SessionService implements ISessionService, ApplicationEventPublishe logger.info("Delete lists of visited session for inactive users."); long unixTime = System.currentTimeMillis(); long lastActivityBefore = unixTime - guestSessionInactivityThresholdDays * 24 * 60 * 60 * 1000L; - databaseDao.deleteInactiveGuestVisitedSessionLists(lastActivityBefore); + visitedSessionRepository.deleteInactiveGuestVisitedSessionLists(lastActivityBefore); } } - public void setDatabaseDao(final IDatabaseDao newDatabaseDao) { - databaseDao = newDatabaseDao; - } - @Override public Session joinSession(final String keyword, final UUID socketId) { /* Socket.IO solution */ - Session session = null != keyword ? databaseDao.getSessionFromKeyword(keyword) : null; + Session session = null != keyword ? sessionRepository.getSessionFromKeyword(keyword) : null; if (null == session) { userService.removeUserFromSessionBySocketId(socketId); @@ -167,9 +167,9 @@ public class SessionService implements ISessionService, ApplicationEventPublishe userService.addUserToSessionBySocketId(socketId, keyword); if (session.getCreator().equals(user.getUsername())) { - databaseDao.updateSessionOwnerActivity(session); + sessionRepository.updateSessionOwnerActivity(session); } - databaseDao.registerAsOnlineUser(user, session); + sessionRepository.registerAsOnlineUser(user, session); if (connectorClient != null && session.isCourseSession()) { final String courseid = session.getCourseId(); @@ -190,7 +190,7 @@ public class SessionService implements ISessionService, ApplicationEventPublishe @PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')") public Session getSessionForAdmin(final String keyword) { - return databaseDao.getSessionFromKeyword(keyword); + return sessionRepository.getSessionFromKeyword(keyword); } /* @@ -199,7 +199,7 @@ public class SessionService implements ISessionService, ApplicationEventPublishe */ @Override public Session getSessionInternal(final String keyword, final User user) { - final Session session = databaseDao.getSessionFromKeyword(keyword); + final Session session = sessionRepository.getSessionFromKeyword(keyword); if (session == null) { throw new NotFoundException(); } @@ -222,50 +222,50 @@ public class SessionService implements ISessionService, ApplicationEventPublishe @Override @PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')") public List<Session> getUserSessions(String username) { - return databaseDao.getSessionsForUsername(username, 0, 0); + return sessionRepository.getSessionsForUsername(username, 0, 0); } @Override @PreAuthorize("isAuthenticated()") public List<Session> getMySessions(final int offset, final int limit) { - return databaseDao.getMySessions(userService.getCurrentUser(), offset, limit); + return sessionRepository.getMySessions(userService.getCurrentUser(), offset, limit); } @Override @PreAuthorize("isAuthenticated()") public List<SessionInfo> getPublicPoolSessionsInfo() { - return databaseDao.getPublicPoolSessionsInfo(); + return sessionRepository.getPublicPoolSessionsInfo(); } @Override @PreAuthorize("isAuthenticated()") public List<SessionInfo> getMyPublicPoolSessionsInfo() { - return databaseDao.getMyPublicPoolSessionsInfo(userService.getCurrentUser()); + return sessionRepository.getMyPublicPoolSessionsInfo(userService.getCurrentUser()); } @Override @PreAuthorize("isAuthenticated()") public List<SessionInfo> getMySessionsInfo(final int offset, final int limit) { final User user = userService.getCurrentUser(); - return databaseDao.getMySessionsInfo(user, offset, limit); + return sessionRepository.getMySessionsInfo(user, offset, limit); } @Override @PreAuthorize("isAuthenticated()") public List<Session> getMyVisitedSessions(final int offset, final int limit) { - return databaseDao.getMyVisitedSessions(userService.getCurrentUser(), offset, limit); + return sessionRepository.getVisitedSessionsForUsername(userService.getCurrentUser().getUsername(), offset, limit); } @Override @PreAuthorize("isAuthenticated() and hasPermission(1, 'motd', 'admin')") public List<Session> getUserVisitedSessions(String username) { - return databaseDao.getVisitedSessionsForUsername(username, 0, 0); + return sessionRepository.getVisitedSessionsForUsername(username, 0, 0); } @Override @PreAuthorize("isAuthenticated()") public List<SessionInfo> getMyVisitedSessionsInfo(final int offset, final int limit) { - return databaseDao.getMyVisitedSessionsInfo(userService.getCurrentUser(), offset, limit); + return sessionRepository.getMyVisitedSessionsInfo(userService.getCurrentUser(), offset, limit); } @Override @@ -294,14 +294,14 @@ public class SessionService implements ISessionService, ApplicationEventPublishe sf.setPi(true); session.setFeatures(sf); - final Session result = databaseDao.saveSession(userService.getCurrentUser(), session); + final Session result = sessionRepository.saveSession(userService.getCurrentUser(), session); this.publisher.publishEvent(new NewSessionEvent(this, result)); return result; } @Override public boolean sessionKeyAvailable(final String keyword) { - return databaseDao.sessionKeyAvailable(keyword); + return sessionRepository.sessionKeyAvailable(keyword); } @Override @@ -319,7 +319,7 @@ public class SessionService implements ISessionService, ApplicationEventPublishe @Override public int countSessions(final List<Course> courses) { - final List<Session> sessions = databaseDao.getCourseSessions(courses); + final List<Session> sessions = sessionRepository.getCourseSessions(courses); if (sessions == null) { return 0; } @@ -333,20 +333,20 @@ public class SessionService implements ISessionService, ApplicationEventPublishe @Override public Session setActive(final String sessionkey, final Boolean lock) { - final Session session = databaseDao.getSessionFromKeyword(sessionkey); + final Session session = sessionRepository.getSessionFromKeyword(sessionkey); final User user = userService.getCurrentUser(); if (!session.isCreator(user)) { throw new ForbiddenException("User is not session creator."); } session.setActive(lock); this.publisher.publishEvent(new StatusSessionEvent(this, session)); - return databaseDao.updateSession(session); + return sessionRepository.updateSession(session); } @Override @PreAuthorize("isAuthenticated() and hasPermission(#session, 'owner')") public Session updateSession(final String sessionkey, final Session session) { - final Session existingSession = databaseDao.getSessionFromKeyword(sessionkey); + final Session existingSession = sessionRepository.getSessionFromKeyword(sessionkey); existingSession.setActive(session.isActive()); existingSession.setShortName(session.getShortName()); @@ -366,17 +366,17 @@ public class SessionService implements ISessionService, ApplicationEventPublishe handleLogo(session); existingSession.setPpLogo(session.getPpLogo()); - return databaseDao.updateSession(existingSession); + return sessionRepository.updateSession(existingSession); } @Override @PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')") public Session changeSessionCreator(String sessionkey, String newCreator) { - final Session existingSession = databaseDao.getSessionFromKeyword(sessionkey); + final Session existingSession = sessionRepository.getSessionFromKeyword(sessionkey); if (existingSession == null) { throw new NullPointerException("Could not load session " + sessionkey + "."); } - return databaseDao.changeSessionCreator(existingSession, newCreator); + return sessionRepository.changeSessionCreator(existingSession, newCreator); } /* @@ -386,7 +386,7 @@ public class SessionService implements ISessionService, ApplicationEventPublishe @Override public Session updateSessionInternal(final Session session, final User user) { if (session.isCreator(user)) { - return databaseDao.updateSession(session); + return sessionRepository.updateSession(session); } return null; } @@ -394,9 +394,9 @@ public class SessionService implements ISessionService, ApplicationEventPublishe @Override @PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')") public void deleteSession(final String sessionkey) { - final Session session = databaseDao.getSessionFromKeyword(sessionkey); + final Session session = sessionRepository.getSessionFromKeyword(sessionkey); - databaseDao.deleteSession(session); + sessionRepository.deleteSession(session); this.publisher.publishEvent(new DeleteSessionEvent(this, session)); } @@ -404,7 +404,7 @@ public class SessionService implements ISessionService, ApplicationEventPublishe @Override @PreAuthorize("isAuthenticated()") public LearningProgressValues getLearningProgress(final String sessionkey, final String progressType, final String questionVariant) { - final Session session = databaseDao.getSessionFromKeyword(sessionkey); + final Session session = sessionRepository.getSessionFromKeyword(sessionkey); LearningProgress learningProgress = learningProgressFactory.create(progressType, questionVariant); return learningProgress.getCourseProgress(session); } @@ -412,7 +412,7 @@ public class SessionService implements ISessionService, ApplicationEventPublishe @Override @PreAuthorize("isAuthenticated()") public LearningProgressValues getMyLearningProgress(final String sessionkey, final String progressType, final String questionVariant) { - final Session session = databaseDao.getSessionFromKeyword(sessionkey); + final Session session = sessionRepository.getSessionFromKeyword(sessionkey); final User user = userService.getCurrentUser(); LearningProgress learningProgress = learningProgressFactory.create(progressType, questionVariant); return learningProgress.getMyProgress(session, user); @@ -422,7 +422,7 @@ public class SessionService implements ISessionService, ApplicationEventPublishe @PreAuthorize("isAuthenticated()") public SessionInfo importSession(ImportExportSession importSession) { final User user = userService.getCurrentUser(); - final SessionInfo info = databaseDao.importSession(user, importSession); + final SessionInfo info = sessionRepository.importSession(user, importSession); if (info == null) { throw new NullPointerException("Could not import session."); } @@ -432,17 +432,17 @@ public class SessionService implements ISessionService, ApplicationEventPublishe @Override @PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')") public ImportExportSession exportSession(String sessionkey, Boolean withAnswerStatistics, Boolean withFeedbackQuestions) { - return databaseDao.exportSession(sessionkey, withAnswerStatistics, withFeedbackQuestions); + return sessionRepository.exportSession(sessionkey, withAnswerStatistics, withFeedbackQuestions); } @Override @PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')") public SessionInfo copySessionToPublicPool(String sessionkey, de.thm.arsnova.entities.transport.ImportExportSession.PublicPool pp) { - ImportExportSession temp = databaseDao.exportSession(sessionkey, false, false); + ImportExportSession temp = sessionRepository.exportSession(sessionkey, false, false); temp.getSession().setPublicPool(pp); temp.getSession().setSessionType("public_pool"); final User user = userService.getCurrentUser(); - return databaseDao.importSession(user, temp); + return sessionRepository.importSession(user, temp); } @Override @@ -452,24 +452,24 @@ public class SessionService implements ISessionService, ApplicationEventPublishe @Override public SessionFeature getSessionFeatures(String sessionkey) { - return databaseDao.getSessionFromKeyword(sessionkey).getFeatures(); + return sessionRepository.getSessionFromKeyword(sessionkey).getFeatures(); } @Override public SessionFeature changeSessionFeatures(String sessionkey, SessionFeature features) { - final Session session = databaseDao.getSessionFromKeyword(sessionkey); + final Session session = sessionRepository.getSessionFromKeyword(sessionkey); final User user = userService.getCurrentUser(); if (!session.isCreator(user)) { throw new UnauthorizedException("User is not session creator."); } session.setFeatures(features); this.publisher.publishEvent(new FeatureChangeEvent(this, session)); - return databaseDao.updateSession(session).getFeatures(); + return sessionRepository.updateSession(session).getFeatures(); } @Override public boolean lockFeedbackInput(String sessionkey, Boolean lock) { - final Session session = databaseDao.getSessionFromKeyword(sessionkey); + final Session session = sessionRepository.getSessionFromKeyword(sessionkey); final User user = userService.getCurrentUser(); if (!session.isCreator(user)) { throw new UnauthorizedException("User is not session creator."); @@ -480,19 +480,19 @@ public class SessionService implements ISessionService, ApplicationEventPublishe session.setFeedbackLock(lock); this.publisher.publishEvent(new LockFeedbackEvent(this, session)); - return databaseDao.updateSession(session).getFeedbackLock(); + return sessionRepository.updateSession(session).getFeedbackLock(); } @Override public boolean flipFlashcards(String sessionkey, Boolean flip) { - final Session session = databaseDao.getSessionFromKeyword(sessionkey); + final Session session = sessionRepository.getSessionFromKeyword(sessionkey); final User user = userService.getCurrentUser(); if (!session.isCreator(user)) { throw new UnauthorizedException("User is not session creator."); } session.setFlipFlashcards(flip); this.publisher.publishEvent(new FlipFlashcardsEvent(this, session)); - return databaseDao.updateSession(session).getFlipFlashcards(); + return sessionRepository.updateSession(session).getFlipFlashcards(); } private void handleLogo(Session session) { diff --git a/src/main/java/de/thm/arsnova/services/StatisticsService.java b/src/main/java/de/thm/arsnova/services/StatisticsService.java index e8e7fff3e2c234b9007811f36490469f46f8cd55..ddf091b7250fcb066dd4661e399c479e69d33f97 100644 --- a/src/main/java/de/thm/arsnova/services/StatisticsService.java +++ b/src/main/java/de/thm/arsnova/services/StatisticsService.java @@ -17,8 +17,8 @@ */ package de.thm.arsnova.services; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.Statistics; +import de.thm.arsnova.persistance.StatisticsRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -29,18 +29,17 @@ import org.springframework.stereotype.Service; */ @Service public class StatisticsService implements IStatisticsService { - @Autowired - private IDatabaseDao databaseDao; + private StatisticsRepository statisticsRepository; @Autowired private IUserService userService; private Statistics statistics = new Statistics(); - @Scheduled(initialDelay = 0, fixedRate = 300000) + @Scheduled(initialDelay = 0, fixedRate = 10000) private void refreshStatistics() { - statistics = databaseDao.getStatistics(); + statistics = statisticsRepository.getStatistics(); } @Override diff --git a/src/main/java/de/thm/arsnova/services/UserService.java b/src/main/java/de/thm/arsnova/services/UserService.java index 32a4f0837fd0063d364760fb2c8a5a99ab741b3e..fe8fdbc632103524944114306e8539c1c49eb44d 100644 --- a/src/main/java/de/thm/arsnova/services/UserService.java +++ b/src/main/java/de/thm/arsnova/services/UserService.java @@ -18,12 +18,12 @@ package de.thm.arsnova.services; import com.codahale.metrics.annotation.Gauge; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.DbUser; import de.thm.arsnova.entities.User; import de.thm.arsnova.exceptions.BadRequestException; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.exceptions.UnauthorizedException; +import de.thm.arsnova.persistance.UserRepository; import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; import org.pac4j.oauth.profile.facebook.FacebookProfile; @@ -96,7 +96,7 @@ public class UserService implements IUserService { private static final ConcurrentHashMap<User, String> user2session = new ConcurrentHashMap<>(); @Autowired - private IDatabaseDao databaseDao; + private UserRepository userRepository; @Autowired private JavaMailSender mailSender; @@ -172,7 +172,7 @@ public class UserService implements IUserService { logger.info("Delete inactive users."); long unixTime = System.currentTimeMillis(); long lastActivityBefore = unixTime - ACTIVATION_KEY_DURABILITY_MS; - databaseDao.deleteInactiveUsers(lastActivityBefore); + userRepository.deleteInactiveUsers(lastActivityBefore); } @Override @@ -339,7 +339,7 @@ public class UserService implements IUserService { @Override public DbUser getDbUser(String username) { - return databaseDao.getUser(username.toLowerCase()); + return userRepository.findUserByUsername(username.toLowerCase()); } @Override @@ -360,7 +360,7 @@ public class UserService implements IUserService { return null; } - if (null != databaseDao.getUser(lcUsername)) { + if (null != userRepository.findUserByUsername(lcUsername)) { logger.info("User registration failed. {} already exists.", lcUsername); return null; @@ -372,7 +372,7 @@ public class UserService implements IUserService { dbUser.setActivationKey(RandomStringUtils.randomAlphanumeric(32)); dbUser.setCreation(System.currentTimeMillis()); - DbUser result = databaseDao.createOrUpdateUser(dbUser); + DbUser result = userRepository.createOrUpdateUser(dbUser); if (null != result) { sendActivationEmail(result); } else { @@ -436,7 +436,7 @@ public class UserService implements IUserService { @Override public DbUser updateDbUser(DbUser dbUser) { if (null != dbUser.getId()) { - return databaseDao.createOrUpdateUser(dbUser); + return userRepository.createOrUpdateUser(dbUser); } return null; @@ -456,7 +456,7 @@ public class UserService implements IUserService { throw new NotFoundException(); } - databaseDao.deleteUser(dbUser); + userRepository.deleteUser(dbUser); return dbUser; } @@ -478,7 +478,7 @@ public class UserService implements IUserService { dbUser.setPasswordResetKey(RandomStringUtils.randomAlphanumeric(32)); dbUser.setPasswordResetTime(System.currentTimeMillis()); - if (null == databaseDao.createOrUpdateUser(dbUser)) { + if (null == userRepository.createOrUpdateUser(dbUser)) { logger.error("Password reset failed. {} could not be updated.", username); } diff --git a/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java b/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java index 0b91b686e7ffed245f728c7be80c5965990e7fe5..b348dc32cc48c729bde2d61fbad4c0b28c0d26ff 100644 --- a/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java +++ b/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java @@ -28,7 +28,7 @@ import com.corundumstudio.socketio.listener.DataListener; import com.corundumstudio.socketio.listener.DisconnectListener; import com.corundumstudio.socketio.protocol.Packet; import com.corundumstudio.socketio.protocol.PacketType; -import de.thm.arsnova.entities.InterposedQuestion; +import de.thm.arsnova.entities.Comment; import de.thm.arsnova.entities.User; import de.thm.arsnova.entities.transport.LearningProgressOptions; import de.thm.arsnova.events.*; @@ -36,11 +36,11 @@ import de.thm.arsnova.exceptions.NoContentException; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.exceptions.UnauthorizedException; import de.thm.arsnova.services.IFeedbackService; -import de.thm.arsnova.services.IQuestionService; +import de.thm.arsnova.services.IContentService; import de.thm.arsnova.services.ISessionService; import de.thm.arsnova.services.IUserService; import de.thm.arsnova.socket.message.Feedback; -import de.thm.arsnova.socket.message.Question; +import de.thm.arsnova.socket.message.Content; import de.thm.arsnova.socket.message.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,7 +76,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor { private ISessionService sessionService; @Autowired - private IQuestionService questionService; + private IContentService contentService; private static final Logger logger = LoggerFactory.getLogger(ARSnovaSocketIOServer.class); @@ -181,19 +181,19 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor { server.addEventListener( "readInterposedQuestion", - de.thm.arsnova.entities.transport.InterposedQuestion.class, - new DataListener<de.thm.arsnova.entities.transport.InterposedQuestion>() { + de.thm.arsnova.entities.transport.Comment.class, + new DataListener<de.thm.arsnova.entities.transport.Comment>() { @Override @Timed(name = "readInterposedQuestionEvent.onData") public void onData( SocketIOClient client, - de.thm.arsnova.entities.transport.InterposedQuestion question, + de.thm.arsnova.entities.transport.Comment comment, AckRequest ackRequest) { final User user = userService.getUser2SocketId(client.getSessionId()); try { - questionService.readInterposedQuestionInternal(question.getId(), user); + contentService.readInterposedQuestionInternal(comment.getId(), user); } catch (NotFoundException | UnauthorizedException e) { - logger.error("Loading of question {} failed for user {} with exception {}", question.getId(), user, e.getMessage()); + logger.error("Loading of comment {} failed for user {} with exception {}", comment.getId(), user, e.getMessage()); } } }); @@ -203,7 +203,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor { public void onData(SocketIOClient client, String answerId, AckRequest ackRequest) { final User user = userService.getUser2SocketId(client.getSessionId()); try { - questionService.readFreetextAnswer(answerId, user); + contentService.readFreetextAnswer(answerId, user); } catch (NotFoundException | UnauthorizedException e) { logger.error("Marking answer {} as read failed for user {} with exception {}", answerId, user, e.getMessage()); } @@ -360,17 +360,17 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor { final de.thm.arsnova.entities.Session session = sessionService.getSessionInternal(sessionKey, user); final de.thm.arsnova.entities.SessionFeature features = sessionService.getSessionFeatures(sessionKey); - client.sendEvent("unansweredLecturerQuestions", questionService.getUnAnsweredLectureQuestionIds(sessionKey, user)); - client.sendEvent("unansweredPreparationQuestions", questionService.getUnAnsweredPreparationQuestionIds(sessionKey, user)); - client.sendEvent("countLectureQuestionAnswers", questionService.countLectureQuestionAnswersInternal(sessionKey)); - client.sendEvent("countPreparationQuestionAnswers", questionService.countPreparationQuestionAnswersInternal(sessionKey)); + client.sendEvent("unansweredLecturerQuestions", contentService.getUnAnsweredLectureQuestionIds(sessionKey, user)); + client.sendEvent("unansweredPreparationQuestions", contentService.getUnAnsweredPreparationQuestionIds(sessionKey, user)); + client.sendEvent("countLectureQuestionAnswers", contentService.countLectureQuestionAnswersInternal(sessionKey)); + client.sendEvent("countPreparationQuestionAnswers", contentService.countPreparationQuestionAnswersInternal(sessionKey)); client.sendEvent("activeUserCountData", sessionService.activeUsers(sessionKey)); client.sendEvent("learningProgressOptions", session.getLearningProgressOptions()); final de.thm.arsnova.entities.Feedback fb = feedbackService.getFeedback(sessionKey); client.sendEvent("feedbackData", fb.getValues()); if (features.isFlashcard() || features.isFlashcardFeature()) { - client.sendEvent("countFlashcards", questionService.countFlashcardsForUserInternal(sessionKey)); + client.sendEvent("countFlashcards", contentService.countFlashcardsForUserInternal(sessionKey)); client.sendEvent("flipFlashcards", session.getFlipFlashcards()); } @@ -421,34 +421,34 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor { broadcastInSession(sessionKey, "activeUserCountData", count); } - public void reportAnswersToLecturerQuestionAvailable(final de.thm.arsnova.entities.Session session, final Question lecturerQuestion) { - broadcastInSession(session.getKeyword(), "answersToLecQuestionAvail", lecturerQuestion.get_id()); + public void reportAnswersToLecturerQuestionAvailable(final de.thm.arsnova.entities.Session session, final Content content) { + broadcastInSession(session.getKeyword(), "answersToLecQuestionAvail", content.get_id()); } - public void reportAudienceQuestionAvailable(final de.thm.arsnova.entities.Session session, final InterposedQuestion audienceQuestion) { + public void reportAudienceQuestionAvailable(final de.thm.arsnova.entities.Session session, final Comment audienceQuestion) { /* TODO role handling implementation, send this only to users with role lecturer */ - broadcastInSession(session.getKeyword(), "audQuestionAvail", audienceQuestion.get_id()); + broadcastInSession(session.getKeyword(), "audQuestionAvail", audienceQuestion.getId()); } - public void reportLecturerQuestionAvailable(final de.thm.arsnova.entities.Session session, final List<de.thm.arsnova.entities.Question> qs) { - List<Question> questions = new ArrayList<>(); - for (de.thm.arsnova.entities.Question q : qs) { - questions.add(new Question(q)); + public void reportLecturerQuestionAvailable(final de.thm.arsnova.entities.Session session, final List<de.thm.arsnova.entities.Content> qs) { + List<Content> contents = new ArrayList<>(); + for (de.thm.arsnova.entities.Content q : qs) { + contents.add(new Content(q)); } /* TODO role handling implementation, send this only to users with role audience */ if (!qs.isEmpty()) { - broadcastInSession(session.getKeyword(), "lecQuestionAvail", questions.get(0).get_id()); // deprecated! + broadcastInSession(session.getKeyword(), "lecQuestionAvail", contents.get(0).get_id()); // deprecated! } - broadcastInSession(session.getKeyword(), "lecturerQuestionAvailable", questions); + broadcastInSession(session.getKeyword(), "lecturerQuestionAvailable", contents); } - public void reportLecturerQuestionsLocked(final de.thm.arsnova.entities.Session session, final List<de.thm.arsnova.entities.Question> qs) { - List<Question> questions = new ArrayList<>(); - for (de.thm.arsnova.entities.Question q : qs) { - questions.add(new Question(q)); + public void reportLecturerQuestionsLocked(final de.thm.arsnova.entities.Session session, final List<de.thm.arsnova.entities.Content> qs) { + List<Content> contents = new ArrayList<>(); + for (de.thm.arsnova.entities.Content q : qs) { + contents.add(new Content(q)); } - broadcastInSession(session.getKeyword(), "lecturerQuestionLocked", questions); + broadcastInSession(session.getKeyword(), "lecturerQuestionLocked", contents); } public void reportSessionStatus(final String sessionKey, final boolean active) { @@ -496,7 +496,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor { } @Override - public void visit(NewInterposedQuestionEvent event) { + public void visit(NewCommentEvent event) { this.reportAudienceQuestionAvailable(event.getSession(), event.getQuestion()); } @@ -505,17 +505,17 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor { @Timed(name = "visit.NewAnswerEvent") public void visit(NewAnswerEvent event) { final String sessionKey = event.getSession().getKeyword(); - this.reportAnswersToLecturerQuestionAvailable(event.getSession(), new Question(event.getQuestion())); - broadcastInSession(sessionKey, "countQuestionAnswersByQuestionId", questionService.getAnswerAndAbstentionCountInternal(event.getQuestion().get_id())); - broadcastInSession(sessionKey, "countLectureQuestionAnswers", questionService.countLectureQuestionAnswersInternal(sessionKey)); - broadcastInSession(sessionKey, "countPreparationQuestionAnswers", questionService.countPreparationQuestionAnswersInternal(sessionKey)); - - // Update the unanswered count for the question variant that was answered. - final de.thm.arsnova.entities.Question question = event.getQuestion(); - if ("lecture".equals(question.getQuestionVariant())) { - sendToUser(event.getUser(), "unansweredLecturerQuestions", questionService.getUnAnsweredLectureQuestionIds(sessionKey, event.getUser())); - } else if ("preparation".equals(question.getQuestionVariant())) { - sendToUser(event.getUser(), "unansweredPreparationQuestions", questionService.getUnAnsweredPreparationQuestionIds(sessionKey, event.getUser())); + this.reportAnswersToLecturerQuestionAvailable(event.getSession(), new Content(event.getContent())); + broadcastInSession(sessionKey, "countQuestionAnswersByQuestionId", contentService.getAnswerAndAbstentionCountInternal(event.getContent().getId())); + broadcastInSession(sessionKey, "countLectureQuestionAnswers", contentService.countLectureQuestionAnswersInternal(sessionKey)); + broadcastInSession(sessionKey, "countPreparationQuestionAnswers", contentService.countPreparationQuestionAnswersInternal(sessionKey)); + + // Update the unanswered count for the content variant that was answered. + final de.thm.arsnova.entities.Content content = event.getContent(); + if ("lecture".equals(content.getQuestionVariant())) { + sendToUser(event.getUser(), "unansweredLecturerQuestions", contentService.getUnAnsweredLectureQuestionIds(sessionKey, event.getUser())); + } else if ("preparation".equals(content.getQuestionVariant())) { + sendToUser(event.getUser(), "unansweredPreparationQuestions", contentService.getUnAnsweredPreparationQuestionIds(sessionKey, event.getUser())); } } @@ -524,10 +524,10 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor { @Timed(name = "visit.DeleteAnswerEvent") public void visit(DeleteAnswerEvent event) { final String sessionKey = event.getSession().getKeyword(); - this.reportAnswersToLecturerQuestionAvailable(event.getSession(), new Question(event.getQuestion())); + this.reportAnswersToLecturerQuestionAvailable(event.getSession(), new Content(event.getQuestion())); // We do not know which user's answer was deleted, so we can't update his 'unanswered' list of questions... - broadcastInSession(sessionKey, "countLectureQuestionAnswers", questionService.countLectureQuestionAnswersInternal(sessionKey)); - broadcastInSession(sessionKey, "countPreparationQuestionAnswers", questionService.countPreparationQuestionAnswersInternal(sessionKey)); + broadcastInSession(sessionKey, "countLectureQuestionAnswers", contentService.countLectureQuestionAnswersInternal(sessionKey)); + broadcastInSession(sessionKey, "countPreparationQuestionAnswers", contentService.countPreparationQuestionAnswersInternal(sessionKey)); } @Async @@ -574,20 +574,20 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor { @Override public void visit(LockVotesEvent event) { - List<Question> questions = new ArrayList<>(); - for (de.thm.arsnova.entities.Question q : event.getQuestions()) { - questions.add(new Question(q)); + List<Content> contents = new ArrayList<>(); + for (de.thm.arsnova.entities.Content q : event.getQuestions()) { + contents.add(new Content(q)); } - broadcastInSession(event.getSession().getKeyword(), "lockVotes", questions); + broadcastInSession(event.getSession().getKeyword(), "lockVotes", contents); } @Override public void visit(UnlockVotesEvent event) { - List<Question> questions = new ArrayList<>(); - for (de.thm.arsnova.entities.Question q : event.getQuestions()) { - questions.add(new Question(q)); + List<Content> contents = new ArrayList<>(); + for (de.thm.arsnova.entities.Content q : event.getQuestions()) { + contents.add(new Content(q)); } - broadcastInSession(event.getSession().getKeyword(), "unlockVotes", questions); + broadcastInSession(event.getSession().getKeyword(), "unlockVotes", contents); } @Override @@ -597,7 +597,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor { broadcastInSession(sessionKey, "featureChange", features); if (features.isFlashcard() || features.isFlashcardFeature()) { - broadcastInSession(sessionKey, "countFlashcards", questionService.countFlashcardsForUserInternal(sessionKey)); + broadcastInSession(sessionKey, "countFlashcards", contentService.countFlashcardsForUserInternal(sessionKey)); broadcastInSession(sessionKey, "flipFlashcards", event.getSession().getFlipFlashcards()); } } @@ -643,7 +643,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor { } @Override - public void visit(DeleteInterposedQuestionEvent deleteInterposedQuestionEvent) { + public void visit(DeleteCommentEvent deleteCommentEvent) { // TODO Auto-generated method stub } diff --git a/src/main/java/de/thm/arsnova/socket/message/Question.java b/src/main/java/de/thm/arsnova/socket/message/Content.java similarity index 85% rename from src/main/java/de/thm/arsnova/socket/message/Question.java rename to src/main/java/de/thm/arsnova/socket/message/Content.java index c8660e3dbc80ef7e479d86832f9c053d1e688287..f462baa7bb7e62b81f15e512774f630d9e848d80 100644 --- a/src/main/java/de/thm/arsnova/socket/message/Question.java +++ b/src/main/java/de/thm/arsnova/socket/message/Content.java @@ -20,14 +20,14 @@ package de.thm.arsnova.socket.message; /** * Represents a question. */ -public class Question { +public class Content { private final String _id; private final String variant; - public Question(de.thm.arsnova.entities.Question question) { - this._id = question.get_id(); - this.variant = question.getQuestionVariant(); + public Content(de.thm.arsnova.entities.Content content) { + this._id = content.getId(); + this.variant = content.getQuestionVariant(); } public String get_id() { diff --git a/src/main/resources/couchdb/answer.design.js b/src/main/resources/couchdb/Answer.design.js similarity index 72% rename from src/main/resources/couchdb/answer.design.js rename to src/main/resources/couchdb/Answer.design.js index a79ac0a403f0d1c97e5aa31faea5d8387333bf37..44d0a92f3b4ecf99a9bcfb878668bb2421f19847 100644 --- a/src/main/resources/couchdb/answer.design.js +++ b/src/main/resources/couchdb/Answer.design.js @@ -1,59 +1,58 @@ var designDoc = { - "_id": "_design/answer", + "_id": "_design/Answer", "language": "javascript", "views": { - "doc_by_questionid_user_piround": { + "by_questionid": { "map": function (doc) { if (doc.type === "skill_question_answer") { - emit([doc.questionId, doc.user, doc.piRound], doc); + emit(doc.questionId, {_rev: doc._rev}); } } }, - "doc_by_questionid_timestamp": { + "by_questionid_piround_text_subject": { "map": function (doc) { if (doc.type === "skill_question_answer") { - emit([doc.questionId, doc.timestamp], doc); + emit([doc.questionId, doc.piRound, doc.abstention, doc.answerText, doc.answerSubject, doc.successfulFreeTextAnswer], {_rev: doc._rev}); } - } + }, + "reduce": "_count" }, - "doc_by_user_sessionid": { + "by_questionid_timestamp": { "map": function (doc) { if (doc.type === "skill_question_answer") { - emit([doc.user, doc.sessionId], doc); + emit([doc.questionId, doc.timestamp], {_rev: doc._rev}); } } }, - "by_questionid": { + "by_questionid_user_piround": { "map": function (doc) { if (doc.type === "skill_question_answer") { - emit(doc.questionId, null); + emit([doc.questionId, doc.user, doc.piRound], {_rev: doc._rev}); } } }, - "by_questionid_piround_text_subject": { + "by_sessionid": { "map": function (doc) { if (doc.type === "skill_question_answer") { - emit([doc.questionId, doc.piRound, doc.abstention, doc.answerText, doc.answerSubject, doc.successfulFreeTextAnswer], null); + emit(doc.sessionId, {_rev: doc._rev}); } }, "reduce": "_count" }, - "by_sessionid": { - /* Redundant view but kept for now to allow simpler queries. */ + "by_sessionid_variant": { "map": function (doc) { if (doc.type === "skill_question_answer") { - emit(doc.sessionId, null); + emit([doc.sessionId, doc.questionVariant], {_rev: doc._rev}); } }, "reduce": "_count" }, - "by_sessionid_variant": { + "by_user_sessionid": { "map": function (doc) { if (doc.type === "skill_question_answer") { - emit([doc.sessionId, doc.questionVariant], null); + emit([doc.user, doc.sessionId], {_rev: doc._rev}); } - }, - "reduce": "_count" + } }, "questionid_by_user_sessionid_variant": { "map": function (doc) { diff --git a/src/main/resources/couchdb/comment.design.js b/src/main/resources/couchdb/Comment.design.js similarity index 61% rename from src/main/resources/couchdb/comment.design.js rename to src/main/resources/couchdb/Comment.design.js index 23936b94eb52f3eddd55b416c2dcd19189c26e18..c407a2eb551f8c45f8f87c8efcdc69443d1affc1 100644 --- a/src/main/resources/couchdb/comment.design.js +++ b/src/main/resources/couchdb/Comment.design.js @@ -1,45 +1,44 @@ var designDoc = { - "_id": "_design/comment", + "_id": "_design/Comment", "language": "javascript", "views": { - "doc_by_sessionid_creator_timestamp": { + "by_sessionid": { "map": function (doc) { if (doc.type === "interposed_question") { - emit([doc.sessionId, doc.creator, doc.timestamp], doc); + emit(doc.sessionId, {_rev: doc._rev}); } - } + }, + "reduce": "_count" }, - "doc_by_sessionid_timestamp": { + "by_sessionid_creator_read": { "map": function (doc) { if (doc.type === "interposed_question") { - emit([doc.sessionId, doc.timestamp], doc); + emit([doc.sessionId, doc.creator, doc.read], {_rev: doc._rev}) } - } + }, + "reduce": "_count" }, - "by_sessionid": { - /* Redundant view but kept for now to allow simpler queries. */ + "by_sessionid_creator_timestamp": { "map": function (doc) { if (doc.type === "interposed_question") { - emit(doc.sessionId, null); + emit([doc.sessionId, doc.creator, doc.timestamp], {_rev: doc._rev}); } - }, - "reduce": "_count" + } }, "by_sessionid_read": { "map": function (doc) { if (doc.type === "interposed_question") { - emit([doc.sessionId, doc.read], null); + emit([doc.sessionId, doc.read], {_rev: doc._rev}); } }, "reduce": "_count" }, - "by_sessionid_creator_read": { + "by_sessionid_timestamp": { "map": function (doc) { if (doc.type === "interposed_question") { - emit([doc.sessionId, doc.creator, doc.read], null); + emit([doc.sessionId, doc.timestamp], {_rev: doc._rev}); } - }, - "reduce": "_count" + } } } }; diff --git a/src/main/resources/couchdb/Content.design.js b/src/main/resources/couchdb/Content.design.js new file mode 100644 index 0000000000000000000000000000000000000000..cb3f2f98208cb83b87d7d5b1183f43c969629c1f --- /dev/null +++ b/src/main/resources/couchdb/Content.design.js @@ -0,0 +1,22 @@ +var designDoc = { + "_id": "_design/Content", + "language": "javascript", + "views": { + "by_sessionid": { + "map": function (doc) { + if (doc.type === "skill_question") { + emit(doc.sessionId, {_rev: doc._rev}); + } + }, + "reduce": "_count" + }, + "by_sessionid_variant_active": { + "map": function (doc) { + if (doc.type === "skill_question") { + emit([doc.sessionId, doc.questionVariant, doc.active, doc.subject, doc.text.substr(0, 16)], {_rev: doc._rev}); + } + }, + "reduce": "_count" + } + } +}; diff --git a/src/main/resources/couchdb/user.design.js b/src/main/resources/couchdb/DbUser.design.js similarity index 69% rename from src/main/resources/couchdb/user.design.js rename to src/main/resources/couchdb/DbUser.design.js index 9299516f89a56f92017224eb5416e16ab3b9cba8..aa63058d15eb951257a7e076ba2f8cb99a25bb15 100644 --- a/src/main/resources/couchdb/user.design.js +++ b/src/main/resources/couchdb/DbUser.design.js @@ -1,20 +1,18 @@ var designDoc = { - "_id": "_design/user", + "_id": "_design/DbUser", "language": "javascript", "views": { - "doc_by_username": { - "map": function (doc) { - if (doc.type === "userdetails") { - emit(doc.username, doc); - } - } - }, "by_creation_for_inactive": { "map": function (doc) { if (doc.type === "userdetails" && doc.activationKey) { emit(doc.creation, {_rev: doc._rev}); } } + }, + "by_username": { + "map": function (doc) { + if (doc.type === "userdetails") emit(doc.username, {_rev: doc._rev}); + } } } }; diff --git a/src/main/resources/couchdb/logged_in.design.js b/src/main/resources/couchdb/LoggedIn.design.js similarity index 94% rename from src/main/resources/couchdb/logged_in.design.js rename to src/main/resources/couchdb/LoggedIn.design.js index 1b196b4f5aecb0e8516d638de58da599941c5913..aadec2d1e364f8ef9b15defa43c75dae29f4ba68 100644 --- a/src/main/resources/couchdb/logged_in.design.js +++ b/src/main/resources/couchdb/LoggedIn.design.js @@ -1,14 +1,7 @@ var designDoc = { - "_id": "_design/logged_in", + "_id": "_design/LoggedIn", "language": "javascript", "views": { - "visited_sessions_by_user": { - "map": function (doc) { - if (doc.type === "logged_in") { - emit(doc.user, doc.visitedSessions); - } - } - }, "all": { "map": function (doc) { if (doc.type === "logged_in"){ @@ -22,6 +15,13 @@ var designDoc = { emit(doc.timestamp || 0, {_rev: doc._rev}); } } + }, + "visited_sessions_by_user": { + "map": function (doc) { + if (doc.type === "logged_in") { + emit(doc.user, doc.visitedSessions); + } + } } } }; diff --git a/src/main/resources/couchdb/motd.design.js b/src/main/resources/couchdb/Motd.design.js similarity index 63% rename from src/main/resources/couchdb/motd.design.js rename to src/main/resources/couchdb/Motd.design.js index 33b111ee3f0765829ca9b4731cbeec47f1efbb8f..8e932510dc5ee7fddcae328058b636d1ae7ca2ac 100644 --- a/src/main/resources/couchdb/motd.design.js +++ b/src/main/resources/couchdb/Motd.design.js @@ -1,25 +1,25 @@ var designDoc = { - "_id": "_design/motd", + "_id": "_design/Motd", "language": "javascript", "views": { - "doc_by_sessionkey": { + "by_audience_for_global": { "map": function (doc) { - if (doc.type === "motd" && doc.audience === "session") { - emit(doc.sessionkey, doc); + if (doc.type === "motd" && doc.audience !== "session") { + emit(doc.audience, {_rev: doc._rev}); } } }, - "doc_by_audience_for_global": { + "by_motdkey": { "map": function (doc) { - if (doc.type === "motd" && doc.audience !== "session") { - emit(doc.audience, doc); + if (doc.type === "motd") { + emit(doc.motdkey, {_rev: doc._rev}); } } }, - "by_motdkey": { + "by_sessionkey": { "map": function (doc) { - if (doc.type === "motd") { - emit(doc.motdkey, doc); + if (doc.type === "motd" && doc.audience === "session") { + emit(doc.sessionkey, {_rev: doc._rev}); } } } diff --git a/src/main/resources/couchdb/motdlist.design.js b/src/main/resources/couchdb/MotdList.design.js similarity index 87% rename from src/main/resources/couchdb/motdlist.design.js rename to src/main/resources/couchdb/MotdList.design.js index 37712e4ea35821de5b9b50d4a2e7b9e703589890..e7ddbe7621d93c89cb4b5ea2784369756452afe6 100644 --- a/src/main/resources/couchdb/motdlist.design.js +++ b/src/main/resources/couchdb/MotdList.design.js @@ -1,5 +1,5 @@ var designDoc = { - "_id": "_design/motdlist", + "_id": "_design/MotdList", "language": "javascript", "views": { "doc_by_username": { diff --git a/src/main/resources/couchdb/session.design.js b/src/main/resources/couchdb/Session.design.js similarity index 76% rename from src/main/resources/couchdb/session.design.js rename to src/main/resources/couchdb/Session.design.js index b8b1346b0a026b32d79c18be68ed55602b1a0357..790e3af6afe0829abf1c2d177a6baa94acb2d390 100644 --- a/src/main/resources/couchdb/session.design.js +++ b/src/main/resources/couchdb/Session.design.js @@ -1,18 +1,25 @@ var designDoc = { - "_id": "_design/session", + "_id": "_design/Session", "language": "javascript", "views": { "by_courseid": { "map": function (doc) { - if (doc.type === "session" && doc.courseId && doc.sessionType !== "public_pool") { - emit(doc.courseId, null); + if (doc.type === "session" && doc.courseId && doc.sessionType !== "public_pool") { + emit(doc.courseId, {_rev: doc._rev}); } } }, "by_keyword": { "map": function (doc) { if (doc.type === "session") { - emit(doc.keyword, null); + emit(doc.keyword, {_rev: doc._rev}); + } + } + }, + "by_lastactivity_for_guests": { + "map": function (doc) { + if (doc.type === "session" && doc.sessionType !== "public_pool" && doc.creator.indexOf("Guest") === 0) { + emit(doc.lastOwnerActivity || doc.creationTime, {_rev: doc._rev}); } } }, @@ -29,9 +36,9 @@ var designDoc = { } } }, - "partial_by_ppsubject_name_for_publicpool": { + "partial_by_subject_name_for_publicpool": { "map": function (doc) { - if (doc.type === "session" && doc.sessionType === "public_pool") { + if (doc.type === "session" && doc.sessiontype === "public_pool") { emit([doc.ppSubject, doc.name], { ppSubject: doc.ppSubject, name: doc.name, @@ -40,13 +47,6 @@ var designDoc = { }); } } - }, - "by_lastactivity_for_guests": { - "map": function (doc) { - if (doc.type === "session" && doc.sessionType !== "public_pool" && doc.creator.indexOf("Guest") === 0) { - emit(doc.lastOwnerActivity || doc.creationTime, {_rev: doc._rev}); - } - } } } }; diff --git a/src/main/resources/couchdb/content.design.js b/src/main/resources/couchdb/content.design.js deleted file mode 100644 index 74518a473fed5b92a06d9059ccaccad10f097e6d..0000000000000000000000000000000000000000 --- a/src/main/resources/couchdb/content.design.js +++ /dev/null @@ -1,31 +0,0 @@ -var designDoc = { - "_id": "_design/content", - "language": "javascript", - "views": { - "doc_by_sessionid_variant_active": { - "map": function (doc) { - if (doc.type === "skill_question") { - emit([doc.sessionId, doc.questionVariant, doc.active, doc.subject, doc.text.substr(0, 16)], doc); - } - }, - "reduce": "_count" - }, - "by_sessionid": { - /* Redundant view but kept for now to allow simpler queries. */ - "map": function (doc) { - if (doc.type === "skill_question") { - emit(doc.sessionId, null); - } - }, - "reduce": "_count" - }, - "by_sessionid_variant_active": { - "map": function (doc) { - if (doc.type === "skill_question") { - emit([doc.sessionId, doc.questionVariant, doc.active, doc.subject, doc.text.substr(0, 16)], null); - } - }, - "reduce": "_count" - } - } -}; diff --git a/src/main/resources/couchdb/jsToJson.js b/src/main/resources/couchdb/jsToJson.js new file mode 100644 index 0000000000000000000000000000000000000000..849ac560a9ac6084f979ab265f43bcc9def3688e --- /dev/null +++ b/src/main/resources/couchdb/jsToJson.js @@ -0,0 +1,15 @@ +/** + * Transforms JS map functions of the passed object to strings. JS functions are + * not valid JSON. Additionally, redundant indentation is removed from the + * function string. + */ +function jsToJson(designDoc) { + var views = designDoc.views; + Object.keys(views).forEach(function (viewName) { + views[viewName].map = views[viewName].map.toString().replace(/\n\t{3}(\t*)/g, function (m, p1) { + return "\n" + p1.replace(/\t/g, " "); + }); + }); + + return designDoc; +} diff --git a/src/main/resources/couchdb/lerning_progress.design.js b/src/main/resources/couchdb/lerning_progress.design.js index 2b35eec84bae6275100ed85f70f95b94d0c2c671..45c27e113fcb2581141d2bcbaf405b926f9eecd1 100644 --- a/src/main/resources/couchdb/lerning_progress.design.js +++ b/src/main/resources/couchdb/lerning_progress.design.js @@ -30,7 +30,7 @@ var designDoc = { var value = 0, answers = [], positiveAnswers = [], score = 0; if (doc.type === "skill_question" && ["school", "flashcard"].indexOf(doc.questionType) === -1) { if ("freetext" === doc.questionType && !doc.fixedAnswer) { return; } - answers = doc.possibleAnswers.map(function(answer) { return answer.value || 0; }); + answers = doc.possibleAnswers.map(function (answer) { return answer.value || 0; }); /* find the maximum value */ if (doc.fixedAnswer) { value = doc.rating; } else { value = Math.max.apply(null, [0].concat(answers)); } @@ -38,9 +38,9 @@ var designDoc = { if (doc.questionType === "vote" && value === 0) { return; } /* special case for mc and grid questions: add up all positive answers. */ if (["grid", "mc"].indexOf(doc.questionType) !== -1) { - positiveAnswers = answers.filter(function(val) { return val >= 0; }); + positiveAnswers = answers.filter(function (val) { return val >= 0; }); if (positiveAnswers.length > 0) { - value = positiveAnswers.reduce(function(prev, cur) { return prev + cur; }, 0); + value = positiveAnswers.reduce(function (prev, cur) { return prev + cur; }, 0); } } emit([doc.sessionId, doc._id], { diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 4e7f0b83335831889672427c0547478cda89c690..03878657246d76d7580eeb9cb346974fe2ef0177 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -11,6 +11,7 @@ <param-name>contextConfigLocation</param-name> <param-value> de.thm.arsnova.config.AppConfig + de.thm.arsnova.config.PersistanceConfig de.thm.arsnova.config.SecurityConfig </param-value> </context-param> diff --git a/src/test/java/de/thm/arsnova/ImageUtilsTest.java b/src/test/java/de/thm/arsnova/ImageUtilsTest.java index 5a2090d6d47b72a48666ebf508121a93be15cf59..5c27e1eb596eaf7dbd2809fd3154dd6c797bb232 100644 --- a/src/test/java/de/thm/arsnova/ImageUtilsTest.java +++ b/src/test/java/de/thm/arsnova/ImageUtilsTest.java @@ -19,6 +19,7 @@ package de.thm.arsnova; import de.thm.arsnova.config.AppConfig; import de.thm.arsnova.config.TestAppConfig; +import de.thm.arsnova.config.TestPersistanceConfig; import de.thm.arsnova.config.TestSecurityConfig; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,7 +34,7 @@ import static org.junit.Assert.*; @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration -@ContextConfiguration(classes = {AppConfig.class, TestAppConfig.class, TestSecurityConfig.class}) +@ContextConfiguration(classes = {AppConfig.class, TestAppConfig.class, TestPersistanceConfig.class, TestSecurityConfig.class}) @ActiveProfiles("test") public class ImageUtilsTest { diff --git a/src/test/java/de/thm/arsnova/config/AppConfigTest.java b/src/test/java/de/thm/arsnova/config/AppConfigTest.java index 8ad3630ccfb52dc086113f1219623e720a6b5871..355cd7f0720cfc8b2f906408e10bdb85cbee02e2 100644 --- a/src/test/java/de/thm/arsnova/config/AppConfigTest.java +++ b/src/test/java/de/thm/arsnova/config/AppConfigTest.java @@ -31,7 +31,7 @@ import static org.junit.Assert.assertNull; @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration -@ContextConfiguration(classes = {AppConfig.class, TestAppConfig.class, TestSecurityConfig.class}) +@ContextConfiguration(classes = {AppConfig.class, TestAppConfig.class, TestPersistanceConfig.class, TestSecurityConfig.class}) @ActiveProfiles("test") public class AppConfigTest extends AbstractJUnit4SpringContextTests { diff --git a/src/test/java/de/thm/arsnova/config/TestAppConfig.java b/src/test/java/de/thm/arsnova/config/TestAppConfig.java index ff715cd5c31e2e28a4247152a9511d9ce7e33371..f2759881edb031f59b10e8070fe79f706b9400ad 100644 --- a/src/test/java/de/thm/arsnova/config/TestAppConfig.java +++ b/src/test/java/de/thm/arsnova/config/TestAppConfig.java @@ -1,7 +1,5 @@ package de.thm.arsnova.config; -import de.thm.arsnova.dao.IDatabaseDao; -import de.thm.arsnova.dao.StubDatabaseDao; import de.thm.arsnova.services.StubUserService; import de.thm.arsnova.socket.ARSnovaSocket; import de.thm.arsnova.socket.ARSnovaSocketIOServer; @@ -60,11 +58,6 @@ public class TestAppConfig { return configurer; } - @Bean - public IDatabaseDao databaseDao() { - return new StubDatabaseDao(); - } - @Bean(name = "socketServer", initMethod = "startServer", destroyMethod = "stopServer") public ARSnovaSocket socketTestServer() { final int testSocketPort = 1234 + testPortOffset++ % 10; diff --git a/src/test/java/de/thm/arsnova/config/TestPersistanceConfig.java b/src/test/java/de/thm/arsnova/config/TestPersistanceConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..4801a85488368fff06279def60b4d0cc18230e65 --- /dev/null +++ b/src/test/java/de/thm/arsnova/config/TestPersistanceConfig.java @@ -0,0 +1,66 @@ +package de.thm.arsnova.config; + +import de.thm.arsnova.persistance.*; +import org.mockito.Mockito; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Profile("test") +@Configuration +public class TestPersistanceConfig { + @Bean + public LogEntryRepository logEntryRepository() { + return Mockito.mock(LogEntryRepository.class); + } + + @Bean + public UserRepository userRepository() { + return Mockito.mock(UserRepository.class); + } + + @Bean + public SessionRepository sessionRepository() { + return Mockito.mock(SessionRepository.class); + } + + @Bean + public CommentRepository commentRepository() { + return Mockito.mock(CommentRepository.class); + } + + @Bean + public ContentRepository contentRepository() { + return Mockito.mock(ContentRepository.class); + } + + @Bean + public AnswerRepository answerRepository() { + return Mockito.mock(AnswerRepository.class); + } + + @Bean + public MotdRepository motdRepository() { + return Mockito.mock(MotdRepository.class); + } + + @Bean + public MotdListRepository motdListRepository() { + return Mockito.mock(MotdListRepository.class); + } + + @Bean + public VisitedSessionRepository visitedSessionRepository() { + return Mockito.mock(VisitedSessionRepository.class); + } + + @Bean + public StatisticsRepository statisticsRepository() { + return Mockito.mock(StatisticsRepository.class); + } + + @Bean + public SessionStatisticsRepository sessionStatisticsRepository() { + return Mockito.mock(SessionStatisticsRepository.class); + } +} diff --git a/src/test/java/de/thm/arsnova/controller/AbstractControllerTest.java b/src/test/java/de/thm/arsnova/controller/AbstractControllerTest.java index c10aecac79c2e4135af085d55e3bca0a2b7d021c..09ee64b6a4cfbe803b57aab8c3352c327741254f 100644 --- a/src/test/java/de/thm/arsnova/controller/AbstractControllerTest.java +++ b/src/test/java/de/thm/arsnova/controller/AbstractControllerTest.java @@ -19,6 +19,7 @@ package de.thm.arsnova.controller; import de.thm.arsnova.config.AppConfig; import de.thm.arsnova.config.TestAppConfig; +import de.thm.arsnova.config.TestPersistanceConfig; import de.thm.arsnova.config.TestSecurityConfig; import de.thm.arsnova.services.StubUserService; import org.junit.After; @@ -38,7 +39,7 @@ import java.util.List; @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration -@ContextConfiguration(classes = {AppConfig.class, TestAppConfig.class, TestSecurityConfig.class}) +@ContextConfiguration(classes = {AppConfig.class, TestAppConfig.class, TestPersistanceConfig.class, TestSecurityConfig.class}) @ActiveProfiles("test") public abstract class AbstractControllerTest extends AbstractJUnit4SpringContextTests { diff --git a/src/test/java/de/thm/arsnova/controller/FeedbackControllerTest.java b/src/test/java/de/thm/arsnova/controller/FeedbackControllerTest.java deleted file mode 100644 index 6824a3706a063b96f8421b413d0ee3d6a5207488..0000000000000000000000000000000000000000 --- a/src/test/java/de/thm/arsnova/controller/FeedbackControllerTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of ARSnova Backend. - * Copyright (C) 2012-2017 The ARSnova Team - * - * 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.controller; - -import de.thm.arsnova.services.StubUserService; -import org.junit.Before; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -public class FeedbackControllerTest extends AbstractControllerTest { - - @Autowired - private StubUserService userService; - - private MockMvc mockMvc; - - @Autowired - private WebApplicationContext webApplicationContext; - - @Before - public void setup() { - mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); - } - - @Test - public void testShouldNotGetFeedbackForUnknownSession() throws Exception { - userService.setUserAuthenticated(true); - mockMvc.perform(get("/session/00000000/feedback").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()); - } - - @Test - public void testShouldNotGetAverageFeedbackContentForSessionWithoutFeedback() throws Exception { - userService.setUserAuthenticated(true); - mockMvc.perform(get("/session/12345678/averagefeedback").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNoContent()); - } - - @Test - public void testShouldNotGetCorrectFeedbackCountForSessionWithoutFeedback() throws Exception { - userService.setUserAuthenticated(true); - mockMvc.perform(get("/session/12345678/feedbackcount").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().string("0")); - } - - @Test - public void testShouldReturnFeedback() throws Exception { - userService.setUserAuthenticated(true); - mockMvc.perform(get("/session/87654321/feedback").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$.values").isArray()); - } - - @Test - public void testShouldReturnXDeprecatedApiHeaderForFeedback() throws Exception { - userService.setUserAuthenticated(true); - mockMvc.perform(get("/session/87654321/feedback").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(header().string(AbstractController.X_DEPRECATED_API, "1")); - } -} diff --git a/src/test/java/de/thm/arsnova/controller/LecturerQuestionControllerTest.java b/src/test/java/de/thm/arsnova/controller/LecturerQuestionControllerTest.java deleted file mode 100644 index 47a9b29c0e1d35b9ede8046a0562e4384f41bad9..0000000000000000000000000000000000000000 --- a/src/test/java/de/thm/arsnova/controller/LecturerQuestionControllerTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * This file is part of ARSnova Backend. - * Copyright (C) 2012-2017 The ARSnova Team - * - * 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.controller; - -import de.thm.arsnova.services.StubUserService; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -public class LecturerQuestionControllerTest extends AbstractControllerTest { - - @Autowired - private StubUserService userService; - - @Autowired - private LecturerQuestionController questionController; - - private MockMvc mockMvc; - - @Autowired - private WebApplicationContext webApplicationContext; - - @Before - public void startup() { - mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); - SecurityContextHolder.clearContext(); - } - - @After - public void cleanup() { - SecurityContextHolder.clearContext(); - userService.setUserAuthenticated(false); - } - - @Test - public void testShouldNotGetLecturerQuestionsIfUnauthorized() throws Exception { - setAuthenticated(false, "nobody"); - - mockMvc.perform( - get("/lecturerquestion/") - .param("sessionkey", "12345678").param("lecturequestionsonly", "true") - .accept(MediaType.APPLICATION_JSON) - ).andExpect(status().isUnauthorized() - ); - } - - @Test - public void testShouldNotGetPreparationQuestionsIfUnauthorized() throws Exception { - setAuthenticated(false, "nobody"); - - mockMvc.perform( - get("/lecturerquestion/") - .param("sessionkey", "12345678").param("preparationquestionsonly", "true") - .accept(MediaType.APPLICATION_JSON) - ).andExpect(status().isUnauthorized() - ); - } - - @Test - public void testShouldReturnQuestionCount() throws Exception { - setAuthenticated(true, "somebody"); - - mockMvc.perform( - get("/lecturerquestion/count") - .param("sessionkey", "12345678").param("lecturequestionsonly", "true") - .accept(MediaType.APPLICATION_JSON) - ).andExpect(status().isOk()) - .andExpect(content().string("0") - ); - } - - @Test - public void testShouldReturnXDeprecatedApiHeaderForQuestionCount() throws Exception { - setAuthenticated(true, "somebody"); - - mockMvc.perform( - get("/lecturerquestion/count") - .param("sessionkey", "12345678").param("lecturequestionsonly", "true") - .accept(MediaType.APPLICATION_JSON) - ).andExpect(status().isOk()) - .andExpect(header().string(AbstractController.X_DEPRECATED_API, "1") - ); - } -} diff --git a/src/test/java/de/thm/arsnova/controller/SessionControllerTest.java b/src/test/java/de/thm/arsnova/controller/SessionControllerTest.java deleted file mode 100644 index 3f219d532f5ed1b9f961c463d68563275095e338..0000000000000000000000000000000000000000 --- a/src/test/java/de/thm/arsnova/controller/SessionControllerTest.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * This file is part of ARSnova Backend. - * Copyright (C) 2012-2017 The ARSnova Team - * - * 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.controller; - -import de.thm.arsnova.services.StubUserService; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -public class SessionControllerTest extends AbstractControllerTest { - - @Autowired - private StubUserService userService; - - @Autowired - private SessionController sessionController; - - private MockMvc mockMvc; - - @Autowired - private WebApplicationContext webApplicationContext; - - @Before - public void startup() { - mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); - SecurityContextHolder.clearContext(); - } - - @After - public void cleanup() { - SecurityContextHolder.clearContext(); - userService.setUserAuthenticated(false); - } - - @Test - public void testShouldNotGetUnknownSession() throws Exception { - setAuthenticated(true, "ptsr00"); - - mockMvc.perform(get("/session/00000000").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()); - } - - @Test - public void testShouldNotGetUnknownSessionIfUnauthorized() throws Exception { - setAuthenticated(false, "ptsr00"); - - mockMvc.perform(get("/session/00000000").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isUnauthorized()); - } - - @Test - public void testShouldCreateSessionIfUnauthorized() throws Exception { - setAuthenticated(false, "ptsr00"); - - mockMvc.perform( - post("/session/") - .accept(MediaType.APPLICATION_JSON) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"keyword\":12345678}") - ) - .andExpect(status().isUnauthorized()); - } - - @Test - public void testShouldNotReturnMySessionsIfUnauthorized() throws Exception { - setAuthenticated(false, "ptsr00"); - - mockMvc.perform(get("/session/").param("ownedonly", "true").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isUnauthorized()); - } - - @Test - public void testShouldNotReturnMyVisitedSessionsIfUnauthorized() throws Exception { - setAuthenticated(false, "ptsr00"); - - mockMvc.perform(get("/session/").param("visitedonly", "true").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isUnauthorized()); - } - - @Test - public void testShouldShowUnimplementedIfNoFlagIsSet() throws Exception { - setAuthenticated(false, "ptsr00"); - - mockMvc.perform(get("/session/").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotImplemented()); - } - - @Test - public void testShouldReturnActiveUserCount() throws Exception { - setAuthenticated(false, "ptsr00"); - - mockMvc.perform(get("/session/12345678/activeusercount").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) - .andExpect(content().string("0")); - } - - @Test - public void testShouldReturnXDeprecatedApiHeaderForActiveUserCount() throws Exception { - setAuthenticated(false, "ptsr00"); - - mockMvc.perform(get("/session/12345678/activeusercount").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(header().string(AbstractController.X_DEPRECATED_API, "1")); - } - - @Test - public void testShouldEndInForbidden() throws Exception { - setAuthenticated(true, "ptsr00"); - - mockMvc.perform( - put("/session/12345678") - .content("{\"keyword\":\"12345678\", \"name\":\"Testsession\"}, \"shortName\":\"TS\", \"creator\":\"ptsr00\", \"active\":true") - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - - setAuthenticated(true, "other"); - - mockMvc.perform(delete("/session/12345678")).andExpect(status().isForbidden()); - } -} diff --git a/src/test/java/de/thm/arsnova/controller/StatisticsControllerTest.java b/src/test/java/de/thm/arsnova/controller/StatisticsControllerTest.java deleted file mode 100644 index b692083fa666a552fcaf568ba5f040b210b441ed..0000000000000000000000000000000000000000 --- a/src/test/java/de/thm/arsnova/controller/StatisticsControllerTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * This file is part of ARSnova Backend. - * Copyright (C) 2012-2017 The ARSnova Team - * - * 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.controller; - -import org.hamcrest.Matchers; -import org.junit.Before; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -public class StatisticsControllerTest extends AbstractControllerTest { - - @Autowired - private StatisticsController statisticsController; - - private MockMvc mockMvc; - - @Autowired - private WebApplicationContext webApplicationContext; - - @Before - public void setup() { - mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); - } - - @Test - public void testShouldGetCurrentOnlineUsers() throws Exception { - mockMvc.perform(get("/statistics/activeusercount").accept(MediaType.TEXT_PLAIN)) - .andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith("text/plain")); - } - - @Test - public void testShouldSendXDeprecatedApiForGetCurrentOnlineUsers() throws Exception { - mockMvc.perform(get("/statistics/activeusercount").accept(MediaType.TEXT_PLAIN)) - .andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith("text/plain")) - .andExpect(header().string(AbstractController.X_DEPRECATED_API,"1")); - } - - @Test - public void testShouldGetSessionCount() throws Exception { - mockMvc.perform(get("/statistics/sessioncount").accept(MediaType.TEXT_PLAIN)) - .andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith("text/plain")) - .andExpect(content().string(Matchers.greaterThanOrEqualTo("0"))); - } - - @Test - public void testShouldSendXDeprecatedApiForGetSessionCount() throws Exception { - mockMvc.perform(get("/statistics/sessioncount").accept(MediaType.TEXT_PLAIN)) - .andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith("text/plain")) - .andExpect(header().string(AbstractController.X_DEPRECATED_API,"1")); - } - - @Test - public void testShouldGetStatistics() throws Exception { - mockMvc.perform(get("/statistics").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$.answers").exists()) - .andExpect(jsonPath("$.questions").exists()) - .andExpect(jsonPath("$.openSessions").exists()) - .andExpect(jsonPath("$.closedSessions").exists()) - .andExpect(jsonPath("$.activeUsers").exists()) - .andExpect(jsonPath("$.interposedQuestions").exists()); - } - - @Test - public void testShouldGetCacheControlHeaderForStatistics() throws Exception { - mockMvc.perform(get("/statistics").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(header().string("cache-control", "public, max-age=60")); - } -} diff --git a/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java b/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java deleted file mode 100644 index aedcf8d648bae20965845bfe2712ec0808c5825a..0000000000000000000000000000000000000000 --- a/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java +++ /dev/null @@ -1,748 +0,0 @@ -/* - * This file is part of ARSnova Backend. - * Copyright (C) 2012-2017 The ARSnova Team - * - * 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.dao; - -import com.fourspaces.couchdb.View; -import de.thm.arsnova.connector.model.Course; -import de.thm.arsnova.domain.CourseScore; -import de.thm.arsnova.entities.*; -import de.thm.arsnova.entities.transport.ImportExportSession; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -@Profile("test") -@Service("databaseDao") -public class StubDatabaseDao implements IDatabaseDao { - - private static Map<String, Session> stubSessions = new ConcurrentHashMap<>(); - private static Map<String, Feedback> stubFeedbacks = new ConcurrentHashMap<>(); - private static Map<String, List<Question>> stubQuestions = new ConcurrentHashMap<>(); - private static Map<String, User> stubUsers = new ConcurrentHashMap<>(); - - public InterposedQuestion interposedQuestion; - - public StubDatabaseDao() { - fillWithDummySessions(); - fillWithDummyFeedbacks(); - fillWithDummyQuestions(); - } - - public void cleanupTestData() { - stubSessions.clear(); - stubFeedbacks.clear(); - stubQuestions.clear(); - stubUsers.clear(); - - fillWithDummySessions(); - fillWithDummyFeedbacks(); - fillWithDummyQuestions(); - } - - private void fillWithDummySessions() { - Session session = new Session(); - session.setActive(true); - session.setCreator("ptsr00"); - session.setKeyword("12345678"); - session.setName("TestSession1"); - session.setShortName("TS1"); - - stubSessions.put("12345678", session); - - session = new Session(); - session.setActive(true); - session.setCreator("ptsr00"); - session.setKeyword("87654321"); - session.setName("TestSession2"); - session.setShortName("TS2"); - - stubSessions.put("87654321", session); - - session = new Session(); - session.setActive(true); - session.setCreator("ptsr00"); - session.setKeyword("18273645"); - session.setName("TestSession2"); - session.setShortName("TS3"); - - stubSessions.put("18273645", session); - } - - private void fillWithDummyFeedbacks() { - stubFeedbacks.put("12345678", new Feedback(0, 0, 0, 0)); - stubFeedbacks.put("87654321", new Feedback(2, 3, 5, 7)); - stubFeedbacks.put("18273645", new Feedback(2, 3, 5, 11)); - } - - private void fillWithDummyQuestions() { - List<Question> questions = new ArrayList<>(); - questions.add(new Question()); - stubQuestions.put("12345678", questions); - } - - @Override - public Session saveSession(User user, Session session) { - stubSessions.put(session.getKeyword(), session); - return session; - } - - @Override - public boolean sessionKeyAvailable(String keyword) { - return (stubSessions.get(keyword) == null); - } - - @Override - public void log(String event, Map<String, Object> payload, LogEntry.LogLevel level) { - // TODO Auto-generated method stub - } - - @Override - public void log(String event, Map<String, Object> payload) { - // TODO Auto-generated method stub - } - - @Override - public void log(String event, LogEntry.LogLevel level, Object... rawPayload) { - // TODO Auto-generated method stub - } - - @Override - public void log(String event, Object... rawPayload) { - // TODO Auto-generated method stub - } - - @Override - public Session getSessionFromKeyword(String keyword) { - return stubSessions.get(keyword); - } - - @Override - public Question saveQuestion(Session session, Question question) { - List<Question> questions = stubQuestions.get(session.getKeyword()); - questions.add(question); - stubQuestions.put(session.get_id(), questions); - - return question; - } - - @Override - public Question getQuestion(String id) { - // Simply ... no such question ;-) - return null; - } - - @Override - public int getSkillQuestionCount(Session session) { - return stubQuestions.get(session.getKeyword()).size(); - } - - @Override - public List<Session> getMySessions(User user, final int start, final int limit) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Session> getSessionsForUsername(String username, final int start, final int limit) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Session> getPublicPoolSessions() { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<SessionInfo> getPublicPoolSessionsInfo() { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Session> getMyPublicPoolSessions(User user) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<SessionInfo> getMyPublicPoolSessionsInfo(final User user) { - // TODO Auto-generated method stub - return null; - } - - @Override - public LoggedIn registerAsOnlineUser(User u, Session s) { - stubUsers.put(s.getKeyword(), u); - return new LoggedIn(); - } - - @Override - public Session updateSessionOwnerActivity(Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Answer getMyAnswer(User user, String questionId, int piRound) { - // TODO Auto-generated method stub - return null; - } - - @Override - public int getAnswerCount(Question question, int piRound) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public List<Answer> getFreetextAnswers(String questionId, final int start, final int limit) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Answer> getMyAnswers(User user, Session session) { - return new ArrayList<>(); - } - - @Override - public int getTotalAnswerCount(String sessionKey) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public int getInterposedCount(String sessionKey) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public List<InterposedQuestion> getInterposedQuestions(Session session, final int start, final int limit) { - // TODO Auto-generated method stub - return null; - } - - @Override - public InterposedQuestion saveQuestion(Session session, InterposedQuestion question, User user) { - // TODO Auto-generated method stub - return null; - } - - @Override - public InterposedQuestion getInterposedQuestion(String questionId) { - return this.interposedQuestion; - } - - @Override - public void markInterposedQuestionAsRead(InterposedQuestion question) { - this.interposedQuestion.setRead(true); - } - - @Override - public List<Session> getMyVisitedSessions(User user, final int start, final int limit) { - // TODO Auto-generated method stub - return null; - } - - @Override - public InterposedReadingCount getInterposedReadingCount(Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<String> getQuestionIds(Session session, User user) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<String> getUnAnsweredQuestionIds(Session session, User user) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Question updateQuestion(Question question) { - // TODO Auto-generated method stub - return null; - } - - @Override - public int deleteQuestionWithAnswers(Question question) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public int deleteAnswers(Question question) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public Answer updateAnswer(Answer answer) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Session getSessionFromId(String sessionId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public void deleteAnswer(String answerId) { - // TODO Auto-generated method stub - } - - @Override - public void deleteInterposedQuestion(InterposedQuestion question) { - // TODO Auto-generated method stub - } - - @Override - public List<Session> getCourseSessions(List<Course> courses) { - return null; - } - - @Override - public Session updateSession(Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Session changeSessionCreator(Session session, final String newCreator) { - // TODO Auto-generated method stub - return null; - } - - @Override - public int[] deleteSession(Session session) { - return new int[] { 0,0 }; - } - - @Override - public int[] deleteInactiveGuestSessions(long lastActivityBefore) { - return new int[] { 0,0 }; - } - - @Override - public int deleteInactiveGuestVisitedSessionLists(long lastActivityBefore) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public int[] deleteAllQuestionsWithAnswers(Session session) { - return new int[] { 0, 0 }; - } - - @Override - public int getLectureQuestionCount(Session session) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public int getFlashcardCount(Session session) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public int getPreparationQuestionCount(Session session) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public int countLectureQuestionAnswers(Session session) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public int countPreparationQuestionAnswers(Session session) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public int[] deleteAllLectureQuestionsWithAnswers(Session session) { - return new int[] { 0, 0 }; - } - - @Override - public int[] deleteAllFlashcardsWithAnswers(Session session) { - return new int[] { 0, 0 }; - } - - @Override - public int[] deleteAllPreparationQuestionsWithAnswers(Session session) { - return new int[] { 0, 0 }; - } - - @Override - public List<String> getUnAnsweredLectureQuestionIds(Session session, User user) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<String> getUnAnsweredPreparationQuestionIds(Session session, User user) { - // TODO Auto-generated method stub - return null; - } - - @Override - public int deleteAllInterposedQuestions(Session session) { - return 0; - } - - @Override - public void publishQuestions(Session session, boolean publish, List<Question> questions) { - // TODO Auto-generated method stub - - } - - @Override - public List<Question> publishAllQuestions(Session session, boolean publish) { - // TODO Auto-generated method stub - return null; - } - - @Override - public int deleteAllQuestionsAnswers(Session session) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public DbUser createOrUpdateUser(DbUser user) { - // TODO Auto-generated method stub - return null; - } - - @Override - public DbUser getUser(String username) { - // TODO Auto-generated method stub - return null; - } - - @Override - public CourseScore getLearningProgress(Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean deleteUser(DbUser dbUser) { - // TODO Auto-generated method stub - return false; - } - - @Override - public int deleteInactiveUsers(long lastActivityBefore) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public List<InterposedQuestion> getInterposedQuestions(Session session, User user, final int start, final int limit) { - // TODO Auto-generated method stub - return null; - } - - @Override - public int deleteAllInterposedQuestions(Session session, User user) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public InterposedReadingCount getInterposedReadingCount(Session session, User user) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<SessionInfo> getMySessionsInfo(User user, final int start, final int limit) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<SessionInfo> getMyVisitedSessionsInfo(User currentUser, final int start, final int limit) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Session> getVisitedSessionsForUsername(String username, final int start, final int limit) { - // TODO Auto-generated method stub - return null; - } - - @Override - public int deleteAllPreparationAnswers(Session session) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public int deleteAllLectureAnswers(Session session) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public int getAbstentionAnswerCount(String questionId) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public SessionInfo importSession(User user, ImportExportSession importSession) { - // TODO Auto-generated method stub - return null; - } - - @Override - public ImportExportSession exportSession(String sessionkey, Boolean withAnswer, Boolean withFeedbackQuestions) { - // TODO Auto.generated method stub - return null; - } - - @Override - public List<Question> getSkillQuestionsForUsers(Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Question> getSkillQuestionsForTeachers(Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Question> getLectureQuestionsForUsers(Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Question> getLectureQuestionsForTeachers(Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Question> getFlashcardsForUsers(Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Question> getFlashcardsForTeachers(Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Question> getPreparationQuestionsForUsers(Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Question> getPreparationQuestionsForTeachers(Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Question> getAllSkillQuestions(Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Answer> getAnswers(Question question, int piRound) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Answer> getAnswers(Question question) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Answer saveAnswer(Answer answer, User user, Question question, Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Statistics getStatistics() { - final Statistics stats = new Statistics(); - stats.setOpenSessions(3); - stats.setClosedSessions(0); - stats.setLectureQuestions(0); - stats.setAnswers(0); - stats.setInterposedQuestions(0); - return stats; - } - - @Override - public List<String> getSubjects(Session session, String questionVariant) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<String> getQuestionIdsBySubject(Session session, String questionVariant, String subject) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Question> getQuestionsByIds(List<String> ids, Session session) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Answer> getAllAnswers(Question question) { - // TODO Auto-generated method stub - return null; - } - - @Override - public int getTotalAnswerCountByQuestion(Question question) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public void resetQuestionsRoundState(Session session, - List<Question> questions) { - // TODO Auto-generated method stub - - } - - @Override - public void setVotingAdmissions(Session session, boolean disableVoting, List<Question> questions) { - // TODO Auto-generated method stub - - } - - @Override - public List<Question> setVotingAdmissionForAllQuestions(Session session, boolean disableVoting) { - // TODO Auto-generated method stub - return null; - } - - @Override - public <T> T getObjectFromId(String documentId, Class<T> klass) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Motd> getAdminMotds() { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Motd> getMotdsForAll() { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Motd> getMotdsForLoggedIn() { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Motd> getMotdsForTutors() { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Motd> getMotdsForStudents() { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Motd> getMotdsForSession(final String sessionkey) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List<Motd> getMotds(View view) { - return null; - } - - @Override - public Motd getMotdByKey(String key) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Motd createOrUpdateMotd(Motd motd) { - // TODO Auto-generated method stub - return null; - } - - @Override - public void deleteMotd(Motd motd) { - // TODO Auto-generated method stub - } - - @Override - public MotdList getMotdListForUser(final String username) { - // TODO Auto-generated method stub - return null; - } - - @Override - public MotdList createOrUpdateMotdList(MotdList motdlist) { - // TODO Auto-generated method stub - return null; - } -} diff --git a/src/test/java/de/thm/arsnova/domain/PointBasedLearningProgressTest.java b/src/test/java/de/thm/arsnova/domain/PointBasedLearningProgressTest.java index bfad95b362dba4174ad3cd34c26b014dec1fa4b8..6f42527dc61881ea06d533e29c7932e85c464317 100644 --- a/src/test/java/de/thm/arsnova/domain/PointBasedLearningProgressTest.java +++ b/src/test/java/de/thm/arsnova/domain/PointBasedLearningProgressTest.java @@ -17,10 +17,10 @@ */ package de.thm.arsnova.domain; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.TestUser; import de.thm.arsnova.entities.User; import de.thm.arsnova.entities.transport.LearningProgressValues; +import de.thm.arsnova.persistance.SessionStatisticsRepository; import org.junit.Before; import org.junit.Test; @@ -50,7 +50,7 @@ public class PointBasedLearningProgressTest { @Before public void setUp() { this.courseScore = new CourseScore(); - IDatabaseDao db = mock(IDatabaseDao.class); + SessionStatisticsRepository db = mock(SessionStatisticsRepository.class); when(db.getLearningProgress(null)).thenReturn(courseScore); this.lp = new PointBasedLearningProgress(db); } diff --git a/src/test/java/de/thm/arsnova/domain/QuestionBasedLearningProgressTest.java b/src/test/java/de/thm/arsnova/domain/QuestionBasedLearningProgressTest.java index 7e029a6cb9a78144a76644ec018daca03f37b2c7..8adf79257cd12f28dafe08d6f6e91f6524628e3e 100644 --- a/src/test/java/de/thm/arsnova/domain/QuestionBasedLearningProgressTest.java +++ b/src/test/java/de/thm/arsnova/domain/QuestionBasedLearningProgressTest.java @@ -17,10 +17,10 @@ */ package de.thm.arsnova.domain; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.TestUser; import de.thm.arsnova.entities.User; import de.thm.arsnova.entities.transport.LearningProgressValues; +import de.thm.arsnova.persistance.SessionStatisticsRepository; import org.junit.Before; import org.junit.Test; @@ -50,7 +50,7 @@ public class QuestionBasedLearningProgressTest { @Before public void setUp() { this.courseScore = new CourseScore(); - IDatabaseDao db = mock(IDatabaseDao.class); + SessionStatisticsRepository db = mock(SessionStatisticsRepository.class); when(db.getLearningProgress(null)).thenReturn(courseScore); this.lp = new QuestionBasedLearningProgress(db); } diff --git a/src/test/java/de/thm/arsnova/entities/QuestionTest.java b/src/test/java/de/thm/arsnova/entities/ContentTest.java similarity index 96% rename from src/test/java/de/thm/arsnova/entities/QuestionTest.java rename to src/test/java/de/thm/arsnova/entities/ContentTest.java index 57569e6f220f4b8463bd62eb11e6e9f336069864..fc6297c8d5668b825f563e1cae5550dae3d38189 100644 --- a/src/test/java/de/thm/arsnova/entities/QuestionTest.java +++ b/src/test/java/de/thm/arsnova/entities/ContentTest.java @@ -23,7 +23,7 @@ import java.util.ArrayList; import static org.junit.Assert.assertEquals; -public class QuestionTest { +public class ContentTest { @SuppressWarnings("serial") @Test @@ -36,7 +36,7 @@ public class QuestionTest { p2.setText("No"); p2.setCorrect(false); p2.setValue(-10); - Question q = new Question(); + Content q = new Content(); q.setQuestionType("yesno"); q.setPossibleAnswers(new ArrayList<PossibleAnswer>() {{ add(p1); @@ -62,7 +62,7 @@ public class QuestionTest { p2.setText("No"); p2.setCorrect(false); p2.setValue(-10); - Question q = new Question(); + Content q = new Content(); q.setAbstention(true); q.setPossibleAnswers(new ArrayList<PossibleAnswer>() {{ add(p1); @@ -89,7 +89,7 @@ public class QuestionTest { p3.setText("Maybe"); p3.setCorrect(true); p3.setValue(10); - Question q = new Question(); + Content q = new Content(); q.setQuestionType("mc"); q.setPossibleAnswers(new ArrayList<PossibleAnswer>() {{ add(p1); @@ -134,7 +134,7 @@ public class QuestionTest { p4.setText("1;1"); p4.setCorrect(true); p4.setValue(10); - Question q = new Question(); + Content q = new Content(); q.setQuestionType("grid"); q.setPossibleAnswers(new ArrayList<PossibleAnswer>() {{ add(p1); diff --git a/src/test/java/de/thm/arsnova/services/FeedbackServiceTest.java b/src/test/java/de/thm/arsnova/services/FeedbackServiceTest.java deleted file mode 100644 index b751c9f9fbeaee564cd5d9160442c1c435a26d9c..0000000000000000000000000000000000000000 --- a/src/test/java/de/thm/arsnova/services/FeedbackServiceTest.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * This file is part of ARSnova Backend. - * Copyright (C) 2012-2017 The ARSnova Team - * - * 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.services; - -import de.thm.arsnova.config.AppConfig; -import de.thm.arsnova.config.TestAppConfig; -import de.thm.arsnova.config.TestSecurityConfig; -import de.thm.arsnova.dao.StubDatabaseDao; -import de.thm.arsnova.entities.TestUser; -import de.thm.arsnova.exceptions.NoContentException; -import de.thm.arsnova.exceptions.NotFoundException; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -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 static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -@RunWith(SpringJUnit4ClassRunner.class) -@WebAppConfiguration -@ContextConfiguration(classes = {AppConfig.class, TestAppConfig.class, TestSecurityConfig.class}) -@ActiveProfiles("test") -public class FeedbackServiceTest { - - @Autowired - private IFeedbackService feedbackService; - - @Autowired - private StubUserService userService; - - @Autowired - private StubDatabaseDao databaseDao; - - @Before - public void setup() { - userService.setUserAuthenticated(false); - - feedbackService.saveFeedback("87654321", 0, new TestUser("testuser01")); - feedbackService.saveFeedback("87654321", 0, new TestUser("testuser02")); - feedbackService.saveFeedback("87654321", 1, new TestUser("testuser11")); - feedbackService.saveFeedback("87654321", 1, new TestUser("testuser12")); - feedbackService.saveFeedback("87654321", 1, new TestUser("testuser13")); - feedbackService.saveFeedback("87654321", 2, new TestUser("testuser21")); - feedbackService.saveFeedback("87654321", 2, new TestUser("testuser22")); - feedbackService.saveFeedback("87654321", 2, new TestUser("testuser23")); - feedbackService.saveFeedback("87654321", 2, new TestUser("testuser24")); - feedbackService.saveFeedback("87654321", 2, new TestUser("testuser25")); - feedbackService.saveFeedback("87654321", 3, new TestUser("testuser31")); - feedbackService.saveFeedback("87654321", 3, new TestUser("testuser32")); - feedbackService.saveFeedback("87654321", 3, new TestUser("testuser33")); - feedbackService.saveFeedback("87654321", 3, new TestUser("testuser34")); - feedbackService.saveFeedback("87654321", 3, new TestUser("testuser35")); - feedbackService.saveFeedback("87654321", 3, new TestUser("testuser36")); - feedbackService.saveFeedback("87654321", 3, new TestUser("testuser37")); - - feedbackService.saveFeedback("18273645", 0, new TestUser("testuser01")); - feedbackService.saveFeedback("18273645", 0, new TestUser("testuser02")); - feedbackService.saveFeedback("18273645", 1, new TestUser("testuser11")); - feedbackService.saveFeedback("18273645", 1, new TestUser("testuser12")); - feedbackService.saveFeedback("18273645", 1, new TestUser("testuser13")); - feedbackService.saveFeedback("18273645", 2, new TestUser("testuser21")); - feedbackService.saveFeedback("18273645", 2, new TestUser("testuser22")); - feedbackService.saveFeedback("18273645", 2, new TestUser("testuser23")); - feedbackService.saveFeedback("18273645", 2, new TestUser("testuser24")); - feedbackService.saveFeedback("18273645", 2, new TestUser("testuser25")); - feedbackService.saveFeedback("18273645", 3, new TestUser("testuser31")); - feedbackService.saveFeedback("18273645", 3, new TestUser("testuser32")); - feedbackService.saveFeedback("18273645", 3, new TestUser("testuser33")); - feedbackService.saveFeedback("18273645", 3, new TestUser("testuser34")); - feedbackService.saveFeedback("18273645", 3, new TestUser("testuser35")); - feedbackService.saveFeedback("18273645", 3, new TestUser("testuser36")); - feedbackService.saveFeedback("18273645", 3, new TestUser("testuser37")); - feedbackService.saveFeedback("18273645", 3, new TestUser("testuser38")); - feedbackService.saveFeedback("18273645", 3, new TestUser("testuser39")); - feedbackService.saveFeedback("18273645", 3, new TestUser("testuser310")); - feedbackService.saveFeedback("18273645", 3, new TestUser("testuser311")); - - } - - @After - public void cleanup() { - databaseDao.cleanupTestData(); - userService.setUserAuthenticated(false); - } - - @Test(expected = NotFoundException.class) - public void testShouldFindFeedbackForNonExistantSession() { - userService.setUserAuthenticated(true); - feedbackService.getFeedback("00000000"); - } - - @Test - public void testShouldReturnFeedback() { - userService.setUserAuthenticated(true); - assertNotNull(feedbackService.getFeedback("87654321")); - assertEquals(2, (int) feedbackService.getFeedback("87654321").getValues().get(0)); - assertEquals(3, (int) feedbackService.getFeedback("87654321").getValues().get(1)); - assertEquals(5, (int) feedbackService.getFeedback("87654321").getValues().get(2)); - assertEquals(7, (int) feedbackService.getFeedback("87654321").getValues().get(3)); - } - - @Test(expected = NotFoundException.class) - public void testShouldFindFeedbackCountForNonExistantSession() { - userService.setUserAuthenticated(true); - feedbackService.getFeedbackCount("00000000"); - } - - @Test - public void testShouldReturnFeedbackCount() { - userService.setUserAuthenticated(true); - assertEquals(17, feedbackService.getFeedbackCount("87654321")); - } - - @Test(expected = NotFoundException.class) - public void testShouldFindAverageFeedbackForNonExistantSession() { - userService.setUserAuthenticated(true); - feedbackService.getAverageFeedback("00000000"); - } - - @Test - public void testShouldReturnZeroFeedbackCountForNoFeedbackAtAll() { - userService.setUserAuthenticated(true); - assertEquals(0, feedbackService.getFeedbackCount("12345678")); - } - - @Test(expected = NoContentException.class) - public void testShouldReturnAverageFeedbackForNoFeedbackAtAll() { - userService.setUserAuthenticated(true); - feedbackService.getAverageFeedback("12345678"); - } - - @Test - public void testShouldReturnAverageFeedbackRounded() { - userService.setUserAuthenticated(true); - assertEquals(2, feedbackService.getAverageFeedbackRounded("18273645")); - } - - @Test - public void testShouldReturnAverageFeedbackNotRounded() { - userService.setUserAuthenticated(true); - assertEquals(2.1904, feedbackService.getAverageFeedback("18273645"), 0.001); - } -} diff --git a/src/test/java/de/thm/arsnova/services/QuestionServiceTest.java b/src/test/java/de/thm/arsnova/services/QuestionServiceTest.java deleted file mode 100644 index 5482ba33d035b60292329dec2fc83c8192c0a8ff..0000000000000000000000000000000000000000 --- a/src/test/java/de/thm/arsnova/services/QuestionServiceTest.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * This file is part of ARSnova Backend. - * Copyright (C) 2012-2017 The ARSnova Team - * - * 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.services; - -import de.thm.arsnova.config.AppConfig; -import de.thm.arsnova.config.TestAppConfig; -import de.thm.arsnova.config.TestSecurityConfig; -import de.thm.arsnova.dao.StubDatabaseDao; -import de.thm.arsnova.entities.InterposedQuestion; -import de.thm.arsnova.entities.Question; -import de.thm.arsnova.exceptions.NotFoundException; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -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 java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.*; - -@RunWith(SpringJUnit4ClassRunner.class) -@WebAppConfiguration -@ContextConfiguration(classes = {AppConfig.class, TestAppConfig.class, TestSecurityConfig.class}) -@ActiveProfiles("test") -public class QuestionServiceTest { - - @Autowired - private IQuestionService questionService; - - @Autowired - private StubUserService userService; - - @Autowired - private StubDatabaseDao databaseDao; - - private void setAuthenticated(final boolean isAuthenticated, final String username) { - if (isAuthenticated) { - final List<GrantedAuthority> ga = new ArrayList<>(); - final UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, "secret", ga); - SecurityContextHolder.getContext().setAuthentication(token); - userService.setUserAuthenticated(isAuthenticated, username); - } else { - userService.setUserAuthenticated(isAuthenticated); - } - } - - @Before - public void startup() { - SecurityContextHolder.clearContext(); - } - - @After - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test(expected = AuthenticationCredentialsNotFoundException.class) - public void testShouldNotReturnQuestionsIfNotAuthenticated() { - setAuthenticated(false, "nobody"); - questionService.getSkillQuestions("12345678"); - } - - @Test(expected = NotFoundException.class) - public void testShouldFindQuestionsForNonExistantSession() { - setAuthenticated(true, "ptsr00"); - questionService.getSkillQuestions("00000000"); - } - - @Test - public void testShouldFindQuestions() { - setAuthenticated(true, "ptsr00"); - assertEquals(1, questionService.getSkillQuestionCount("12345678")); - } - - @Test - public void testShouldMarkInterposedQuestionAsReadIfSessionCreator() throws Exception { - setAuthenticated(true, "ptsr00"); - final InterposedQuestion theQ = new InterposedQuestion(); - theQ.setRead(false); - theQ.set_id("the internal id"); - theQ.setSessionId("12345678"); - databaseDao.interposedQuestion = theQ; - - questionService.readInterposedQuestion(theQ.get_id()); - - assertTrue(theQ.isRead()); - } - - @Test - public void testShouldNotMarkInterposedQuestionAsReadIfRegularUser() throws Exception { - setAuthenticated(true, "regular user"); - final InterposedQuestion theQ = new InterposedQuestion(); - theQ.setRead(false); - theQ.set_id("the internal id"); - theQ.setSessionId("12345678"); - theQ.setCreator("regular user"); - databaseDao.interposedQuestion = theQ; - - questionService.readInterposedQuestion(theQ.get_id()); - - assertFalse(theQ.isRead()); - } - - @Test(expected = AccessDeniedException.class) - public void testShouldSaveQuestion() throws Exception{ - setAuthenticated(true, "regular user"); - final Question question = new Question(); - question.setSessionKeyword("12345678"); - question.setQuestionVariant("freetext"); - questionService.saveQuestion(question); - } - - @Test(expected = AccessDeniedException.class) - public void testShouldNotDeleteQuestion() throws Exception{ - setAuthenticated(true, "otheruser"); - questionService.deleteQuestion("a1a2a3a4a5a6a7a8a9a"); - } - - @Test(expected = AccessDeniedException.class) - public void testShouldNotDeleteInterposedQuestion() throws Exception{ - setAuthenticated(true, "otheruser"); - questionService.deleteInterposedQuestion("a1a2a3a4a5a6a7a8a9a"); - } -} diff --git a/src/test/java/de/thm/arsnova/services/SessionServiceTest.java b/src/test/java/de/thm/arsnova/services/SessionServiceTest.java deleted file mode 100644 index de86f5c5a0821da44c3e856db049749ec89b99b8..0000000000000000000000000000000000000000 --- a/src/test/java/de/thm/arsnova/services/SessionServiceTest.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * This file is part of ARSnova Backend. - * Copyright (C) 2012-2017 The ARSnova Team - * - * 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.services; - -import de.thm.arsnova.config.AppConfig; -import de.thm.arsnova.config.TestAppConfig; -import de.thm.arsnova.config.TestSecurityConfig; -import de.thm.arsnova.dao.IDatabaseDao; -import de.thm.arsnova.dao.StubDatabaseDao; -import de.thm.arsnova.entities.Session; -import de.thm.arsnova.exceptions.NotFoundException; -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.aop.framework.Advised; -import org.springframework.aop.support.AopUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -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.test.util.ReflectionTestUtils; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; - -import static org.junit.Assert.*; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.*; - -@RunWith(SpringJUnit4ClassRunner.class) -@WebAppConfiguration -@ContextConfiguration(classes = {AppConfig.class, TestAppConfig.class, TestSecurityConfig.class}) -@ActiveProfiles("test") -public class SessionServiceTest { - - @Autowired - private ISessionService sessionService; - - @Autowired - private StubUserService userService; - - @Autowired - private StubDatabaseDao databaseDao; - - private void setAuthenticated(final boolean isAuthenticated, final String username) { - if (isAuthenticated) { - final List<GrantedAuthority> ga = new ArrayList<>(); - final UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, "secret", ga); - SecurityContextHolder.getContext().setAuthentication(token); - userService.setUserAuthenticated(isAuthenticated, username); - } else { - userService.setUserAuthenticated(isAuthenticated); - } - } - - @Before - public void startup() { - SecurityContextHolder.clearContext(); - } - - @After - public void cleanup() { - databaseDao.cleanupTestData(); - SecurityContextHolder.clearContext(); - userService.setUserAuthenticated(false); - } - - @Test - public void testShouldGenerateSessionKeyword() { - assertTrue(sessionService.generateKeyword().matches("^[0-9]{8}$")); - } - - @Test(expected = NotFoundException.class) - public void testShouldNotFindNonExistantSession() { - setAuthenticated(true, "ptsr00"); - sessionService.getSession("00000000"); - } - - @Test(expected = AuthenticationCredentialsNotFoundException.class) - public void testShouldNotReturnSessionIfUnauthorized() { - setAuthenticated(false, null); - sessionService.getSession("12345678"); - } - - @Test(expected = AuthenticationCredentialsNotFoundException.class) - public void testShouldNotSaveSessionIfUnauthorized() { - setAuthenticated(false, null); - - final Session session = new Session(); - session.setActive(true); - session.setCreator("ptsr00"); - session.setKeyword("11111111"); - session.setName("TestSessionX"); - session.setShortName("TSX"); - sessionService.saveSession(session); - - setAuthenticated(true, "ptsr00"); - - assertNull(sessionService.getSession("11111111")); - } - - @Test - public void testShouldSaveSession() { - setAuthenticated(true, "ptsr00"); - - final Session session = new Session(); - session.setActive(true); - session.setCreator("ptsr00"); - session.setKeyword("11111111"); - session.setName("TestSessionX"); - session.setShortName("TSX"); - sessionService.saveSession(session); - assertNotNull(sessionService.getSession("11111111")); - } - - @Test(expected = AccessDeniedException.class) - public void testShouldUpdateSession() { - setAuthenticated(true, "ptsr00"); - - final Session session = new Session(); - session.setActive(true); - session.setCreator("ptsr00"); - session.setKeyword("11111111"); - session.setName("TestSessionX"); - session.setShortName("TSX"); - sessionService.saveSession(session); - - setAuthenticated(true, "other"); - sessionService.updateSession(session.getKeyword(), session); - } - - @Test - @Ignore("Test fails on JDK 8 (ClassCastException)") - public void testShouldDeleteAllSessionData() { - /* FIXME: fails with ClassCastException on JDK 8 */ - final IDatabaseDao tempDatabase = (IDatabaseDao) ReflectionTestUtils.getField(getTargetObject(sessionService), "databaseDao"); - try { - setAuthenticated(true, "ptsr00"); - - final Session session = new Session(); - session.setKeyword("12345678"); - session.setCreator(userService.getCurrentUser().getUsername()); - - final IDatabaseDao mockDatabase = mock(IDatabaseDao.class); - when(mockDatabase.getSessionFromKeyword(anyString())).thenReturn(session); - ReflectionTestUtils.setField(getTargetObject(sessionService), "databaseDao", mockDatabase); - - sessionService.deleteSession(session.getKeyword()); - - verify(mockDatabase).deleteSession(session); - } finally { - ReflectionTestUtils.setField(getTargetObject(sessionService), "databaseDao", tempDatabase); - } - } - - @Test(expected = AuthenticationCredentialsNotFoundException.class) - public void testShouldNotDeleteSessionIfUnauthorized() { - setAuthenticated(false, "nobody"); - sessionService.deleteSession("12345678"); - } - - @Test(expected = AccessDeniedException.class) - public void testShouldNotDeleteSessionIfNotOwner() { - setAuthenticated(true, "anybody"); - sessionService.deleteSession("12345678"); - } - - @Test - public void testShouldCompareSessionByName() { - final Session sessionA = new Session(); - sessionA.setName("TestSessionA"); - sessionA.setShortName("TSA"); - - final Session sessionB = new Session(); - sessionB.setName("TestSessionB"); - sessionB.setShortName("TSB"); - - final Comparator<Session> comp = new SessionService.SessionNameComparator(); - assertTrue(comp.compare(sessionA, sessionB) < 0); - } - - @Test - public void testShouldCompareSessionByShortName() { - final Session sessionA = new Session(); - sessionA.setName("TestSessionA"); - sessionA.setShortName("TSA"); - - final Session sessionB = new Session(); - sessionB.setName("TestSessionB"); - sessionB.setShortName("TSB"); - - final Comparator<Session> comp = new SessionService.SessionShortNameComparator(); - assertTrue(comp.compare(sessionA, sessionB) < 0); - } - - @SuppressWarnings("unchecked") - public static <T> T getTargetObject(final Object proxy) { - if (AopUtils.isJdkDynamicProxy(proxy)) { - try { - return (T) getTargetObject(((Advised) proxy).getTargetSource().getTarget()); - } catch (final Exception e) { - throw new RuntimeException("Failed to unproxy target.", e); - } - } - return (T) proxy; - } -} diff --git a/src/test/java/de/thm/arsnova/services/UserServiceTest.java b/src/test/java/de/thm/arsnova/services/UserServiceTest.java index 78eb90c346a65bbb0be58dbbde44f31bca816e4a..1d2cb8ec70e5105001bf4336432d6c5ec0ee18bb 100644 --- a/src/test/java/de/thm/arsnova/services/UserServiceTest.java +++ b/src/test/java/de/thm/arsnova/services/UserServiceTest.java @@ -19,6 +19,7 @@ package de.thm.arsnova.services; import de.thm.arsnova.config.AppConfig; import de.thm.arsnova.config.TestAppConfig; +import de.thm.arsnova.config.TestPersistanceConfig; import de.thm.arsnova.config.TestSecurityConfig; import de.thm.arsnova.entities.User; import org.jasig.cas.client.authentication.AttributePrincipalImpl; @@ -52,7 +53,7 @@ import static org.junit.Assert.assertEquals; @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration -@ContextConfiguration(classes = {AppConfig.class, TestAppConfig.class, TestSecurityConfig.class}) +@ContextConfiguration(classes = {AppConfig.class, TestAppConfig.class, TestPersistanceConfig.class, TestSecurityConfig.class}) @ActiveProfiles("test") public class UserServiceTest {