From bbf2464ea9c42507dfe80830ac267b25f92e25a4 Mon Sep 17 00:00:00 2001
From: Daniel Gerhardt <code@dgerhardt.net>
Date: Tue, 18 Jul 2017 15:53:10 +0200
Subject: [PATCH] Refactor package and class hierarchy

* Moved classes from `de.thm.arsnova` root package.
* New util package
* Do not start interface names with `I`. Appended `Impl` to the
  implementing class instead.
* Renamed learning progress classes to score and move them from
  `.domain` to a new `.services.score` package.
* Do not capitalize acronyms in class names:
    * CASLogoutSuccessHandler -> CasLogoutSuccessHandler
    * SocketIO* -> Socketio*
* Renamed `.socket` package to `.websocket`
---
 .../java/de/thm/arsnova/aop/RangeAspect.java  |    2 +-
 .../de/thm/arsnova/cache/CacheBuster.java     |  111 +-
 .../de/thm/arsnova/cache/CacheBusterImpl.java |  130 ++
 ...Listener.java => CacheBusterListener.java} |   12 +-
 .../de/thm/arsnova/cache/ICacheBuster.java    |   23 -
 .../java/de/thm/arsnova/config/AppConfig.java |   37 +-
 .../de/thm/arsnova/config/SecurityConfig.java |   10 +-
 .../arsnova/controller/CommentController.java |    4 +-
 .../arsnova/controller/ContentController.java |    6 +-
 .../arsnova/controller/CourseController.java  |    4 +-
 .../controller/FeedbackController.java        |   11 +-
 .../arsnova/controller/LegacyController.java  |    4 +-
 .../arsnova/controller/LoginController.java   |    4 +-
 .../arsnova/controller/MotdController.java    |    4 +-
 .../arsnova/controller/SessionController.java |   32 +-
 .../arsnova/controller/SocketController.java  |    8 +-
 .../controller/StatisticsController.java      |    4 +-
 .../arsnova/controller/UserController.java    |    4 +-
 .../de/thm/arsnova/domain/package-info.java   |    4 -
 ...ProgressOptions.java => ScoreOptions.java} |   18 +-
 .../java/de/thm/arsnova/entities/Session.java |   10 +-
 .../thm/arsnova/entities/SessionFeature.java  |    2 +-
 .../arsnova/entities/transport/Answer.java    |    2 +-
 ...ProgressOptions.java => ScoreOptions.java} |   12 +-
 ...ogressValues.java => ScoreStatistics.java} |   12 +-
 .../{NovaEvent.java => ArsnovaEvent.java}     |    6 +-
 ...tVisitor.java => ArsnovaEventVisitor.java} |    4 +-
 ...ogressEvent.java => ChangeScoreEvent.java} |    8 +-
 .../events/DeleteAllLectureAnswersEvent.java  |    2 +-
 .../DeleteAllPreparationAnswersEvent.java     |    2 +-
 .../DeleteAllQuestionsAnswersEvent.java       |    2 +-
 .../events/DeleteAllQuestionsEvent.java       |    2 +-
 .../thm/arsnova/events/DeleteAnswerEvent.java |    2 +-
 .../arsnova/events/DeleteCommentEvent.java    |    2 +-
 .../DeleteFeedbackForSessionsEvent.java       |    4 +-
 .../arsnova/events/DeleteQuestionEvent.java   |    2 +-
 .../arsnova/events/DeleteSessionEvent.java    |    2 +-
 .../arsnova/events/FeatureChangeEvent.java    |    2 +-
 .../arsnova/events/FlipFlashcardsEvent.java   |    2 +-
 .../thm/arsnova/events/LockFeedbackEvent.java |    2 +-
 .../thm/arsnova/events/LockQuestionEvent.java |    2 +-
 .../arsnova/events/LockQuestionsEvent.java    |    2 +-
 .../de/thm/arsnova/events/LockVoteEvent.java  |    2 +-
 .../de/thm/arsnova/events/LockVotesEvent.java |    2 +-
 .../de/thm/arsnova/events/NewAnswerEvent.java |    2 +-
 .../thm/arsnova/events/NewCommentEvent.java   |    2 +-
 .../thm/arsnova/events/NewFeedbackEvent.java  |    2 +-
 .../thm/arsnova/events/NewQuestionEvent.java  |    2 +-
 .../thm/arsnova/events/NewSessionEvent.java   |    2 +-
 .../arsnova/events/PiRoundCancelEvent.java    |    2 +-
 .../events/PiRoundDelayedStartEvent.java      |    2 +-
 .../thm/arsnova/events/PiRoundEndEvent.java   |    2 +-
 .../thm/arsnova/events/PiRoundResetEvent.java |    2 +-
 .../de/thm/arsnova/events/SessionEvent.java   |    4 +-
 .../arsnova/events/StatusSessionEvent.java    |    2 +-
 .../arsnova/events/UnlockQuestionEvent.java   |    2 +-
 .../arsnova/events/UnlockQuestionsEvent.java  |    2 +-
 .../thm/arsnova/events/UnlockVoteEvent.java   |    2 +-
 .../thm/arsnova/events/UnlockVotesEvent.java  |    2 +-
 .../SessionStatisticsRepository.java          |    4 +-
 .../couchdb/CouchDbMotdRepository.java        |    4 +-
 .../couchdb/CouchDbSessionRepository.java     |    4 +-
 .../CouchDbSessionStatisticsRepository.java   |    6 +-
 .../CasLogoutSuccessHandler.java}             |    6 +-
 .../{ => security}/CasUserDetailsService.java |    2 +-
 .../LoginAuthenticationFailureHandler.java    |    2 +-
 .../LoginAuthenticationSucessHandler.java     |    2 +-
 .../thm/arsnova/services/ContentService.java  | 1152 ++---------------
 .../arsnova/services/ContentServiceImpl.java  | 1041 +++++++++++++++
 .../thm/arsnova/services/FeedbackService.java |  166 +--
 .../arsnova/services/FeedbackServiceImpl.java |  184 +++
 .../services/FeedbackStorageService.java      |   16 +
 .../FeedbackStorageServiceImpl.java}          |   11 +-
 .../thm/arsnova/services/IContentService.java |  171 ---
 .../arsnova/services/IFeedbackService.java    |   42 -
 .../de/thm/arsnova/services/IMotdService.java |   59 -
 .../thm/arsnova/services/ISessionService.java |  100 --
 .../arsnova/services/IStatisticsService.java  |   27 -
 .../de/thm/arsnova/services/IUserService.java |   70 -
 .../de/thm/arsnova/services/MotdService.java  |  208 +--
 .../thm/arsnova/services/MotdServiceImpl.java |  197 +++
 .../thm/arsnova/services/SessionService.java  |  543 +-------
 .../arsnova/services/SessionServiceImpl.java  |  515 ++++++++
 .../arsnova/services/StatisticsService.java   |   29 +-
 .../services/StatisticsServiceImpl.java       |   50 +
 .../de/thm/arsnova/services/UserService.java  |  519 +-------
 .../thm/arsnova/services/UserServiceImpl.java |  545 ++++++++
 .../score/QuestionBasedScoreCalculator.java}  |   16 +-
 .../score}/QuestionScore.java                 |    4 +-
 .../score/Score.java}                         |   16 +-
 .../score/ScoreBasedScoreCalculator.java}     |   18 +-
 .../score/ScoreCalculator.java}               |   12 +-
 .../score/ScoreCalculatorFactory.java}        |    6 +-
 .../score/ScoreCalculatorFactoryImpl.java}    |   50 +-
 .../score/ScoreCalculatorListener.java}       |   16 +-
 .../{domain => services/score}/UserScore.java |    2 +-
 .../score/VariantScoreCalculator.java}        |   20 +-
 .../de/thm/arsnova/{ => util}/ImageUtils.java |    2 +-
 .../{ => util}/PaginationListDecorator.java   |    2 +-
 .../ArsnovaSocketioServer.java}               |    4 +-
 .../ArsnovaSocketioServerImpl.java}           |   46 +-
 .../ArsnovaSocketioServerListener.java}       |   14 +-
 .../message/Content.java                      |    2 +-
 .../message/Feedback.java                     |    2 +-
 .../message/Session.java                      |    2 +-
 .../message/package-info.java                 |    2 +-
 .../{socket => websocket}/package-info.java   |    2 +-
 src/site/markdown/development/event-system.md |    6 +-
 .../de/thm/arsnova/config/TestAppConfig.java  |   11 +-
 .../arsnova/config/TestSecurityConfig.java    |    2 +-
 .../thm/arsnova/services/StubUserService.java |    2 +-
 .../QuestionBasedScoreCalculatorTest.java}    |   37 +-
 .../score/ScoreBasedScoreCalculatorTest.java} |   31 +-
 .../arsnova/{ => util}/ImageUtilsTest.java    |    7 +-
 114 files changed, 3289 insertions(+), 3274 deletions(-)
 create mode 100644 src/main/java/de/thm/arsnova/cache/CacheBusterImpl.java
 rename src/main/java/de/thm/arsnova/cache/{CacheBustListener.java => CacheBusterListener.java} (78%)
 delete mode 100644 src/main/java/de/thm/arsnova/cache/ICacheBuster.java
 delete mode 100644 src/main/java/de/thm/arsnova/domain/package-info.java
 rename src/main/java/de/thm/arsnova/entities/{LearningProgressOptions.java => ScoreOptions.java} (74%)
 rename src/main/java/de/thm/arsnova/entities/transport/{LearningProgressOptions.java => ScoreOptions.java} (78%)
 rename src/main/java/de/thm/arsnova/entities/transport/{LearningProgressValues.java => ScoreStatistics.java} (93%)
 rename src/main/java/de/thm/arsnova/events/{NovaEvent.java => ArsnovaEvent.java} (85%)
 rename src/main/java/de/thm/arsnova/events/{NovaEventVisitor.java => ArsnovaEventVisitor.java} (96%)
 rename src/main/java/de/thm/arsnova/events/{ChangeLearningProgressEvent.java => ChangeScoreEvent.java} (79%)
 rename src/main/java/de/thm/arsnova/{CASLogoutSuccessHandler.java => security/CasLogoutSuccessHandler.java} (92%)
 rename src/main/java/de/thm/arsnova/{ => security}/CasUserDetailsService.java (98%)
 rename src/main/java/de/thm/arsnova/{ => security}/LoginAuthenticationFailureHandler.java (98%)
 rename src/main/java/de/thm/arsnova/{ => security}/LoginAuthenticationSucessHandler.java (97%)
 create mode 100644 src/main/java/de/thm/arsnova/services/ContentServiceImpl.java
 create mode 100644 src/main/java/de/thm/arsnova/services/FeedbackServiceImpl.java
 create mode 100644 src/main/java/de/thm/arsnova/services/FeedbackStorageService.java
 rename src/main/java/de/thm/arsnova/{FeedbackStorage.java => services/FeedbackStorageServiceImpl.java} (95%)
 delete mode 100644 src/main/java/de/thm/arsnova/services/IContentService.java
 delete mode 100644 src/main/java/de/thm/arsnova/services/IFeedbackService.java
 delete mode 100644 src/main/java/de/thm/arsnova/services/IMotdService.java
 delete mode 100644 src/main/java/de/thm/arsnova/services/ISessionService.java
 delete mode 100644 src/main/java/de/thm/arsnova/services/IStatisticsService.java
 delete mode 100644 src/main/java/de/thm/arsnova/services/IUserService.java
 create mode 100644 src/main/java/de/thm/arsnova/services/MotdServiceImpl.java
 create mode 100644 src/main/java/de/thm/arsnova/services/SessionServiceImpl.java
 create mode 100644 src/main/java/de/thm/arsnova/services/StatisticsServiceImpl.java
 create mode 100644 src/main/java/de/thm/arsnova/services/UserServiceImpl.java
 rename src/main/java/de/thm/arsnova/{domain/QuestionBasedLearningProgress.java => services/score/QuestionBasedScoreCalculator.java} (87%)
 rename src/main/java/de/thm/arsnova/{domain => services/score}/QuestionScore.java (96%)
 rename src/main/java/de/thm/arsnova/{domain/CourseScore.java => services/score/Score.java} (87%)
 rename src/main/java/de/thm/arsnova/{domain/PointBasedLearningProgress.java => services/score/ScoreBasedScoreCalculator.java} (80%)
 rename src/main/java/de/thm/arsnova/{domain/LearningProgress.java => services/score/ScoreCalculator.java} (70%)
 rename src/main/java/de/thm/arsnova/{domain/ILearningProgressFactory.java => services/score/ScoreCalculatorFactory.java} (84%)
 rename src/main/java/de/thm/arsnova/{domain/LearningProgressFactory.java => services/score/ScoreCalculatorFactoryImpl.java} (68%)
 rename src/main/java/de/thm/arsnova/{domain/LearningProgressListener.java => services/score/ScoreCalculatorListener.java} (70%)
 rename src/main/java/de/thm/arsnova/{domain => services/score}/UserScore.java (97%)
 rename src/main/java/de/thm/arsnova/{domain/VariantLearningProgress.java => services/score/VariantScoreCalculator.java} (71%)
 rename src/main/java/de/thm/arsnova/{ => util}/ImageUtils.java (99%)
 rename src/main/java/de/thm/arsnova/{ => util}/PaginationListDecorator.java (99%)
 rename src/main/java/de/thm/arsnova/{socket/ARSnovaSocket.java => websocket/ArsnovaSocketioServer.java} (92%)
 rename src/main/java/de/thm/arsnova/{socket/ARSnovaSocketIOServer.java => websocket/ArsnovaSocketioServerImpl.java} (95%)
 rename src/main/java/de/thm/arsnova/{socket/ARSnovaSocketListener.java => websocket/ArsnovaSocketioServerListener.java} (75%)
 rename src/main/java/de/thm/arsnova/{socket => websocket}/message/Content.java (96%)
 rename src/main/java/de/thm/arsnova/{socket => websocket}/message/Feedback.java (96%)
 rename src/main/java/de/thm/arsnova/{socket => websocket}/message/Session.java (95%)
 rename src/main/java/de/thm/arsnova/{socket => websocket}/message/package-info.java (60%)
 rename src/main/java/de/thm/arsnova/{socket => websocket}/package-info.java (66%)
 rename src/test/java/de/thm/arsnova/{domain/QuestionBasedLearningProgressTest.java => services/score/QuestionBasedScoreCalculatorTest.java} (87%)
 rename src/test/java/de/thm/arsnova/{domain/PointBasedLearningProgressTest.java => services/score/ScoreBasedScoreCalculatorTest.java} (84%)
 rename src/test/java/de/thm/arsnova/{ => util}/ImageUtilsTest.java (94%)

diff --git a/src/main/java/de/thm/arsnova/aop/RangeAspect.java b/src/main/java/de/thm/arsnova/aop/RangeAspect.java
index 705b7529d..19734913d 100644
--- a/src/main/java/de/thm/arsnova/aop/RangeAspect.java
+++ b/src/main/java/de/thm/arsnova/aop/RangeAspect.java
@@ -17,7 +17,7 @@
  */
 package de.thm.arsnova.aop;
 
-import de.thm.arsnova.PaginationListDecorator;
+import de.thm.arsnova.util.PaginationListDecorator;
 import de.thm.arsnova.controller.PaginationController;
 import de.thm.arsnova.services.ResponseProviderService;
 import org.aspectj.lang.ProceedingJoinPoint;
diff --git a/src/main/java/de/thm/arsnova/cache/CacheBuster.java b/src/main/java/de/thm/arsnova/cache/CacheBuster.java
index c998bd4f3..510f9e740 100644
--- a/src/main/java/de/thm/arsnova/cache/CacheBuster.java
+++ b/src/main/java/de/thm/arsnova/cache/CacheBuster.java
@@ -17,114 +17,7 @@
  */
 package de.thm.arsnova.cache;
 
-import de.thm.arsnova.events.*;
-import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.stereotype.Component;
-
 /**
- * This class is used to evict caches based on events. The events carry all necessary information to clear the
- * caches, e.g, for a specific session.
+ * This interface is used as a tag to make Spring dependency injection happy...
  */
-@Component
-public class CacheBuster implements ICacheBuster, NovaEventVisitor {
-
-	@CacheEvict(value = "statistics", allEntries = true)
-	@Override
-	public void visit(NewCommentEvent event) { }
-
-	@CacheEvict(value = "statistics", allEntries = true)
-	@Override
-	public void visit(DeleteCommentEvent event) { }
-
-	@Override
-	public void visit(NewQuestionEvent event) { }
-
-	@Override
-	public void visit(UnlockQuestionEvent event) { }
-
-	@Override
-	public void visit(UnlockQuestionsEvent newQuestionsEvent) { }
-
-	@Override
-	public void visit(LockQuestionEvent lockQuestionEvent) { }
-
-	@Override
-	public void visit(LockQuestionsEvent lockQuestionsEvent) { }
-
-	@CacheEvict(value = "answers", key = "#event.content")
-	@Override
-	public void visit(NewAnswerEvent event) { }
-
-	@Override
-	public void visit(DeleteAnswerEvent event) { }
-
-	@Override
-	public void visit(DeleteQuestionEvent event) { }
-
-	@Override
-	public void visit(DeleteAllQuestionsEvent event) { }
-
-	@Override
-	public void visit(DeleteAllQuestionsAnswersEvent event) { }
-
-	@Override
-	public void visit(DeleteAllPreparationAnswersEvent event) { }
-
-	@Override
-	public void visit(DeleteAllLectureAnswersEvent event) { }
-
-	@Override
-	public void visit(NewFeedbackEvent event) { }
-
-	@Override
-	public void visit(DeleteFeedbackForSessionsEvent event) { }
-
-	@Override
-	public void visit(StatusSessionEvent event) { }
-
-	@CacheEvict(value = "statistics", allEntries = true)
-	@Override
-	public void visit(ChangeLearningProgressEvent changeLearningProgress) { }
-
-	@Override
-	public void visit(PiRoundDelayedStartEvent piRoundDelayedStartEvent) { }
-
-	@Override
-	public void visit(PiRoundEndEvent piRoundEndEvent) { }
-
-	@Override
-	public void visit(PiRoundCancelEvent piRoundCancelEvent) { }
-
-	@Override
-	public void visit(PiRoundResetEvent piRoundResetEvent) { }
-
-	@CacheEvict(value = "statistics", allEntries = true)
-	@Override
-	public void visit(NewSessionEvent newSessionEvent) { }
-
-	@CacheEvict(value = "statistics", allEntries = true)
-	@Override
-	public void visit(DeleteSessionEvent deleteSessionEvent) { }
-
-	@Override
-	public void visit(LockVoteEvent lockVoteEvent) { }
-
-	@Override
-	public void visit(LockVotesEvent lockVotesEvent) { }
-
-	@Override
-	public void visit(UnlockVoteEvent unlockVoteEvent) { }
-
-	@Override
-	public void visit(UnlockVotesEvent unlockVotesEvent) { }
-
-	@Override
-	public void visit(FeatureChangeEvent featureChangeEvent) { }
-
-	@Override
-	public void visit(LockFeedbackEvent lockFeedbackEvent) { }
-
-	@Override
-	public void visit(FlipFlashcardsEvent flipFlashcardsEvent) { }
-
-}
+public interface CacheBuster { }
diff --git a/src/main/java/de/thm/arsnova/cache/CacheBusterImpl.java b/src/main/java/de/thm/arsnova/cache/CacheBusterImpl.java
new file mode 100644
index 000000000..dad103c84
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/cache/CacheBusterImpl.java
@@ -0,0 +1,130 @@
+/*
+ * 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.cache;
+
+import de.thm.arsnova.events.*;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.stereotype.Component;
+
+/**
+ * This class is used to evict caches based on events. The events carry all necessary information to clear the
+ * caches, e.g, for a specific session.
+ */
+@Component
+public class CacheBusterImpl implements CacheBuster, ArsnovaEventVisitor {
+
+	@CacheEvict(value = "statistics", allEntries = true)
+	@Override
+	public void visit(NewCommentEvent event) { }
+
+	@CacheEvict(value = "statistics", allEntries = true)
+	@Override
+	public void visit(DeleteCommentEvent event) { }
+
+	@Override
+	public void visit(NewQuestionEvent event) { }
+
+	@Override
+	public void visit(UnlockQuestionEvent event) { }
+
+	@Override
+	public void visit(UnlockQuestionsEvent newQuestionsEvent) { }
+
+	@Override
+	public void visit(LockQuestionEvent lockQuestionEvent) { }
+
+	@Override
+	public void visit(LockQuestionsEvent lockQuestionsEvent) { }
+
+	@CacheEvict(value = "answers", key = "#event.content")
+	@Override
+	public void visit(NewAnswerEvent event) { }
+
+	@Override
+	public void visit(DeleteAnswerEvent event) { }
+
+	@Override
+	public void visit(DeleteQuestionEvent event) { }
+
+	@Override
+	public void visit(DeleteAllQuestionsEvent event) { }
+
+	@Override
+	public void visit(DeleteAllQuestionsAnswersEvent event) { }
+
+	@Override
+	public void visit(DeleteAllPreparationAnswersEvent event) { }
+
+	@Override
+	public void visit(DeleteAllLectureAnswersEvent event) { }
+
+	@Override
+	public void visit(NewFeedbackEvent event) { }
+
+	@Override
+	public void visit(DeleteFeedbackForSessionsEvent event) { }
+
+	@Override
+	public void visit(StatusSessionEvent event) { }
+
+	@CacheEvict(value = "statistics", allEntries = true)
+	@Override
+	public void visit(ChangeScoreEvent changeLearningProgress) { }
+
+	@Override
+	public void visit(PiRoundDelayedStartEvent piRoundDelayedStartEvent) { }
+
+	@Override
+	public void visit(PiRoundEndEvent piRoundEndEvent) { }
+
+	@Override
+	public void visit(PiRoundCancelEvent piRoundCancelEvent) { }
+
+	@Override
+	public void visit(PiRoundResetEvent piRoundResetEvent) { }
+
+	@CacheEvict(value = "statistics", allEntries = true)
+	@Override
+	public void visit(NewSessionEvent newSessionEvent) { }
+
+	@CacheEvict(value = "statistics", allEntries = true)
+	@Override
+	public void visit(DeleteSessionEvent deleteSessionEvent) { }
+
+	@Override
+	public void visit(LockVoteEvent lockVoteEvent) { }
+
+	@Override
+	public void visit(LockVotesEvent lockVotesEvent) { }
+
+	@Override
+	public void visit(UnlockVoteEvent unlockVoteEvent) { }
+
+	@Override
+	public void visit(UnlockVotesEvent unlockVotesEvent) { }
+
+	@Override
+	public void visit(FeatureChangeEvent featureChangeEvent) { }
+
+	@Override
+	public void visit(LockFeedbackEvent lockFeedbackEvent) { }
+
+	@Override
+	public void visit(FlipFlashcardsEvent flipFlashcardsEvent) { }
+
+}
diff --git a/src/main/java/de/thm/arsnova/cache/CacheBustListener.java b/src/main/java/de/thm/arsnova/cache/CacheBusterListener.java
similarity index 78%
rename from src/main/java/de/thm/arsnova/cache/CacheBustListener.java
rename to src/main/java/de/thm/arsnova/cache/CacheBusterListener.java
index db365d064..5bab8c63c 100644
--- a/src/main/java/de/thm/arsnova/cache/CacheBustListener.java
+++ b/src/main/java/de/thm/arsnova/cache/CacheBusterListener.java
@@ -17,8 +17,8 @@
  */
 package de.thm.arsnova.cache;
 
-import de.thm.arsnova.events.NovaEvent;
-import de.thm.arsnova.events.NovaEventVisitor;
+import de.thm.arsnova.events.ArsnovaEvent;
+import de.thm.arsnova.events.ArsnovaEventVisitor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationListener;
 import org.springframework.stereotype.Component;
@@ -29,14 +29,14 @@ import org.springframework.stereotype.Component;
  * Note that this class is necessary in order for the annotations to work.
  */
 @Component
-public class CacheBustListener implements ApplicationListener<NovaEvent> {
+public class CacheBusterListener implements ApplicationListener<ArsnovaEvent> {
 
 	@Autowired
-	private ICacheBuster cacheBuster;
+	private CacheBuster cacheBuster;
 
 	@Override
-	public void onApplicationEvent(NovaEvent event) {
-		event.accept((NovaEventVisitor) cacheBuster);
+	public void onApplicationEvent(ArsnovaEvent event) {
+		event.accept((ArsnovaEventVisitor) cacheBuster);
 	}
 
 }
diff --git a/src/main/java/de/thm/arsnova/cache/ICacheBuster.java b/src/main/java/de/thm/arsnova/cache/ICacheBuster.java
deleted file mode 100644
index ab937cdd3..000000000
--- a/src/main/java/de/thm/arsnova/cache/ICacheBuster.java
+++ /dev/null
@@ -1,23 +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.cache;
-
-/**
- * This interface is used as a tag to make Spring dependency injection happy...
- */
-public interface ICacheBuster { }
diff --git a/src/main/java/de/thm/arsnova/config/AppConfig.java b/src/main/java/de/thm/arsnova/config/AppConfig.java
index ab92d140c..73719ab35 100644
--- a/src/main/java/de/thm/arsnova/config/AppConfig.java
+++ b/src/main/java/de/thm/arsnova/config/AppConfig.java
@@ -20,26 +20,18 @@ 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.util.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.socket.ARSnovaSocketListener;
+import de.thm.arsnova.websocket.ArsnovaSocketioServer;
+import de.thm.arsnova.websocket.ArsnovaSocketioServerImpl;
+import de.thm.arsnova.websocket.ArsnovaSocketioServerListener;
 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;
@@ -88,7 +80,6 @@ import java.util.List;
 		"de.thm.arsnova.aop",
 		"de.thm.arsnova.cache",
 		"de.thm.arsnova.controller",
-		"de.thm.arsnova.domain",
 		"de.thm.arsnova.dao",
 		"de.thm.arsnova.events",
 		"de.thm.arsnova.security",
@@ -261,19 +252,19 @@ public class AppConfig extends WebMvcConfigurerAdapter {
 
 	@Profile("!test")
 	@Bean(name = "socketServer", initMethod = "startServer", destroyMethod = "stopServer")
-	public ARSnovaSocket socketServer() {
-		final ARSnovaSocketIOServer socketServer = new ARSnovaSocketIOServer();
-		socketServer.setHostIp(socketAddress);
-		socketServer.setPortNumber(socketPort);
-		socketServer.setUseSSL(!socketKeystore.isEmpty());
-		socketServer.setKeystore(socketKeystore);
-		socketServer.setStorepass(socketKeystorePassword);
-		return socketServer;
+	public ArsnovaSocketioServer socketServer() {
+		final ArsnovaSocketioServerImpl socketioServer = new ArsnovaSocketioServerImpl();
+		socketioServer.setHostIp(socketAddress);
+		socketioServer.setPortNumber(socketPort);
+		socketioServer.setUseSSL(!socketKeystore.isEmpty());
+		socketioServer.setKeystore(socketKeystore);
+		socketioServer.setStorepass(socketKeystorePassword);
+		return socketioServer;
 	}
 
 	@Bean
-	public ARSnovaSocketListener arsnovaSocketListener() {
-		return new ARSnovaSocketListener();
+	public ArsnovaSocketioServerListener arsnovaSocketListener() {
+		return new ArsnovaSocketioServerListener();
 	}
 
 	@Bean
diff --git a/src/main/java/de/thm/arsnova/config/SecurityConfig.java b/src/main/java/de/thm/arsnova/config/SecurityConfig.java
index fbf39de4f..fe8fb5974 100644
--- a/src/main/java/de/thm/arsnova/config/SecurityConfig.java
+++ b/src/main/java/de/thm/arsnova/config/SecurityConfig.java
@@ -17,10 +17,10 @@
  */
 package de.thm.arsnova.config;
 
-import de.thm.arsnova.CASLogoutSuccessHandler;
-import de.thm.arsnova.CasUserDetailsService;
-import de.thm.arsnova.LoginAuthenticationFailureHandler;
-import de.thm.arsnova.LoginAuthenticationSucessHandler;
+import de.thm.arsnova.security.CasLogoutSuccessHandler;
+import de.thm.arsnova.security.CasUserDetailsService;
+import de.thm.arsnova.security.LoginAuthenticationFailureHandler;
+import de.thm.arsnova.security.LoginAuthenticationSucessHandler;
 import de.thm.arsnova.security.ApplicationPermissionEvaluator;
 import de.thm.arsnova.security.CustomLdapUserDetailsMapper;
 import de.thm.arsnova.security.DbUserDetailsService;
@@ -364,7 +364,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
 	@Bean
 	public LogoutSuccessHandler casLogoutSuccessHandler() {
-		CASLogoutSuccessHandler handler = new CASLogoutSuccessHandler();
+		CasLogoutSuccessHandler handler = new CasLogoutSuccessHandler();
 		handler.setCasUrl(casUrl);
 		handler.setDefaultTarget(rootUrl);
 
diff --git a/src/main/java/de/thm/arsnova/controller/CommentController.java b/src/main/java/de/thm/arsnova/controller/CommentController.java
index e38c98630..c2d792f36 100644
--- a/src/main/java/de/thm/arsnova/controller/CommentController.java
+++ b/src/main/java/de/thm/arsnova/controller/CommentController.java
@@ -20,7 +20,7 @@ package de.thm.arsnova.controller;
 import de.thm.arsnova.entities.CommentReadingCount;
 import de.thm.arsnova.entities.transport.Comment;
 import de.thm.arsnova.exceptions.BadRequestException;
-import de.thm.arsnova.services.IContentService;
+import de.thm.arsnova.services.ContentService;
 import de.thm.arsnova.web.DeprecatedApi;
 import de.thm.arsnova.web.Pagination;
 import io.swagger.annotations.Api;
@@ -49,7 +49,7 @@ import java.util.List;
 public class CommentController extends PaginationController {
 
 	@Autowired
-	private IContentService contentService;
+	private ContentService contentService;
 
 	@ApiOperation(value = "Count all the comments in current session",
 			nickname = "getAudienceQuestionCount")
diff --git a/src/main/java/de/thm/arsnova/controller/ContentController.java b/src/main/java/de/thm/arsnova/controller/ContentController.java
index 9695d3651..f2c77d5e3 100644
--- a/src/main/java/de/thm/arsnova/controller/ContentController.java
+++ b/src/main/java/de/thm/arsnova/controller/ContentController.java
@@ -17,14 +17,14 @@
  */
 package de.thm.arsnova.controller;
 
-import de.thm.arsnova.PaginationListDecorator;
+import de.thm.arsnova.util.PaginationListDecorator;
 import de.thm.arsnova.entities.Answer;
 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.IContentService;
+import de.thm.arsnova.services.ContentService;
 import de.thm.arsnova.web.DeprecatedApi;
 import de.thm.arsnova.web.Pagination;
 import io.swagger.annotations.Api;
@@ -54,7 +54,7 @@ import java.util.List;
 @Api(value = "/lecturerquestion", description = "Operations for Lecture Questions")
 public class ContentController extends PaginationController {
 	@Autowired
-	private IContentService contentService;
+	private ContentService contentService;
 
 	@ApiOperation(value = "Get question with provided question Id",
 			nickname = "getQuestion")
diff --git a/src/main/java/de/thm/arsnova/controller/CourseController.java b/src/main/java/de/thm/arsnova/controller/CourseController.java
index e8919bc43..74e6c5148 100644
--- a/src/main/java/de/thm/arsnova/controller/CourseController.java
+++ b/src/main/java/de/thm/arsnova/controller/CourseController.java
@@ -23,7 +23,7 @@ import de.thm.arsnova.connector.model.UserRole;
 import de.thm.arsnova.entities.User;
 import de.thm.arsnova.exceptions.NotImplementedException;
 import de.thm.arsnova.exceptions.UnauthorizedException;
-import de.thm.arsnova.services.IUserService;
+import de.thm.arsnova.services.UserService;
 import io.swagger.annotations.ApiParam;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -46,7 +46,7 @@ public class CourseController extends AbstractController {
 	private ConnectorClient connectorClient;
 
 	@Autowired
-	private IUserService userService;
+	private UserService userService;
 
 	@RequestMapping(value = "/mycourses", method = RequestMethod.GET)
 	public List<Course> myCourses(
diff --git a/src/main/java/de/thm/arsnova/controller/FeedbackController.java b/src/main/java/de/thm/arsnova/controller/FeedbackController.java
index 2f3e3b16c..000176a21 100644
--- a/src/main/java/de/thm/arsnova/controller/FeedbackController.java
+++ b/src/main/java/de/thm/arsnova/controller/FeedbackController.java
@@ -20,8 +20,9 @@ package de.thm.arsnova.controller;
 import de.thm.arsnova.entities.Feedback;
 import de.thm.arsnova.entities.User;
 import de.thm.arsnova.exceptions.NotFoundException;
-import de.thm.arsnova.services.IFeedbackService;
-import de.thm.arsnova.services.IUserService;
+import de.thm.arsnova.services.FeedbackService;
+import de.thm.arsnova.services.UserService;
+import de.thm.arsnova.websocket.ArsnovaSocketioServerImpl;
 import de.thm.arsnova.web.DeprecatedApi;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
@@ -36,15 +37,15 @@ import org.springframework.web.bind.annotation.RestController;
  * Handles requests concerning the user's feedback, i.e., "too fast" or "faster, please". This HTTP API is
  * deprecated in favor of the socket implementation.
  *
- * @see de.thm.arsnova.socket.ARSnovaSocketIOServer
+ * @see ArsnovaSocketioServerImpl
  */
 @RestController
 public class FeedbackController extends AbstractController {
 	@Autowired
-	private IFeedbackService feedbackService;
+	private FeedbackService feedbackService;
 
 	@Autowired
-	private IUserService userService;
+	private UserService userService;
 
 	@DeprecatedApi
 	@Deprecated
diff --git a/src/main/java/de/thm/arsnova/controller/LegacyController.java b/src/main/java/de/thm/arsnova/controller/LegacyController.java
index a1d3a9de2..f1966160e 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.IContentService;
+import de.thm.arsnova.services.ContentService;
 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 IContentService contentService;
+	private ContentService contentService;
 
 	/* specific routes */
 
diff --git a/src/main/java/de/thm/arsnova/controller/LoginController.java b/src/main/java/de/thm/arsnova/controller/LoginController.java
index 119e7594e..820bba435 100644
--- a/src/main/java/de/thm/arsnova/controller/LoginController.java
+++ b/src/main/java/de/thm/arsnova/controller/LoginController.java
@@ -21,7 +21,7 @@ import de.thm.arsnova.entities.ServiceDescription;
 import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.User;
 import de.thm.arsnova.exceptions.UnauthorizedException;
-import de.thm.arsnova.services.IUserService;
+import de.thm.arsnova.services.UserService;
 import de.thm.arsnova.services.UserSessionService;
 import org.pac4j.core.context.J2EContext;
 import org.pac4j.core.exception.HttpAction;
@@ -142,7 +142,7 @@ public class LoginController extends AbstractController {
 	private CasAuthenticationEntryPoint casEntryPoint;
 
 	@Autowired
-	private IUserService userService;
+	private UserService userService;
 
 	@Autowired
 	private UserSessionService userSessionService;
diff --git a/src/main/java/de/thm/arsnova/controller/MotdController.java b/src/main/java/de/thm/arsnova/controller/MotdController.java
index 9f1e51554..c566a602e 100644
--- a/src/main/java/de/thm/arsnova/controller/MotdController.java
+++ b/src/main/java/de/thm/arsnova/controller/MotdController.java
@@ -19,7 +19,7 @@ package de.thm.arsnova.controller;
 
 import de.thm.arsnova.entities.Motd;
 import de.thm.arsnova.entities.MotdList;
-import de.thm.arsnova.services.IMotdService;
+import de.thm.arsnova.services.MotdService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -48,7 +48,7 @@ import java.util.List;
 public class MotdController extends AbstractController {
 
 	@Autowired
-	private IMotdService motdService;
+	private MotdService motdService;
 
 	@ApiOperation(value = "get messages. if adminview=false, only messages with startdate<clientdate<enddate are returned")
 	@RequestMapping(value = "/", method = RequestMethod.GET)
diff --git a/src/main/java/de/thm/arsnova/controller/SessionController.java b/src/main/java/de/thm/arsnova/controller/SessionController.java
index 9b0dcd48a..77ee2bfac 100644
--- a/src/main/java/de/thm/arsnova/controller/SessionController.java
+++ b/src/main/java/de/thm/arsnova/controller/SessionController.java
@@ -22,14 +22,14 @@ import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.SessionFeature;
 import de.thm.arsnova.entities.SessionInfo;
 import de.thm.arsnova.entities.transport.ImportExportSession;
-import de.thm.arsnova.entities.transport.LearningProgressValues;
+import de.thm.arsnova.entities.transport.ScoreStatistics;
 import de.thm.arsnova.exceptions.UnauthorizedException;
-import de.thm.arsnova.services.ISessionService;
-import de.thm.arsnova.services.IUserService;
-import de.thm.arsnova.services.SessionService.SessionInfoNameComparator;
-import de.thm.arsnova.services.SessionService.SessionInfoShortNameComparator;
-import de.thm.arsnova.services.SessionService.SessionNameComparator;
-import de.thm.arsnova.services.SessionService.SessionShortNameComparator;
+import de.thm.arsnova.services.SessionService;
+import de.thm.arsnova.services.UserService;
+import de.thm.arsnova.services.SessionServiceImpl.SessionInfoNameComparator;
+import de.thm.arsnova.services.SessionServiceImpl.SessionInfoShortNameComparator;
+import de.thm.arsnova.services.SessionServiceImpl.SessionNameComparator;
+import de.thm.arsnova.services.SessionServiceImpl.SessionShortNameComparator;
 import de.thm.arsnova.web.DeprecatedApi;
 import de.thm.arsnova.web.Pagination;
 import io.swagger.annotations.Api;
@@ -61,10 +61,10 @@ import java.util.List;
 @Api(value = "/session", description = "the Session Controller API")
 public class SessionController extends PaginationController {
 	@Autowired
-	private ISessionService sessionService;
+	private SessionService sessionService;
 
 	@Autowired
-	private IUserService userService;
+	private UserService userService;
 
 	@ApiOperation(value = "join a session",
 			nickname = "joinSession")
@@ -345,28 +345,28 @@ public class SessionController extends PaginationController {
 		return null;
 	}
 
-	@ApiOperation(value = "retrieves a value for the learning progress",
+	@ApiOperation(value = "retrieves a value for the score",
 			nickname = "getLearningProgress")
 	@RequestMapping(value = "/{sessionkey}/learningprogress", method = RequestMethod.GET)
-	public LearningProgressValues getLearningProgress(
+	public ScoreStatistics getLearningProgress(
 			@ApiParam(value = "session-key from current session", required = true) @PathVariable final String sessionkey,
-			@ApiParam(value = "progress type", required = false) @RequestParam(value = "type", defaultValue = "questions") final String progressType,
+			@ApiParam(value = "type", required = false) @RequestParam(value = "type", defaultValue = "questions") final String type,
 			@ApiParam(value = "question variant", required = false) @RequestParam(value = "questionVariant", required = false) final String questionVariant,
 			final HttpServletResponse response
 			) {
-		return sessionService.getLearningProgress(sessionkey, progressType, questionVariant);
+		return sessionService.getLearningProgress(sessionkey, type, questionVariant);
 	}
 
 	@ApiOperation(value = "retrieves a value for the learning progress for the current user",
 			nickname = "getMyLearningProgress")
 	@RequestMapping(value = "/{sessionkey}/mylearningprogress", method = RequestMethod.GET)
-	public LearningProgressValues getMyLearningProgress(
+	public ScoreStatistics getMyLearningProgress(
 			@ApiParam(value = "session-key from current session", required = true) @PathVariable final String sessionkey,
-			@RequestParam(value = "type", defaultValue = "questions") final String progressType,
+			@RequestParam(value = "type", defaultValue = "questions") final String type,
 			@RequestParam(value = "questionVariant", required = false) final String questionVariant,
 			final HttpServletResponse response
 			) {
-		return sessionService.getMyLearningProgress(sessionkey, progressType, questionVariant);
+		return sessionService.getMyLearningProgress(sessionkey, type, questionVariant);
 	}
 
 	@ApiOperation(value = "retrieves all session features",
diff --git a/src/main/java/de/thm/arsnova/controller/SocketController.java b/src/main/java/de/thm/arsnova/controller/SocketController.java
index 685d1ff7d..0fa66e39a 100644
--- a/src/main/java/de/thm/arsnova/controller/SocketController.java
+++ b/src/main/java/de/thm/arsnova/controller/SocketController.java
@@ -18,9 +18,9 @@
 package de.thm.arsnova.controller;
 
 import de.thm.arsnova.entities.User;
-import de.thm.arsnova.services.IUserService;
+import de.thm.arsnova.services.UserService;
 import de.thm.arsnova.services.UserSessionService;
-import de.thm.arsnova.socket.ARSnovaSocket;
+import de.thm.arsnova.websocket.ArsnovaSocketioServer;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -49,13 +49,13 @@ import java.util.UUID;
 public class SocketController extends AbstractController {
 
 	@Autowired
-	private IUserService userService;
+	private UserService userService;
 
 	@Autowired
 	private UserSessionService userSessionService;
 
 	@Autowired
-	private ARSnovaSocket server;
+	private ArsnovaSocketioServer server;
 
 	private static final Logger logger = LoggerFactory.getLogger(SocketController.class);
 
diff --git a/src/main/java/de/thm/arsnova/controller/StatisticsController.java b/src/main/java/de/thm/arsnova/controller/StatisticsController.java
index a82e57535..baedcf0ae 100644
--- a/src/main/java/de/thm/arsnova/controller/StatisticsController.java
+++ b/src/main/java/de/thm/arsnova/controller/StatisticsController.java
@@ -18,7 +18,7 @@
 package de.thm.arsnova.controller;
 
 import de.thm.arsnova.entities.Statistics;
-import de.thm.arsnova.services.IStatisticsService;
+import de.thm.arsnova.services.StatisticsService;
 import de.thm.arsnova.web.CacheControl;
 import de.thm.arsnova.web.DeprecatedApi;
 import io.swagger.annotations.Api;
@@ -36,7 +36,7 @@ import org.springframework.web.bind.annotation.RestController;
 public class StatisticsController extends AbstractController {
 
 	@Autowired
-	private IStatisticsService statisticsService;
+	private StatisticsService statisticsService;
 
 	@ApiOperation(value = "Retrieves global statistics",
 			nickname = "getStatistics")
diff --git a/src/main/java/de/thm/arsnova/controller/UserController.java b/src/main/java/de/thm/arsnova/controller/UserController.java
index df9237599..e9b2d5c25 100644
--- a/src/main/java/de/thm/arsnova/controller/UserController.java
+++ b/src/main/java/de/thm/arsnova/controller/UserController.java
@@ -18,7 +18,7 @@
 package de.thm.arsnova.controller;
 
 import de.thm.arsnova.entities.DbUser;
-import de.thm.arsnova.services.IUserService;
+import de.thm.arsnova.services.UserService;
 import de.thm.arsnova.services.UserSessionService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
@@ -41,7 +41,7 @@ public class UserController extends AbstractController {
 	private DaoAuthenticationProvider daoProvider;
 
 	@Autowired
-	private IUserService userService;
+	private UserService userService;
 
 	@Autowired
 	private UserSessionService userSessionService;
diff --git a/src/main/java/de/thm/arsnova/domain/package-info.java b/src/main/java/de/thm/arsnova/domain/package-info.java
deleted file mode 100644
index 4995c742e..000000000
--- a/src/main/java/de/thm/arsnova/domain/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * The 'M' in MVC
- */
-package de.thm.arsnova.domain;
diff --git a/src/main/java/de/thm/arsnova/entities/LearningProgressOptions.java b/src/main/java/de/thm/arsnova/entities/ScoreOptions.java
similarity index 74%
rename from src/main/java/de/thm/arsnova/entities/LearningProgressOptions.java
rename to src/main/java/de/thm/arsnova/entities/ScoreOptions.java
index 342d2c29a..1715f484e 100644
--- a/src/main/java/de/thm/arsnova/entities/LearningProgressOptions.java
+++ b/src/main/java/de/thm/arsnova/entities/ScoreOptions.java
@@ -25,22 +25,22 @@ import io.swagger.annotations.ApiModelProperty;
 import java.io.Serializable;
 
 /**
- * A session's settings regarding the calculation of the learning progress.
+ * A session's settings regarding the calculation of the score.
  */
-@ApiModel(value = "learning progress options", description = "the learning progress entity")
-public class LearningProgressOptions implements Serializable {
+@ApiModel(value = "score options", description = "the score entity")
+public class ScoreOptions implements Serializable {
 
 	private String type = "questions";
 
 	private String questionVariant = "";
 
-	public LearningProgressOptions(LearningProgressOptions learningProgressOptions) {
+	public ScoreOptions(ScoreOptions scoreOptions) {
 		this();
-		this.type = learningProgressOptions.getType();
-		this.questionVariant = learningProgressOptions.getQuestionVariant();
+		this.type = scoreOptions.getType();
+		this.questionVariant = scoreOptions.getQuestionVariant();
 	}
 
-	public LearningProgressOptions() { }
+	public ScoreOptions() { }
 
 	@ApiModelProperty(required = true, value = "the type")
 	@JsonView({View.Persistence.class, View.Public.class})
@@ -49,8 +49,8 @@ public class LearningProgressOptions implements Serializable {
 	}
 
 	@JsonView({View.Persistence.class, View.Public.class})
-	public void setType(String learningProgressType) {
-		this.type = learningProgressType;
+	public void setType(String type) {
+		this.type = type;
 	}
 
 	@ApiModelProperty(required = true, value = "either lecture or preparation")
diff --git a/src/main/java/de/thm/arsnova/entities/Session.java b/src/main/java/de/thm/arsnova/entities/Session.java
index 65bf6c626..eeb54b5f4 100644
--- a/src/main/java/de/thm/arsnova/entities/Session.java
+++ b/src/main/java/de/thm/arsnova/entities/Session.java
@@ -39,7 +39,7 @@ public class Session implements Entity {
 	private String courseType;
 	private String courseId;
 	private long creationTime;
-	private LearningProgressOptions learningProgressOptions = new LearningProgressOptions();
+	private ScoreOptions learningProgressOptions = new ScoreOptions();
 	private SessionFeature features = new SessionFeature();
 
 	private String ppAuthorName;
@@ -71,7 +71,7 @@ public class Session implements Entity {
 		copy.courseType = original.courseType;
 		copy.courseId = original.courseId;
 		copy.creationTime = original.creationTime;
-		copy.learningProgressOptions = new LearningProgressOptions(original.learningProgressOptions);
+		copy.learningProgressOptions = new ScoreOptions(original.learningProgressOptions);
 		copy.features = new SessionFeature(original.features);
 		// public pool
 		copy.ppAuthorName = original.ppAuthorName;
@@ -220,14 +220,14 @@ public class Session implements Entity {
 		this.creationTime = creationTime;
 	}
 
-	@ApiModelProperty(required = true, value = "the learning progress options")
+	@ApiModelProperty(required = true, value = "the score options")
 	@JsonView({View.Persistence.class, View.Public.class})
-	public LearningProgressOptions getLearningProgressOptions() {
+	public ScoreOptions getLearningProgressOptions() {
 		return learningProgressOptions;
 	}
 
 	@JsonView({View.Persistence.class, View.Public.class})
-	public void setLearningProgressOptions(LearningProgressOptions learningProgressOptions) {
+	public void setLearningProgressOptions(ScoreOptions learningProgressOptions) {
 		this.learningProgressOptions = learningProgressOptions;
 	}
 
diff --git a/src/main/java/de/thm/arsnova/entities/SessionFeature.java b/src/main/java/de/thm/arsnova/entities/SessionFeature.java
index e04390add..618727cd6 100644
--- a/src/main/java/de/thm/arsnova/entities/SessionFeature.java
+++ b/src/main/java/de/thm/arsnova/entities/SessionFeature.java
@@ -128,7 +128,7 @@ public class SessionFeature implements Serializable {
 		this.pi = pi;
 	}
 
-	@ApiModelProperty(required = true, value = "learning progress")
+	@ApiModelProperty(required = true, value = "score")
 	@JsonView({View.Persistence.class, View.Public.class})
 	public boolean isLearningProgress() {
 		return learningProgress;
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 de074d3e1..02e1f2fe4 100644
--- a/src/main/java/de/thm/arsnova/entities/transport/Answer.java
+++ b/src/main/java/de/thm/arsnova/entities/transport/Answer.java
@@ -139,7 +139,7 @@ public class Answer implements Serializable {
 		theAnswer.setTimestamp(new Date().getTime());
 		theAnswer.setQuestionVariant(content.getQuestionVariant());
 		theAnswer.setAbstention(this.isAbstention());
-		// calculate learning progress value after all properties are set
+		// calculate score value after all properties are set
 		theAnswer.setQuestionValue(content.calculateValue(theAnswer));
 		theAnswer.setAnswerImage(this.getAnswerImage());
 		theAnswer.setSuccessfulFreeTextAnswer(this.isSuccessfulFreeTextAnswer());
diff --git a/src/main/java/de/thm/arsnova/entities/transport/LearningProgressOptions.java b/src/main/java/de/thm/arsnova/entities/transport/ScoreOptions.java
similarity index 78%
rename from src/main/java/de/thm/arsnova/entities/transport/LearningProgressOptions.java
rename to src/main/java/de/thm/arsnova/entities/transport/ScoreOptions.java
index 2bdc44886..5bf0b49a8 100644
--- a/src/main/java/de/thm/arsnova/entities/transport/LearningProgressOptions.java
+++ b/src/main/java/de/thm/arsnova/entities/transport/ScoreOptions.java
@@ -18,9 +18,9 @@
 package de.thm.arsnova.entities.transport;
 
 /**
- * A session's settings regarding the calculation of the learning progress.
+ * A session's settings regarding the calculation of the score.
  */
-public class LearningProgressOptions {
+public class ScoreOptions {
 
 	private String sessionKeyword;
 
@@ -40,8 +40,8 @@ public class LearningProgressOptions {
 		return type;
 	}
 
-	public void setType(String learningProgressType) {
-		this.type = learningProgressType;
+	public void setType(String type) {
+		this.type = type;
 	}
 
 	public String getQuestionVariant() {
@@ -52,8 +52,8 @@ public class LearningProgressOptions {
 		this.questionVariant = questionVariant;
 	}
 
-	public de.thm.arsnova.entities.LearningProgressOptions toEntity() {
-		de.thm.arsnova.entities.LearningProgressOptions entity = new de.thm.arsnova.entities.LearningProgressOptions();
+	public de.thm.arsnova.entities.ScoreOptions toEntity() {
+		de.thm.arsnova.entities.ScoreOptions entity = new de.thm.arsnova.entities.ScoreOptions();
 		entity.setType(this.getType());
 		entity.setQuestionVariant(this.getQuestionVariant());
 		return entity;
diff --git a/src/main/java/de/thm/arsnova/entities/transport/LearningProgressValues.java b/src/main/java/de/thm/arsnova/entities/transport/ScoreStatistics.java
similarity index 93%
rename from src/main/java/de/thm/arsnova/entities/transport/LearningProgressValues.java
rename to src/main/java/de/thm/arsnova/entities/transport/ScoreStatistics.java
index 686a733d0..1bb9af1a6 100644
--- a/src/main/java/de/thm/arsnova/entities/transport/LearningProgressValues.java
+++ b/src/main/java/de/thm/arsnova/entities/transport/ScoreStatistics.java
@@ -23,10 +23,10 @@ import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 
 /**
- * The calculated learning progress along with meta-data.
+ * The calculated score along with meta-data.
  */
-@ApiModel(value = "session/{sessionkey}/learningprogress", description = "the learning progress API")
-public class LearningProgressValues {
+@ApiModel(value = "session/{sessionkey}/learningprogress", description = "the score API")
+public class ScoreStatistics {
 
 	private int courseProgress;
 
@@ -40,7 +40,7 @@ public class LearningProgressValues {
 
 	private int numUsers;
 
-	@ApiModelProperty(required = true, value = "used to display course progress")
+	@ApiModelProperty(required = true, value = "used to display course score")
 	@JsonView(View.Public.class)
 	public int getCourseProgress() {
 		return courseProgress;
@@ -50,7 +50,7 @@ public class LearningProgressValues {
 		this.courseProgress = courseProgress;
 	}
 
-	@ApiModelProperty(required = true, value = "used to display my progress")
+	@ApiModelProperty(required = true, value = "used to display my score")
 	@JsonView(View.Public.class)
 	public int getMyProgress() {
 		return myProgress;
@@ -121,7 +121,7 @@ public class LearningProgressValues {
 		if (getClass() != obj.getClass()) {
 			return false;
 		}
-		LearningProgressValues other = (LearningProgressValues) obj;
+		ScoreStatistics other = (ScoreStatistics) obj;
 		if (courseProgress != other.courseProgress) {
 			return false;
 		}
diff --git a/src/main/java/de/thm/arsnova/events/NovaEvent.java b/src/main/java/de/thm/arsnova/events/ArsnovaEvent.java
similarity index 85%
rename from src/main/java/de/thm/arsnova/events/NovaEvent.java
rename to src/main/java/de/thm/arsnova/events/ArsnovaEvent.java
index 818113b28..a647eeb35 100644
--- a/src/main/java/de/thm/arsnova/events/NovaEvent.java
+++ b/src/main/java/de/thm/arsnova/events/ArsnovaEvent.java
@@ -22,14 +22,14 @@ import org.springframework.context.ApplicationEvent;
 /**
  * Base class of an ARSnova event.
  */
-public abstract class NovaEvent extends ApplicationEvent {
+public abstract class ArsnovaEvent extends ApplicationEvent {
 
 	private static final long serialVersionUID = 1L;
 
-	public NovaEvent(Object source) {
+	public ArsnovaEvent(Object source) {
 		super(source);
 	}
 
-	public abstract void accept(NovaEventVisitor visitor);
+	public abstract void accept(ArsnovaEventVisitor visitor);
 
 }
diff --git a/src/main/java/de/thm/arsnova/events/NovaEventVisitor.java b/src/main/java/de/thm/arsnova/events/ArsnovaEventVisitor.java
similarity index 96%
rename from src/main/java/de/thm/arsnova/events/NovaEventVisitor.java
rename to src/main/java/de/thm/arsnova/events/ArsnovaEventVisitor.java
index f02c32154..6e09b1a9c 100644
--- a/src/main/java/de/thm/arsnova/events/NovaEventVisitor.java
+++ b/src/main/java/de/thm/arsnova/events/ArsnovaEventVisitor.java
@@ -20,7 +20,7 @@ package de.thm.arsnova.events;
 /**
  * Listeners wanting to receive ARSnova's internal events should implement this interface.
  */
-public interface NovaEventVisitor {
+public interface ArsnovaEventVisitor {
 
 	void visit(NewCommentEvent newCommentEvent);
 
@@ -56,7 +56,7 @@ public interface NovaEventVisitor {
 
 	void visit(StatusSessionEvent statusSessionEvent);
 
-	void visit(ChangeLearningProgressEvent changeLearningProgress);
+	void visit(ChangeScoreEvent changeLearningProgress);
 
 	void visit(PiRoundDelayedStartEvent piRoundDelayedStartEvent);
 
diff --git a/src/main/java/de/thm/arsnova/events/ChangeLearningProgressEvent.java b/src/main/java/de/thm/arsnova/events/ChangeScoreEvent.java
similarity index 79%
rename from src/main/java/de/thm/arsnova/events/ChangeLearningProgressEvent.java
rename to src/main/java/de/thm/arsnova/events/ChangeScoreEvent.java
index 60c104ab2..ef8e5f944 100644
--- a/src/main/java/de/thm/arsnova/events/ChangeLearningProgressEvent.java
+++ b/src/main/java/de/thm/arsnova/events/ChangeScoreEvent.java
@@ -20,18 +20,18 @@ package de.thm.arsnova.events;
 import de.thm.arsnova.entities.Session;
 
 /**
- * Fires whenever a learning progress related value changes.
+ * Fires whenever a score related value changes.
  */
-public class ChangeLearningProgressEvent extends SessionEvent {
+public class ChangeScoreEvent extends SessionEvent {
 
 	private static final long serialVersionUID = 1L;
 
-	public ChangeLearningProgressEvent(Object source, Session session) {
+	public ChangeScoreEvent(Object source, Session session) {
 		super(source, session);
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/DeleteAllLectureAnswersEvent.java b/src/main/java/de/thm/arsnova/events/DeleteAllLectureAnswersEvent.java
index 14848eec7..488255963 100644
--- a/src/main/java/de/thm/arsnova/events/DeleteAllLectureAnswersEvent.java
+++ b/src/main/java/de/thm/arsnova/events/DeleteAllLectureAnswersEvent.java
@@ -31,7 +31,7 @@ public class DeleteAllLectureAnswersEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/DeleteAllPreparationAnswersEvent.java b/src/main/java/de/thm/arsnova/events/DeleteAllPreparationAnswersEvent.java
index 1321bc33f..df3b433d1 100644
--- a/src/main/java/de/thm/arsnova/events/DeleteAllPreparationAnswersEvent.java
+++ b/src/main/java/de/thm/arsnova/events/DeleteAllPreparationAnswersEvent.java
@@ -31,7 +31,7 @@ public class DeleteAllPreparationAnswersEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/events/DeleteAllQuestionsAnswersEvent.java b/src/main/java/de/thm/arsnova/events/DeleteAllQuestionsAnswersEvent.java
index 7c772bc46..0fbfe9639 100644
--- a/src/main/java/de/thm/arsnova/events/DeleteAllQuestionsAnswersEvent.java
+++ b/src/main/java/de/thm/arsnova/events/DeleteAllQuestionsAnswersEvent.java
@@ -31,7 +31,7 @@ public class DeleteAllQuestionsAnswersEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/events/DeleteAllQuestionsEvent.java b/src/main/java/de/thm/arsnova/events/DeleteAllQuestionsEvent.java
index 0ee77d87c..8eb0c43ca 100644
--- a/src/main/java/de/thm/arsnova/events/DeleteAllQuestionsEvent.java
+++ b/src/main/java/de/thm/arsnova/events/DeleteAllQuestionsEvent.java
@@ -32,7 +32,7 @@ public class DeleteAllQuestionsEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/DeleteAnswerEvent.java b/src/main/java/de/thm/arsnova/events/DeleteAnswerEvent.java
index 55970e207..b47ae62d1 100644
--- a/src/main/java/de/thm/arsnova/events/DeleteAnswerEvent.java
+++ b/src/main/java/de/thm/arsnova/events/DeleteAnswerEvent.java
@@ -35,7 +35,7 @@ public class DeleteAnswerEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/DeleteCommentEvent.java b/src/main/java/de/thm/arsnova/events/DeleteCommentEvent.java
index a22b545aa..545412632 100644
--- a/src/main/java/de/thm/arsnova/events/DeleteCommentEvent.java
+++ b/src/main/java/de/thm/arsnova/events/DeleteCommentEvent.java
@@ -35,7 +35,7 @@ public class DeleteCommentEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/DeleteFeedbackForSessionsEvent.java b/src/main/java/de/thm/arsnova/events/DeleteFeedbackForSessionsEvent.java
index a416b205d..f8f3f4994 100644
--- a/src/main/java/de/thm/arsnova/events/DeleteFeedbackForSessionsEvent.java
+++ b/src/main/java/de/thm/arsnova/events/DeleteFeedbackForSessionsEvent.java
@@ -25,7 +25,7 @@ import java.util.Set;
 /**
  * Fires whenever the feedback of a specific user has been reset.
  */
-public class DeleteFeedbackForSessionsEvent extends NovaEvent {
+public class DeleteFeedbackForSessionsEvent extends ArsnovaEvent {
 
 	private static final long serialVersionUID = 1L;
 
@@ -48,7 +48,7 @@ public class DeleteFeedbackForSessionsEvent extends NovaEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/DeleteQuestionEvent.java b/src/main/java/de/thm/arsnova/events/DeleteQuestionEvent.java
index 0204ee5b7..e2c1d7eb5 100644
--- a/src/main/java/de/thm/arsnova/events/DeleteQuestionEvent.java
+++ b/src/main/java/de/thm/arsnova/events/DeleteQuestionEvent.java
@@ -39,7 +39,7 @@ public class DeleteQuestionEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/DeleteSessionEvent.java b/src/main/java/de/thm/arsnova/events/DeleteSessionEvent.java
index 3bffe73bb..94f621e1a 100644
--- a/src/main/java/de/thm/arsnova/events/DeleteSessionEvent.java
+++ b/src/main/java/de/thm/arsnova/events/DeleteSessionEvent.java
@@ -32,7 +32,7 @@ public class DeleteSessionEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/FeatureChangeEvent.java b/src/main/java/de/thm/arsnova/events/FeatureChangeEvent.java
index 40e084625..f8b062fa4 100644
--- a/src/main/java/de/thm/arsnova/events/FeatureChangeEvent.java
+++ b/src/main/java/de/thm/arsnova/events/FeatureChangeEvent.java
@@ -31,7 +31,7 @@ public class FeatureChangeEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/FlipFlashcardsEvent.java b/src/main/java/de/thm/arsnova/events/FlipFlashcardsEvent.java
index 78d7688fb..023378e35 100644
--- a/src/main/java/de/thm/arsnova/events/FlipFlashcardsEvent.java
+++ b/src/main/java/de/thm/arsnova/events/FlipFlashcardsEvent.java
@@ -31,7 +31,7 @@ public class FlipFlashcardsEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/events/LockFeedbackEvent.java b/src/main/java/de/thm/arsnova/events/LockFeedbackEvent.java
index c6330eb6e..fa19651f6 100644
--- a/src/main/java/de/thm/arsnova/events/LockFeedbackEvent.java
+++ b/src/main/java/de/thm/arsnova/events/LockFeedbackEvent.java
@@ -31,7 +31,7 @@ public class LockFeedbackEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/events/LockQuestionEvent.java b/src/main/java/de/thm/arsnova/events/LockQuestionEvent.java
index 8371c8b01..d9b3a11b3 100644
--- a/src/main/java/de/thm/arsnova/events/LockQuestionEvent.java
+++ b/src/main/java/de/thm/arsnova/events/LockQuestionEvent.java
@@ -39,7 +39,7 @@ public class LockQuestionEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/LockQuestionsEvent.java b/src/main/java/de/thm/arsnova/events/LockQuestionsEvent.java
index 2981f4e18..b5e792ede 100644
--- a/src/main/java/de/thm/arsnova/events/LockQuestionsEvent.java
+++ b/src/main/java/de/thm/arsnova/events/LockQuestionsEvent.java
@@ -41,7 +41,7 @@ public class LockQuestionsEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/LockVoteEvent.java b/src/main/java/de/thm/arsnova/events/LockVoteEvent.java
index e3e9f000f..e18457bd0 100644
--- a/src/main/java/de/thm/arsnova/events/LockVoteEvent.java
+++ b/src/main/java/de/thm/arsnova/events/LockVoteEvent.java
@@ -58,7 +58,7 @@ public class LockVoteEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/events/LockVotesEvent.java b/src/main/java/de/thm/arsnova/events/LockVotesEvent.java
index 6f8043c18..5a08c87a3 100644
--- a/src/main/java/de/thm/arsnova/events/LockVotesEvent.java
+++ b/src/main/java/de/thm/arsnova/events/LockVotesEvent.java
@@ -41,7 +41,7 @@ public class LockVotesEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/NewAnswerEvent.java b/src/main/java/de/thm/arsnova/events/NewAnswerEvent.java
index 9172a5b5a..afb0e6a70 100644
--- a/src/main/java/de/thm/arsnova/events/NewAnswerEvent.java
+++ b/src/main/java/de/thm/arsnova/events/NewAnswerEvent.java
@@ -43,7 +43,7 @@ public class NewAnswerEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/NewCommentEvent.java b/src/main/java/de/thm/arsnova/events/NewCommentEvent.java
index 8330cbcd5..8b205b401 100644
--- a/src/main/java/de/thm/arsnova/events/NewCommentEvent.java
+++ b/src/main/java/de/thm/arsnova/events/NewCommentEvent.java
@@ -39,7 +39,7 @@ public class NewCommentEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/NewFeedbackEvent.java b/src/main/java/de/thm/arsnova/events/NewFeedbackEvent.java
index f4a88dbf8..10cd6fe9b 100644
--- a/src/main/java/de/thm/arsnova/events/NewFeedbackEvent.java
+++ b/src/main/java/de/thm/arsnova/events/NewFeedbackEvent.java
@@ -31,7 +31,7 @@ public class NewFeedbackEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/NewQuestionEvent.java b/src/main/java/de/thm/arsnova/events/NewQuestionEvent.java
index 1a9e1616e..07e93e03e 100644
--- a/src/main/java/de/thm/arsnova/events/NewQuestionEvent.java
+++ b/src/main/java/de/thm/arsnova/events/NewQuestionEvent.java
@@ -39,7 +39,7 @@ public class NewQuestionEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/events/NewSessionEvent.java b/src/main/java/de/thm/arsnova/events/NewSessionEvent.java
index ca8115e50..5bb289384 100644
--- a/src/main/java/de/thm/arsnova/events/NewSessionEvent.java
+++ b/src/main/java/de/thm/arsnova/events/NewSessionEvent.java
@@ -31,7 +31,7 @@ public class NewSessionEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/PiRoundCancelEvent.java b/src/main/java/de/thm/arsnova/events/PiRoundCancelEvent.java
index 64b51aea8..6833d7a03 100644
--- a/src/main/java/de/thm/arsnova/events/PiRoundCancelEvent.java
+++ b/src/main/java/de/thm/arsnova/events/PiRoundCancelEvent.java
@@ -32,7 +32,7 @@ public class PiRoundCancelEvent extends PiRoundEndEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/PiRoundDelayedStartEvent.java b/src/main/java/de/thm/arsnova/events/PiRoundDelayedStartEvent.java
index dfaed2bb4..c99799502 100644
--- a/src/main/java/de/thm/arsnova/events/PiRoundDelayedStartEvent.java
+++ b/src/main/java/de/thm/arsnova/events/PiRoundDelayedStartEvent.java
@@ -46,7 +46,7 @@ public class PiRoundDelayedStartEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/PiRoundEndEvent.java b/src/main/java/de/thm/arsnova/events/PiRoundEndEvent.java
index 335345185..c6d6c240c 100644
--- a/src/main/java/de/thm/arsnova/events/PiRoundEndEvent.java
+++ b/src/main/java/de/thm/arsnova/events/PiRoundEndEvent.java
@@ -40,7 +40,7 @@ public class PiRoundEndEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/PiRoundResetEvent.java b/src/main/java/de/thm/arsnova/events/PiRoundResetEvent.java
index 96bd50df0..0182fec22 100644
--- a/src/main/java/de/thm/arsnova/events/PiRoundResetEvent.java
+++ b/src/main/java/de/thm/arsnova/events/PiRoundResetEvent.java
@@ -40,7 +40,7 @@ public class PiRoundResetEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/SessionEvent.java b/src/main/java/de/thm/arsnova/events/SessionEvent.java
index 00ea28c42..66200d7de 100644
--- a/src/main/java/de/thm/arsnova/events/SessionEvent.java
+++ b/src/main/java/de/thm/arsnova/events/SessionEvent.java
@@ -20,9 +20,9 @@ package de.thm.arsnova.events;
 import de.thm.arsnova.entities.Session;
 
 /**
- * Base class for all {@link NovaEvent}s that are related to a session.
+ * Base class for all {@link ArsnovaEvent}s that are related to a session.
  */
-public abstract class SessionEvent extends NovaEvent {
+public abstract class SessionEvent extends ArsnovaEvent {
 
 	private static final long serialVersionUID = 1L;
 
diff --git a/src/main/java/de/thm/arsnova/events/StatusSessionEvent.java b/src/main/java/de/thm/arsnova/events/StatusSessionEvent.java
index 32c575dad..751aebee8 100644
--- a/src/main/java/de/thm/arsnova/events/StatusSessionEvent.java
+++ b/src/main/java/de/thm/arsnova/events/StatusSessionEvent.java
@@ -31,7 +31,7 @@ public class StatusSessionEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/UnlockQuestionEvent.java b/src/main/java/de/thm/arsnova/events/UnlockQuestionEvent.java
index 1d8e24c61..8401e0845 100644
--- a/src/main/java/de/thm/arsnova/events/UnlockQuestionEvent.java
+++ b/src/main/java/de/thm/arsnova/events/UnlockQuestionEvent.java
@@ -39,7 +39,7 @@ public class UnlockQuestionEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/UnlockQuestionsEvent.java b/src/main/java/de/thm/arsnova/events/UnlockQuestionsEvent.java
index 153b702a6..72fbe8e98 100644
--- a/src/main/java/de/thm/arsnova/events/UnlockQuestionsEvent.java
+++ b/src/main/java/de/thm/arsnova/events/UnlockQuestionsEvent.java
@@ -41,7 +41,7 @@ public class UnlockQuestionsEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/events/UnlockVoteEvent.java b/src/main/java/de/thm/arsnova/events/UnlockVoteEvent.java
index 00bf47a4d..5ec264092 100644
--- a/src/main/java/de/thm/arsnova/events/UnlockVoteEvent.java
+++ b/src/main/java/de/thm/arsnova/events/UnlockVoteEvent.java
@@ -58,7 +58,7 @@ public class UnlockVoteEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/events/UnlockVotesEvent.java b/src/main/java/de/thm/arsnova/events/UnlockVotesEvent.java
index 2dd3d4671..1f834846f 100644
--- a/src/main/java/de/thm/arsnova/events/UnlockVotesEvent.java
+++ b/src/main/java/de/thm/arsnova/events/UnlockVotesEvent.java
@@ -41,7 +41,7 @@ public class UnlockVotesEvent extends SessionEvent {
 	}
 
 	@Override
-	public void accept(NovaEventVisitor visitor) {
+	public void accept(ArsnovaEventVisitor visitor) {
 		visitor.visit(this);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/persistance/SessionStatisticsRepository.java b/src/main/java/de/thm/arsnova/persistance/SessionStatisticsRepository.java
index 193da0333..579128041 100644
--- a/src/main/java/de/thm/arsnova/persistance/SessionStatisticsRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/SessionStatisticsRepository.java
@@ -1,8 +1,8 @@
 package de.thm.arsnova.persistance;
 
-import de.thm.arsnova.domain.CourseScore;
+import de.thm.arsnova.services.score.Score;
 import de.thm.arsnova.entities.Session;
 
 public interface SessionStatisticsRepository {
-	CourseScore getLearningProgress(Session session);
+	Score getLearningProgress(Session session);
 }
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java
index b0ee9bf42..e3df5f055 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java
@@ -19,7 +19,7 @@ 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 de.thm.arsnova.services.SessionService;
 import org.ektorp.CouchDbConnector;
 import org.ektorp.support.CouchDbRepositorySupport;
 import org.slf4j.Logger;
@@ -35,7 +35,7 @@ public class CouchDbMotdRepository extends CouchDbRepositorySupport<Motd> implem
 	private static final Logger logger = LoggerFactory.getLogger(CouchDbMotdRepository.class);
 
 	@Autowired
-	private ISessionService sessionService;
+	private SessionService sessionService;
 
 	public CouchDbMotdRepository(CouchDbConnector db, boolean createIfNotExists) {
 		super(Motd.class, db, createIfNotExists);
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java
index 2731d70a0..8a00fddfe 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java
@@ -29,7 +29,7 @@ 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 de.thm.arsnova.services.SessionService;
 import org.ektorp.ComplexKey;
 import org.ektorp.CouchDbConnector;
 import org.ektorp.DocumentNotFoundException;
@@ -59,7 +59,7 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 	private static final Logger logger = LoggerFactory.getLogger(CouchDbSessionRepository.class);
 
 	@Autowired
-	private ISessionService sessionService;
+	private SessionService sessionService;
 
 	@Autowired
 	private LogEntryRepository dbLogger;
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionStatisticsRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionStatisticsRepository.java
index 0d7bbcb1d..f98ea9e1a 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionStatisticsRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionStatisticsRepository.java
@@ -1,7 +1,7 @@
 package de.thm.arsnova.persistance.couchdb;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import de.thm.arsnova.domain.CourseScore;
+import de.thm.arsnova.services.score.Score;
 import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.persistance.SessionStatisticsRepository;
 import org.ektorp.ComplexKey;
@@ -17,14 +17,14 @@ public class CouchDbSessionStatisticsRepository extends CouchDbRepositorySupport
 
 	@Cacheable("learningprogress")
 	@Override
-	public CourseScore getLearningProgress(final Session session) {
+	public Score 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();
+		final Score courseScore = new Score();
 
 		// no results found
 		if (maximumValueResult.isEmpty() && answerSumResult.isEmpty()) {
diff --git a/src/main/java/de/thm/arsnova/CASLogoutSuccessHandler.java b/src/main/java/de/thm/arsnova/security/CasLogoutSuccessHandler.java
similarity index 92%
rename from src/main/java/de/thm/arsnova/CASLogoutSuccessHandler.java
rename to src/main/java/de/thm/arsnova/security/CasLogoutSuccessHandler.java
index 13eb93434..dbb451a6f 100644
--- a/src/main/java/de/thm/arsnova/CASLogoutSuccessHandler.java
+++ b/src/main/java/de/thm/arsnova/security/CasLogoutSuccessHandler.java
@@ -15,7 +15,7 @@
  * 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;
+package de.thm.arsnova.security;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -32,9 +32,9 @@ import java.io.IOException;
 /**
  * This class gets called when a user has been successfully logged out from CAS.
  */
-public class CASLogoutSuccessHandler implements LogoutSuccessHandler {
+public class CasLogoutSuccessHandler implements LogoutSuccessHandler {
 
-	private static final Logger logger = LoggerFactory.getLogger(CASLogoutSuccessHandler.class);
+	private static final Logger logger = LoggerFactory.getLogger(CasLogoutSuccessHandler.class);
 
 	private String casUrl;
 	private String defaultTarget;
diff --git a/src/main/java/de/thm/arsnova/CasUserDetailsService.java b/src/main/java/de/thm/arsnova/security/CasUserDetailsService.java
similarity index 98%
rename from src/main/java/de/thm/arsnova/CasUserDetailsService.java
rename to src/main/java/de/thm/arsnova/security/CasUserDetailsService.java
index 9261648fb..05bc988df 100644
--- a/src/main/java/de/thm/arsnova/CasUserDetailsService.java
+++ b/src/main/java/de/thm/arsnova/security/CasUserDetailsService.java
@@ -15,7 +15,7 @@
  * 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;
+package de.thm.arsnova.security;
 
 import org.jasig.cas.client.validation.Assertion;
 import org.springframework.security.cas.userdetails.AbstractCasAssertionUserDetailsService;
diff --git a/src/main/java/de/thm/arsnova/LoginAuthenticationFailureHandler.java b/src/main/java/de/thm/arsnova/security/LoginAuthenticationFailureHandler.java
similarity index 98%
rename from src/main/java/de/thm/arsnova/LoginAuthenticationFailureHandler.java
rename to src/main/java/de/thm/arsnova/security/LoginAuthenticationFailureHandler.java
index f3f429875..3a4e1c04d 100644
--- a/src/main/java/de/thm/arsnova/LoginAuthenticationFailureHandler.java
+++ b/src/main/java/de/thm/arsnova/security/LoginAuthenticationFailureHandler.java
@@ -15,7 +15,7 @@
  * 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;
+package de.thm.arsnova.security;
 
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.web.DefaultRedirectStrategy;
diff --git a/src/main/java/de/thm/arsnova/LoginAuthenticationSucessHandler.java b/src/main/java/de/thm/arsnova/security/LoginAuthenticationSucessHandler.java
similarity index 97%
rename from src/main/java/de/thm/arsnova/LoginAuthenticationSucessHandler.java
rename to src/main/java/de/thm/arsnova/security/LoginAuthenticationSucessHandler.java
index 714578ee6..b78b74627 100644
--- a/src/main/java/de/thm/arsnova/LoginAuthenticationSucessHandler.java
+++ b/src/main/java/de/thm/arsnova/security/LoginAuthenticationSucessHandler.java
@@ -15,7 +15,7 @@
  * 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;
+package de.thm.arsnova.security;
 
 import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
 
diff --git a/src/main/java/de/thm/arsnova/services/ContentService.java b/src/main/java/de/thm/arsnova/services/ContentService.java
index 2191d8cf7..9e861af00 100644
--- a/src/main/java/de/thm/arsnova/services/ContentService.java
+++ b/src/main/java/de/thm/arsnova/services/ContentService.java
@@ -17,1025 +17,155 @@
  */
 package de.thm.arsnova.services;
 
-import de.thm.arsnova.ImageUtils;
 import de.thm.arsnova.entities.Answer;
 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.*;
-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;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.ApplicationEventPublisher;
-import org.springframework.context.ApplicationEventPublisherAware;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.stereotype.Service;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
+
 import java.util.List;
 import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
 
 /**
- * Performs all question, comment, and answer related operations.
+ * The functionality the question service should provide.
  */
-@Service
-public class ContentService implements IContentService, ApplicationEventPublisherAware {
-	@Autowired
-	private IUserService userService;
-
-	@Autowired
-	private SessionRepository sessionRepository;
-
-	@Autowired
-	private CommentRepository commentRepository;
-
-	@Autowired
-	private ContentRepository contentRepository;
-
-	@Autowired
-	private AnswerRepository answerRepository;
-
-	@Autowired
-	private ImageUtils imageUtils;
-
-	@Value("${upload.filesize_b}")
-	private int uploadFileSizeByte;
-
-	private ApplicationEventPublisher publisher;
-
-	private static final Logger logger = LoggerFactory.getLogger(ContentService.class);
-
-	private HashMap<String, Timer> timerList = new HashMap<>();
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<Content> getSkillQuestions(final String sessionkey) {
-		final Session session = getSession(sessionkey);
-		final User user = userService.getCurrentUser();
-		if (session.isCreator(user)) {
-			return contentRepository.getSkillQuestionsForTeachers(session);
-		} else {
-			return contentRepository.getSkillQuestionsForUsers(session);
-		}
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public int getSkillQuestionCount(final String sessionkey) {
-		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(#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(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(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) ((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 Content result = contentRepository.saveQuestion(session, content);
-
-		final NewQuestionEvent event = new NewQuestionEvent(this, session, result);
-		this.publisher.publishEvent(event);
-
-		return result;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	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 NewCommentEvent event = new NewCommentEvent(this, session, result);
-			this.publisher.publishEvent(event);
-			return true;
-		}
-		return false;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public Content getQuestion(final String id) {
-		final Content result = contentRepository.getQuestion(id);
-		if (result == null) {
-			return null;
-		}
-		if (!"freetext".equals(result.getQuestionType()) && 0 == result.getPiRound()) {
-			/* needed for legacy questions whose piRound property has not been set */
-			result.setPiRound(1);
-		}
-
-		return result;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
-	public void deleteQuestion(final String questionId) {
-		final Content content = contentRepository.getQuestion(questionId);
-		if (content == null) {
-			throw new NotFoundException();
-		}
-
-		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
-		if (session == null) {
-			throw new UnauthorizedException();
-		}
-		contentRepository.deleteQuestionWithAnswers(content);
-
-		final DeleteQuestionEvent event = new DeleteQuestionEvent(this, session, content);
-		this.publisher.publishEvent(event);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#sessionKeyword, 'session', 'owner')")
-	public void deleteAllQuestions(final String sessionKeyword) {
-		final Session session = getSessionWithAuthCheck(sessionKeyword);
-		contentRepository.deleteAllQuestionsWithAnswers(session);
-
-		final DeleteAllQuestionsEvent event = new DeleteAllQuestionsEvent(this, session);
-		this.publisher.publishEvent(event);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
-	public void startNewPiRound(final String questionId, User user) {
-		final Content content = contentRepository.getQuestion(questionId);
-		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
-
-		if (null == user) {
-			user = userService.getCurrentUser();
-		}
-
-		cancelDelayedPiRoundChange(questionId);
-
-		content.setPiRoundEndTime(0);
-		content.setVotingDisabled(true);
-		content.updateRoundManagementState();
-		update(content, user);
-
-		this.publisher.publishEvent(new PiRoundEndEvent(this, session, content));
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
-	public void startNewPiRoundDelayed(final String questionId, final int time) {
-		final IContentService contentService = this;
-		final User user = userService.getCurrentUser();
-		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));
-		content.updateRoundStartVariables(date, endDate);
-		update(content);
-
-		this.publisher.publishEvent(new PiRoundDelayedStartEvent(this, session, content));
-		timerList.put(questionId, timer);
-
-		timer.schedule(new TimerTask() {
-			@Override
-			public void run() {
-				contentService.startNewPiRound(questionId, user);
-			}
-		}, endDate);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
-	public void cancelPiRoundChange(final String questionId) {
-		final Content content = contentRepository.getQuestion(questionId);
-		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
-
-		cancelDelayedPiRoundChange(questionId);
-		content.resetRoundManagementState();
-
-		if (0 == content.getPiRound() || 1 == content.getPiRound()) {
-			content.setPiRoundFinished(false);
-		} else {
-			content.setPiRound(1);
-			content.setPiRoundFinished(true);
-		}
-
-		update(content);
-		this.publisher.publishEvent(new PiRoundCancelEvent(this, session, content));
-	}
-
-	@Override
-	public void cancelDelayedPiRoundChange(final String questionId) {
-		Timer timer = timerList.get(questionId);
-
-		if (null != timer) {
-			timer.cancel();
-			timerList.remove(questionId);
-			timer.purge();
-		}
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
-	public void resetPiRoundState(final String questionId) {
-		final Content content = contentRepository.getQuestion(questionId);
-		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
-		cancelDelayedPiRoundChange(questionId);
-
-		if ("freetext".equals(content.getQuestionType())) {
-			content.setPiRound(0);
-		} else {
-			content.setPiRound(1);
-		}
-
-		content.resetRoundManagementState();
-		answerRepository.deleteAnswers(content);
-		update(content);
-		this.publisher.publishEvent(new PiRoundResetEvent(this, session, content));
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
-	public void setVotingAdmission(final String questionId, final boolean disableVoting) {
-		final Content content = contentRepository.getQuestion(questionId);
-		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
-		content.setVotingDisabled(disableVoting);
-
-		if (!disableVoting && !content.isActive()) {
-			content.setActive(true);
-			update(content);
-		} else {
-			contentRepository.updateQuestion(content);
-		}
-		NovaEvent event;
-		if (disableVoting) {
-			event = new LockVoteEvent(this, session, content);
-		} else {
-			event = new UnlockVoteEvent(this, session, content);
-		}
-		this.publisher.publishEvent(event);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	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();
-		}
-		contentRepository.setVotingAdmissions(session, disableVoting, contents);
-		NovaEvent event;
-		if (disableVoting) {
-			event = new LockVotesEvent(this, session, contents);
-		} else {
-			event = new UnlockVotesEvent(this, session, contents);
-		}
-		this.publisher.publishEvent(event);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public void setVotingAdmissionForAllQuestions(final String sessionkey, final boolean disableVoting) {
-		final User user = getCurrentUser();
-		final Session session = getSession(sessionkey);
-		if (!session.isCreator(user)) {
-			throw new UnauthorizedException();
-		}
-		final List<Content> contents = contentRepository.setVotingAdmissionForAllQuestions(session, disableVoting);
-		NovaEvent event;
-		if (disableVoting) {
-			event = new LockVotesEvent(this, session, contents);
-		} else {
-			event = new UnlockVotesEvent(this, session, contents);
-		}
-		this.publisher.publishEvent(event);
-	}
-
-	private Session getSessionWithAuthCheck(final String sessionKeyword) {
-		final User user = userService.getCurrentUser();
-		final Session session = sessionRepository.getSessionFromKeyword(sessionKeyword);
-		if (user == null || session == null || !session.isCreator(user)) {
-			throw new UnauthorizedException();
-		}
-		return session;
-	}
-
-	@Override
-	@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();
-		}
-		commentRepository.deleteInterposedQuestion(comment);
-
-		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 = sessionRepository.getSessionFromKeyword(sessionKeyword);
-		if (session == null) {
-			throw new UnauthorizedException();
-		}
-		final User user = getCurrentUser();
-		if (session.isCreator(user)) {
-			commentRepository.deleteAllInterposedQuestions(session);
-		} else {
-			commentRepository.deleteAllInterposedQuestions(session, user);
-		}
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
-	public void deleteAnswers(final String questionId) {
-		final Content content = contentRepository.getQuestion(questionId);
-		content.resetQuestionState();
-		contentRepository.updateQuestion(content);
-		answerRepository.deleteAnswers(content);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<String> getUnAnsweredQuestionIds(final String sessionKey) {
-		final User user = getCurrentUser();
-		final Session session = getSession(sessionKey);
-		return contentRepository.getUnAnsweredQuestionIds(session, user);
-	}
-
-	private User getCurrentUser() {
-		final User user = userService.getCurrentUser();
-		if (user == null) {
-			throw new UnauthorizedException();
-		}
-		return user;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public Answer getMyAnswer(final String questionId) {
-		final Content content = getQuestion(questionId);
-		if (content == null) {
-			throw new NotFoundException();
-		}
-		return answerRepository.getMyAnswer(userService.getCurrentUser(), questionId, content.getPiRound());
-	}
-
-	@Override
-	public void readFreetextAnswer(final String answerId, final User user) {
-		final Answer answer = answerRepository.get(answerId);
-		if (answer == null) {
-			throw new NotFoundException();
-		}
-		if (answer.isRead()) {
-			return;
-		}
-		final Session session = sessionRepository.getSessionFromId(answer.getSessionId());
-		if (session.isCreator(user)) {
-			answer.setRead(true);
-			answerRepository.updateAnswer(answer);
-		}
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<Answer> getAnswers(final String questionId, final int piRound, final int offset, final int limit) {
-		final Content content = contentRepository.getQuestion(questionId);
-		if (content == null) {
-			throw new NotFoundException();
-		}
-		return "freetext".equals(content.getQuestionType())
-				? getFreetextAnswers(questionId, offset, limit)
-						: answerRepository.getAnswers(content, piRound);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<Answer> getAnswers(final String questionId, final int offset, final int limit) {
-		final Content content = getQuestion(questionId);
-		if (content == null) {
-			throw new NotFoundException();
-		}
-		if ("freetext".equals(content.getQuestionType())) {
-			return getFreetextAnswers(questionId, offset, limit);
-		} else {
-			return answerRepository.getAnswers(content);
-		}
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<Answer> getAllAnswers(final String questionId, final int offset, final int limit) {
-		final Content content = getQuestion(questionId);
-		if (content == null) {
-			throw new NotFoundException();
-		}
-		if ("freetext".equals(content.getQuestionType())) {
-			return getFreetextAnswers(questionId, offset, limit);
-		} else {
-			return answerRepository.getAllAnswers(content);
-		}
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public int getAnswerCount(final String questionId) {
-		final Content content = getQuestion(questionId);
-		if (content == null) {
-			return 0;
-		}
-
-		if ("freetext".equals(content.getQuestionType())) {
-			return answerRepository.getTotalAnswerCountByQuestion(content);
-		} else {
-			return answerRepository.getAnswerCount(content, content.getPiRound());
-		}
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public int getAnswerCount(final String questionId, final int piRound) {
-		final Content content = getQuestion(questionId);
-		if (content == null) {
-			return 0;
-		}
-
-		return answerRepository.getAnswerCount(content, piRound);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public int getAbstentionAnswerCount(final String questionId) {
-		final Content content = getQuestion(questionId);
-		if (content == null) {
-			return 0;
-		}
-
-		return answerRepository.getAbstentionAnswerCount(questionId);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public int getTotalAnswerCountByQuestion(final String questionId) {
-		final Content content = getQuestion(questionId);
-		if (content == null) {
-			return 0;
-		}
-
-		return answerRepository.getTotalAnswerCountByQuestion(content);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<Answer> getFreetextAnswers(final String questionId, final int offset, final int limit) {
-		final List<Answer> answers = answerRepository.getFreetextAnswers(questionId, offset, limit);
-		if (answers == null) {
-			throw new NotFoundException();
-		}
-		/* Remove user for privacy concerns */
-		for (Answer answer : answers) {
-			answer.setUser(null);
-		}
-
-		return answers;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<Answer> getMyAnswers(final String sessionKey) {
-		final Session session = getSession(sessionKey);
-		// 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 = answerRepository.getMyAnswers(userService.getCurrentUser(), session);
-		final List<Answer> filteredAnswers = new ArrayList<>();
-		for (final Answer answer : answers) {
-			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(content.getQuestionType())) {
-				answer.setPiRound(1);
-			}
-
-			// discard all answers that aren't in the same piRound as the content
-			if (answer.getPiRound() == content.getPiRound()) {
-				filteredAnswers.add(answer);
-			}
-		}
-
-		return filteredAnswers;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public int getTotalAnswerCount(final String sessionKey) {
-		return answerRepository.getTotalAnswerCount(sessionKey);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public int getInterposedCount(final String sessionKey) {
-		return commentRepository.getInterposedCount(sessionKey);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public CommentReadingCount getInterposedReadingCount(final String sessionKey, String username) {
-		final Session session = sessionRepository.getSessionFromKeyword(sessionKey);
-		if (session == null) {
-			throw new NotFoundException();
-		}
-		if (username == null) {
-			return commentRepository.getInterposedReadingCount(session);
-		} else {
-			User currentUser = userService.getCurrentUser();
-			if (!currentUser.getUsername().equals(username)) {
-				throw new ForbiddenException();
-			}
-
-			return commentRepository.getInterposedReadingCount(session, currentUser);
-		}
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	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 commentRepository.getInterposedQuestions(session, offset, limit);
-		} else {
-			return commentRepository.getInterposedQuestions(session, user, offset, limit);
-		}
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public Comment readInterposedQuestion(final String commentId) {
-		final User user = userService.getCurrentUser();
-		return this.readInterposedQuestionInternal(commentId, user);
-	}
-
-	/*
-	 * The "internal" suffix means it is called by internal services that have no authentication!
-	 * TODO: Find a better way of doing this...
-	 */
-	@Override
-	public Comment readInterposedQuestionInternal(final String commentId, User user) {
-		final Comment comment = commentRepository.getInterposedQuestion(commentId);
-		if (comment == null) {
-			throw new NotFoundException();
-		}
-		final Session session = sessionRepository.getSessionFromId(comment.getSessionId());
-		if (!comment.isCreator(user) && !session.isCreator(user)) {
-			throw new UnauthorizedException();
-		}
-		if (session.isCreator(user)) {
-			commentRepository.markInterposedQuestionAsRead(comment);
-		}
-		return comment;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public Content update(final Content content) {
-		final User user = userService.getCurrentUser();
-		return update(content, user);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public Content update(final Content content, User user) {
-		final Content oldContent = contentRepository.getQuestion(content.getId());
-		if (null == oldContent) {
-			throw new NotFoundException();
-		}
-
-		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
-		if (user == null || session == null || !session.isCreator(user)) {
-			throw new UnauthorizedException();
-		}
-
-		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 Content result = contentRepository.updateQuestion(content);
-
-		if (!oldContent.isActive() && content.isActive()) {
-			final UnlockQuestionEvent event = new UnlockQuestionEvent(this, session, result);
-			this.publisher.publishEvent(event);
-		} else if (oldContent.isActive() && !content.isActive()) {
-			final LockQuestionEvent event = new LockQuestionEvent(this, session, result);
-			this.publisher.publishEvent(event);
-		}
-		return result;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public Answer saveAnswer(final String questionId, final de.thm.arsnova.entities.transport.Answer answer) {
-		final User user = getCurrentUser();
-		final Content content = getQuestion(questionId);
-		if (content == null) {
-			throw new NotFoundException();
-		}
-		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
-
-		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 (content.isFixedAnswer() && content.getText() != null) {
-				theAnswer.setAnswerTextRaw(theAnswer.getAnswerText());
-
-				if (content.isStrictMode()) {
-					content.checkTextStrictOptions(theAnswer);
-				}
-				theAnswer.setQuestionValue(content.evaluateCorrectAnswerFixedText(theAnswer.getAnswerTextRaw()));
-				theAnswer.setSuccessfulFreeTextAnswer(content.isSuccessfulFreeTextAnswer(theAnswer.getAnswerTextRaw()));
-			}
-		}
-
-		return answerRepository.saveAnswer(theAnswer, user, content, session);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public Answer updateAnswer(final Answer answer) {
-		final User user = userService.getCurrentUser();
-		final Answer realAnswer = this.getMyAnswer(answer.getQuestionId());
-		if (user == null || realAnswer == null || !user.getUsername().equals(realAnswer.getUser())) {
-			throw new UnauthorizedException();
-		}
-
-		final Content content = getQuestion(answer.getQuestionId());
-		if ("freetext".equals(content.getQuestionType())) {
-			imageUtils.generateThumbnailImage(realAnswer);
-			content.checkTextStrictOptions(realAnswer);
-		}
-		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;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public void deleteAnswer(final String questionId, final String answerId) {
-		final Content content = contentRepository.getQuestion(questionId);
-		if (content == null) {
-			throw new NotFoundException();
-		}
-		final User user = userService.getCurrentUser();
-		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
-		if (user == null || session == null || !session.isCreator(user)) {
-			throw new UnauthorizedException();
-		}
-		answerRepository.deleteAnswer(answerId);
-
-		this.publisher.publishEvent(new DeleteAnswerEvent(this, session, content));
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<Content> getLectureQuestions(final String sessionkey) {
-		final Session session = getSession(sessionkey);
-		final User user = userService.getCurrentUser();
-		if (session.isCreator(user)) {
-			return contentRepository.getLectureQuestionsForTeachers(session);
-		} else {
-			return contentRepository.getLectureQuestionsForUsers(session);
-		}
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<Content> getFlashcards(final String sessionkey) {
-		final Session session = getSession(sessionkey);
-		final User user = userService.getCurrentUser();
-		if (session.isCreator(user)) {
-			return contentRepository.getFlashcardsForTeachers(session);
-		} else {
-			return contentRepository.getFlashcardsForUsers(session);
-		}
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<Content> getPreparationQuestions(final String sessionkey) {
-		final Session session = getSession(sessionkey);
-		final User user = userService.getCurrentUser();
-		if (session.isCreator(user)) {
-			return contentRepository.getPreparationQuestionsForTeachers(session);
-		} else {
-			return contentRepository.getPreparationQuestionsForUsers(session);
-		}
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	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 contents;
-	}
-
-	private Session getSession(final String sessionkey) {
-		final Session session = sessionRepository.getSessionFromKeyword(sessionkey);
-		if (session == null) {
-			throw new NotFoundException();
-		}
-		return session;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public int getLectureQuestionCount(final String sessionkey) {
-		return contentRepository.getLectureQuestionCount(getSession(sessionkey));
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public int getFlashcardCount(final String sessionkey) {
-		return contentRepository.getFlashcardCount(getSession(sessionkey));
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public int getPreparationQuestionCount(final String sessionkey) {
-		return contentRepository.getPreparationQuestionCount(getSession(sessionkey));
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public int countLectureQuestionAnswers(final String sessionkey) {
-		return this.countLectureQuestionAnswersInternal(sessionkey);
-	}
-
-	/*
-	 * The "internal" suffix means it is called by internal services that have no authentication!
-	 * TODO: Find a better way of doing this...
-	 */
-	@Override
-	public int countLectureQuestionAnswersInternal(final String sessionkey) {
-		return answerRepository.countLectureQuestionAnswers(getSession(sessionkey));
-	}
-
-	@Override
-	public Map<String, Object> getAnswerAndAbstentionCountInternal(final String questionId) {
-		final Content content = getQuestion(questionId);
-		HashMap<String, Object> map = new HashMap<>();
-
-		if (content == null) {
-			return null;
-		}
-
-		map.put("_id", questionId);
-		map.put("answers", answerRepository.getAnswerCount(content, content.getPiRound()));
-		map.put("abstentions", answerRepository.getAbstentionAnswerCount(questionId));
-
-		return map;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public int countPreparationQuestionAnswers(final String sessionkey) {
-		return this.countPreparationQuestionAnswersInternal(sessionkey);
-	}
-
-	/*
-	 * The "internal" suffix means it is called by internal services that have no authentication!
-	 * TODO: Find a better way of doing this...
-	 */
-	@Override
-	public int countPreparationQuestionAnswersInternal(final String sessionkey) {
-		return answerRepository.countPreparationQuestionAnswers(getSession(sessionkey));
-	}
-
-	/*
-	 * The "internal" suffix means it is called by internal services that have no authentication!
-	 * TODO: Find a better way of doing this...
-	 */
-	@Override
-	public int countFlashcardsForUserInternal(final String sessionkey) {
-		return contentRepository.getFlashcardsForUsers(getSession(sessionkey)).size();
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public void deleteLectureQuestions(final String sessionkey) {
-		final Session session = getSessionWithAuthCheck(sessionkey);
-		contentRepository.deleteAllLectureQuestionsWithAnswers(session);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public void deleteFlashcards(final String sessionkey) {
-		final Session session = getSessionWithAuthCheck(sessionkey);
-		contentRepository.deleteAllFlashcardsWithAnswers(session);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public void deletePreparationQuestions(final String sessionkey) {
-		final Session session = getSessionWithAuthCheck(sessionkey);
-		contentRepository.deleteAllPreparationQuestionsWithAnswers(session);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<String> getUnAnsweredLectureQuestionIds(final String sessionkey) {
-		final User user = getCurrentUser();
-		return this.getUnAnsweredLectureQuestionIds(sessionkey, user);
-	}
-
-	@Override
-	public List<String> getUnAnsweredLectureQuestionIds(final String sessionkey, final User user) {
-		final Session session = getSession(sessionkey);
-		return contentRepository.getUnAnsweredLectureQuestionIds(session, user);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<String> getUnAnsweredPreparationQuestionIds(final String sessionkey) {
-		final User user = getCurrentUser();
-		return this.getUnAnsweredPreparationQuestionIds(sessionkey, user);
-	}
-
-	@Override
-	public List<String> getUnAnsweredPreparationQuestionIds(final String sessionkey, final User user) {
-		final Session session = getSession(sessionkey);
-		return contentRepository.getUnAnsweredPreparationQuestionIds(session, user);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public void publishAll(final String sessionkey, final boolean publish) {
-		final User user = getCurrentUser();
-		final Session session = getSession(sessionkey);
-		if (!session.isCreator(user)) {
-			throw new UnauthorizedException();
-		}
-		final List<Content> contents = contentRepository.publishAllQuestions(session, publish);
-		NovaEvent event;
-		if (publish) {
-			event = new UnlockQuestionsEvent(this, session, contents);
-		} else {
-			event = new LockQuestionsEvent(this, session, contents);
-		}
-		this.publisher.publishEvent(event);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	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();
-		}
-		contentRepository.publishQuestions(session, publish, contents);
-		NovaEvent event;
-		if (publish) {
-			event = new UnlockQuestionsEvent(this, session, contents);
-		} else {
-			event = new LockQuestionsEvent(this, session, contents);
-		}
-		this.publisher.publishEvent(event);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public void deleteAllQuestionsAnswers(final String sessionkey) {
-		final User user = getCurrentUser();
-		final Session session = getSession(sessionkey);
-		if (!session.isCreator(user)) {
-			throw new UnauthorizedException();
-		}
-		answerRepository.deleteAllQuestionsAnswers(session);
-
-		this.publisher.publishEvent(new DeleteAllQuestionsAnswersEvent(this, session));
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
-	public void deleteAllPreparationAnswers(String sessionkey) {
-		final Session session = getSession(sessionkey);
-		answerRepository.deleteAllPreparationAnswers(session);
-
-		this.publisher.publishEvent(new DeleteAllPreparationAnswersEvent(this, session));
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
-	public void deleteAllLectureAnswers(String sessionkey) {
-		final Session session = getSession(sessionkey);
-		answerRepository.deleteAllLectureAnswers(session);
-
-		this.publisher.publishEvent(new DeleteAllLectureAnswersEvent(this, session));
-	}
-
-	@Override
-	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
-		this.publisher = publisher;
-	}
-
-	@Override
-	public String getImage(String questionId, String answerId) {
-		final List<Answer> answers = getAnswers(questionId, -1, -1);
-		Answer answer = null;
-
-		for (Answer a : answers) {
-			if (answerId.equals(a.getId())) {
-				answer = a;
-				break;
-			}
-		}
-
-		if (answer == null) {
-			throw new NotFoundException();
-		}
-
-		return answer.getAnswerImage();
-	}
-
-	@Override
-	public String getQuestionImage(String questionId) {
-		Content content = contentRepository.getQuestion(questionId);
-		String imageData = content.getImage();
-
-		if (imageData == null) {
-			imageData = "";
-		}
-
-		return imageData;
-	}
-
-	@Override
-	public String getQuestionFcImage(String questionId) {
-		Content content = contentRepository.getQuestion(questionId);
-		String imageData = content.getFcImage();
-
-		if (imageData == null) {
-			imageData = "";
-		}
-
-		return imageData;
-	}
+public interface ContentService {
+	Content saveQuestion(Content content);
+
+	boolean saveQuestion(Comment comment);
+
+	Content getQuestion(String id);
+
+	List<Content> getSkillQuestions(String sessionkey);
+
+	int getSkillQuestionCount(String sessionkey);
+
+	void deleteQuestion(String questionId);
+
+	void deleteAllQuestions(String sessionKeyword);
+
+	void startNewPiRound(String questionId, User user);
+
+	void startNewPiRoundDelayed(String questionId, int time);
+
+	void cancelPiRoundChange(String questionId);
+
+	void cancelDelayedPiRoundChange(String questionId);
+
+	void resetPiRoundState(String questionId);
+
+	List<String> getUnAnsweredQuestionIds(String sessionKey);
+
+	Answer getMyAnswer(String questionId);
+
+	void readFreetextAnswer(String answerId, User user);
+
+	List<Answer> getAnswers(String questionId, int piRound, int offset, int limit);
+
+	List<Answer> getAnswers(String questionId, int offset, int limit);
+
+	List<Answer> getAllAnswers(String questionId, int offset, int limit);
+
+	int getAnswerCount(String questionId);
+
+	int getAnswerCount(String questionId, int piRound);
+
+	List<Answer> getFreetextAnswers(String questionId, int offset, int limit);
+
+	List<Answer> getMyAnswers(String sessionKey);
+
+	int getTotalAnswerCount(String sessionKey);
+
+	int getTotalAnswerCountByQuestion(String questionId);
+
+	int getInterposedCount(String sessionKey);
+
+	CommentReadingCount getInterposedReadingCount(String sessionKey, String username);
+
+	List<Comment> getInterposedQuestions(String sessionKey, int offset, int limit);
+
+	Comment readInterposedQuestion(String commentId);
+
+	Comment readInterposedQuestionInternal(String commentId, User user);
+
+	Content update(Content content);
+
+	Content update(Content content, User user);
+
+	void deleteAnswers(String questionId);
+
+	Answer saveAnswer(String questionId, de.thm.arsnova.entities.transport.Answer answer);
+
+	Answer updateAnswer(Answer answer);
+
+	void deleteAnswer(String questionId, String answerId);
+
+	void deleteInterposedQuestion(String commentId);
+
+	List<Content> getLectureQuestions(String sessionkey);
+
+	List<Content> getFlashcards(String sessionkey);
+
+	List<Content> getPreparationQuestions(String sessionkey);
+
+	int getLectureQuestionCount(String sessionkey);
+
+	int getFlashcardCount(String sessionkey);
+
+	int getPreparationQuestionCount(String sessionkey);
+
+	Map<String, Object> getAnswerAndAbstentionCountInternal(String questionid);
+
+	int countLectureQuestionAnswers(String sessionkey);
+
+	int countLectureQuestionAnswersInternal(String sessionkey);
+
+	int countPreparationQuestionAnswers(String sessionkey);
+
+	int countPreparationQuestionAnswersInternal(String sessionkey);
+
+	int countFlashcardsForUserInternal(String sessionkey);
+
+	void deleteLectureQuestions(String sessionkey);
+
+	void deleteFlashcards(String sessionkey);
+
+	void deletePreparationQuestions(String sessionkey);
+
+	List<String> getUnAnsweredLectureQuestionIds(String sessionkey);
+
+	List<String> getUnAnsweredLectureQuestionIds(String sessionKey, User user);
+
+	List<String> getUnAnsweredPreparationQuestionIds(String sessionkey);
+
+	List<String> getUnAnsweredPreparationQuestionIds(String sessionKey, User user);
+
+	void deleteAllInterposedQuestions(String sessionKeyword);
+
+	void publishAll(String sessionkey, boolean publish);
+
+	void publishQuestions(String sessionkey, boolean publish, List<Content> contents);
+
+	void deleteAllQuestionsAnswers(String sessionkey);
+
+	void deleteAllPreparationAnswers(String sessionkey);
+
+	void deleteAllLectureAnswers(String sessionkey);
+
+	int getAbstentionAnswerCount(String questionId);
+
+	String getImage(String questionId, String answerId);
+
+	void setVotingAdmission(String questionId, boolean disableVoting);
+
+	void setVotingAdmissions(String sessionkey, boolean disableVoting, List<Content> contents);
+
+	void setVotingAdmissionForAllQuestions(String sessionkey, boolean disableVoting);
+
+	String getQuestionImage(String questionId);
+
+	String getQuestionFcImage(String questionId);
+
+	List<Content> replaceImageData(List<Content> contents);
+
 }
diff --git a/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java b/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java
new file mode 100644
index 000000000..62580548f
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java
@@ -0,0 +1,1041 @@
+/*
+ * 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.util.ImageUtils;
+import de.thm.arsnova.entities.Answer;
+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.*;
+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;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * Performs all question, comment, and answer related operations.
+ */
+@Service
+public class ContentServiceImpl implements ContentService, ApplicationEventPublisherAware {
+	@Autowired
+	private UserService userService;
+
+	@Autowired
+	private SessionRepository sessionRepository;
+
+	@Autowired
+	private CommentRepository commentRepository;
+
+	@Autowired
+	private ContentRepository contentRepository;
+
+	@Autowired
+	private AnswerRepository answerRepository;
+
+	@Autowired
+	private ImageUtils imageUtils;
+
+	@Value("${upload.filesize_b}")
+	private int uploadFileSizeByte;
+
+	private ApplicationEventPublisher publisher;
+
+	private static final Logger logger = LoggerFactory.getLogger(ContentServiceImpl.class);
+
+	private HashMap<String, Timer> timerList = new HashMap<>();
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Content> getSkillQuestions(final String sessionkey) {
+		final Session session = getSession(sessionkey);
+		final User user = userService.getCurrentUser();
+		if (session.isCreator(user)) {
+			return contentRepository.getSkillQuestionsForTeachers(session);
+		} else {
+			return contentRepository.getSkillQuestionsForUsers(session);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int getSkillQuestionCount(final String sessionkey) {
+		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(#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(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(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) ((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 Content result = contentRepository.saveQuestion(session, content);
+
+		final NewQuestionEvent event = new NewQuestionEvent(this, session, result);
+		this.publisher.publishEvent(event);
+
+		return result;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	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 NewCommentEvent event = new NewCommentEvent(this, session, result);
+			this.publisher.publishEvent(event);
+			return true;
+		}
+		return false;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public Content getQuestion(final String id) {
+		final Content result = contentRepository.getQuestion(id);
+		if (result == null) {
+			return null;
+		}
+		if (!"freetext".equals(result.getQuestionType()) && 0 == result.getPiRound()) {
+			/* needed for legacy questions whose piRound property has not been set */
+			result.setPiRound(1);
+		}
+
+		return result;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
+	public void deleteQuestion(final String questionId) {
+		final Content content = contentRepository.getQuestion(questionId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+
+		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
+		if (session == null) {
+			throw new UnauthorizedException();
+		}
+		contentRepository.deleteQuestionWithAnswers(content);
+
+		final DeleteQuestionEvent event = new DeleteQuestionEvent(this, session, content);
+		this.publisher.publishEvent(event);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#sessionKeyword, 'session', 'owner')")
+	public void deleteAllQuestions(final String sessionKeyword) {
+		final Session session = getSessionWithAuthCheck(sessionKeyword);
+		contentRepository.deleteAllQuestionsWithAnswers(session);
+
+		final DeleteAllQuestionsEvent event = new DeleteAllQuestionsEvent(this, session);
+		this.publisher.publishEvent(event);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
+	public void startNewPiRound(final String questionId, User user) {
+		final Content content = contentRepository.getQuestion(questionId);
+		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
+
+		if (null == user) {
+			user = userService.getCurrentUser();
+		}
+
+		cancelDelayedPiRoundChange(questionId);
+
+		content.setPiRoundEndTime(0);
+		content.setVotingDisabled(true);
+		content.updateRoundManagementState();
+		update(content, user);
+
+		this.publisher.publishEvent(new PiRoundEndEvent(this, session, content));
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
+	public void startNewPiRoundDelayed(final String questionId, final int time) {
+		final ContentService contentService = this;
+		final User user = userService.getCurrentUser();
+		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));
+		content.updateRoundStartVariables(date, endDate);
+		update(content);
+
+		this.publisher.publishEvent(new PiRoundDelayedStartEvent(this, session, content));
+		timerList.put(questionId, timer);
+
+		timer.schedule(new TimerTask() {
+			@Override
+			public void run() {
+				contentService.startNewPiRound(questionId, user);
+			}
+		}, endDate);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
+	public void cancelPiRoundChange(final String questionId) {
+		final Content content = contentRepository.getQuestion(questionId);
+		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
+
+		cancelDelayedPiRoundChange(questionId);
+		content.resetRoundManagementState();
+
+		if (0 == content.getPiRound() || 1 == content.getPiRound()) {
+			content.setPiRoundFinished(false);
+		} else {
+			content.setPiRound(1);
+			content.setPiRoundFinished(true);
+		}
+
+		update(content);
+		this.publisher.publishEvent(new PiRoundCancelEvent(this, session, content));
+	}
+
+	@Override
+	public void cancelDelayedPiRoundChange(final String questionId) {
+		Timer timer = timerList.get(questionId);
+
+		if (null != timer) {
+			timer.cancel();
+			timerList.remove(questionId);
+			timer.purge();
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
+	public void resetPiRoundState(final String questionId) {
+		final Content content = contentRepository.getQuestion(questionId);
+		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
+		cancelDelayedPiRoundChange(questionId);
+
+		if ("freetext".equals(content.getQuestionType())) {
+			content.setPiRound(0);
+		} else {
+			content.setPiRound(1);
+		}
+
+		content.resetRoundManagementState();
+		answerRepository.deleteAnswers(content);
+		update(content);
+		this.publisher.publishEvent(new PiRoundResetEvent(this, session, content));
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
+	public void setVotingAdmission(final String questionId, final boolean disableVoting) {
+		final Content content = contentRepository.getQuestion(questionId);
+		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
+		content.setVotingDisabled(disableVoting);
+
+		if (!disableVoting && !content.isActive()) {
+			content.setActive(true);
+			update(content);
+		} else {
+			contentRepository.updateQuestion(content);
+		}
+		ArsnovaEvent event;
+		if (disableVoting) {
+			event = new LockVoteEvent(this, session, content);
+		} else {
+			event = new UnlockVoteEvent(this, session, content);
+		}
+		this.publisher.publishEvent(event);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	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();
+		}
+		contentRepository.setVotingAdmissions(session, disableVoting, contents);
+		ArsnovaEvent event;
+		if (disableVoting) {
+			event = new LockVotesEvent(this, session, contents);
+		} else {
+			event = new UnlockVotesEvent(this, session, contents);
+		}
+		this.publisher.publishEvent(event);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public void setVotingAdmissionForAllQuestions(final String sessionkey, final boolean disableVoting) {
+		final User user = getCurrentUser();
+		final Session session = getSession(sessionkey);
+		if (!session.isCreator(user)) {
+			throw new UnauthorizedException();
+		}
+		final List<Content> contents = contentRepository.setVotingAdmissionForAllQuestions(session, disableVoting);
+		ArsnovaEvent event;
+		if (disableVoting) {
+			event = new LockVotesEvent(this, session, contents);
+		} else {
+			event = new UnlockVotesEvent(this, session, contents);
+		}
+		this.publisher.publishEvent(event);
+	}
+
+	private Session getSessionWithAuthCheck(final String sessionKeyword) {
+		final User user = userService.getCurrentUser();
+		final Session session = sessionRepository.getSessionFromKeyword(sessionKeyword);
+		if (user == null || session == null || !session.isCreator(user)) {
+			throw new UnauthorizedException();
+		}
+		return session;
+	}
+
+	@Override
+	@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();
+		}
+		commentRepository.deleteInterposedQuestion(comment);
+
+		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 = sessionRepository.getSessionFromKeyword(sessionKeyword);
+		if (session == null) {
+			throw new UnauthorizedException();
+		}
+		final User user = getCurrentUser();
+		if (session.isCreator(user)) {
+			commentRepository.deleteAllInterposedQuestions(session);
+		} else {
+			commentRepository.deleteAllInterposedQuestions(session, user);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
+	public void deleteAnswers(final String questionId) {
+		final Content content = contentRepository.getQuestion(questionId);
+		content.resetQuestionState();
+		contentRepository.updateQuestion(content);
+		answerRepository.deleteAnswers(content);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<String> getUnAnsweredQuestionIds(final String sessionKey) {
+		final User user = getCurrentUser();
+		final Session session = getSession(sessionKey);
+		return contentRepository.getUnAnsweredQuestionIds(session, user);
+	}
+
+	private User getCurrentUser() {
+		final User user = userService.getCurrentUser();
+		if (user == null) {
+			throw new UnauthorizedException();
+		}
+		return user;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public Answer getMyAnswer(final String questionId) {
+		final Content content = getQuestion(questionId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+		return answerRepository.getMyAnswer(userService.getCurrentUser(), questionId, content.getPiRound());
+	}
+
+	@Override
+	public void readFreetextAnswer(final String answerId, final User user) {
+		final Answer answer = answerRepository.get(answerId);
+		if (answer == null) {
+			throw new NotFoundException();
+		}
+		if (answer.isRead()) {
+			return;
+		}
+		final Session session = sessionRepository.getSessionFromId(answer.getSessionId());
+		if (session.isCreator(user)) {
+			answer.setRead(true);
+			answerRepository.updateAnswer(answer);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Answer> getAnswers(final String questionId, final int piRound, final int offset, final int limit) {
+		final Content content = contentRepository.getQuestion(questionId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+		return "freetext".equals(content.getQuestionType())
+				? getFreetextAnswers(questionId, offset, limit)
+						: answerRepository.getAnswers(content, piRound);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Answer> getAnswers(final String questionId, final int offset, final int limit) {
+		final Content content = getQuestion(questionId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+		if ("freetext".equals(content.getQuestionType())) {
+			return getFreetextAnswers(questionId, offset, limit);
+		} else {
+			return answerRepository.getAnswers(content);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Answer> getAllAnswers(final String questionId, final int offset, final int limit) {
+		final Content content = getQuestion(questionId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+		if ("freetext".equals(content.getQuestionType())) {
+			return getFreetextAnswers(questionId, offset, limit);
+		} else {
+			return answerRepository.getAllAnswers(content);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int getAnswerCount(final String questionId) {
+		final Content content = getQuestion(questionId);
+		if (content == null) {
+			return 0;
+		}
+
+		if ("freetext".equals(content.getQuestionType())) {
+			return answerRepository.getTotalAnswerCountByQuestion(content);
+		} else {
+			return answerRepository.getAnswerCount(content, content.getPiRound());
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int getAnswerCount(final String questionId, final int piRound) {
+		final Content content = getQuestion(questionId);
+		if (content == null) {
+			return 0;
+		}
+
+		return answerRepository.getAnswerCount(content, piRound);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int getAbstentionAnswerCount(final String questionId) {
+		final Content content = getQuestion(questionId);
+		if (content == null) {
+			return 0;
+		}
+
+		return answerRepository.getAbstentionAnswerCount(questionId);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int getTotalAnswerCountByQuestion(final String questionId) {
+		final Content content = getQuestion(questionId);
+		if (content == null) {
+			return 0;
+		}
+
+		return answerRepository.getTotalAnswerCountByQuestion(content);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Answer> getFreetextAnswers(final String questionId, final int offset, final int limit) {
+		final List<Answer> answers = answerRepository.getFreetextAnswers(questionId, offset, limit);
+		if (answers == null) {
+			throw new NotFoundException();
+		}
+		/* Remove user for privacy concerns */
+		for (Answer answer : answers) {
+			answer.setUser(null);
+		}
+
+		return answers;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Answer> getMyAnswers(final String sessionKey) {
+		final Session session = getSession(sessionKey);
+		// 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 = answerRepository.getMyAnswers(userService.getCurrentUser(), session);
+		final List<Answer> filteredAnswers = new ArrayList<>();
+		for (final Answer answer : answers) {
+			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(content.getQuestionType())) {
+				answer.setPiRound(1);
+			}
+
+			// discard all answers that aren't in the same piRound as the content
+			if (answer.getPiRound() == content.getPiRound()) {
+				filteredAnswers.add(answer);
+			}
+		}
+
+		return filteredAnswers;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int getTotalAnswerCount(final String sessionKey) {
+		return answerRepository.getTotalAnswerCount(sessionKey);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int getInterposedCount(final String sessionKey) {
+		return commentRepository.getInterposedCount(sessionKey);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public CommentReadingCount getInterposedReadingCount(final String sessionKey, String username) {
+		final Session session = sessionRepository.getSessionFromKeyword(sessionKey);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		if (username == null) {
+			return commentRepository.getInterposedReadingCount(session);
+		} else {
+			User currentUser = userService.getCurrentUser();
+			if (!currentUser.getUsername().equals(username)) {
+				throw new ForbiddenException();
+			}
+
+			return commentRepository.getInterposedReadingCount(session, currentUser);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	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 commentRepository.getInterposedQuestions(session, offset, limit);
+		} else {
+			return commentRepository.getInterposedQuestions(session, user, offset, limit);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public Comment readInterposedQuestion(final String commentId) {
+		final User user = userService.getCurrentUser();
+		return this.readInterposedQuestionInternal(commentId, user);
+	}
+
+	/*
+	 * The "internal" suffix means it is called by internal services that have no authentication!
+	 * TODO: Find a better way of doing this...
+	 */
+	@Override
+	public Comment readInterposedQuestionInternal(final String commentId, User user) {
+		final Comment comment = commentRepository.getInterposedQuestion(commentId);
+		if (comment == null) {
+			throw new NotFoundException();
+		}
+		final Session session = sessionRepository.getSessionFromId(comment.getSessionId());
+		if (!comment.isCreator(user) && !session.isCreator(user)) {
+			throw new UnauthorizedException();
+		}
+		if (session.isCreator(user)) {
+			commentRepository.markInterposedQuestionAsRead(comment);
+		}
+		return comment;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public Content update(final Content content) {
+		final User user = userService.getCurrentUser();
+		return update(content, user);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public Content update(final Content content, User user) {
+		final Content oldContent = contentRepository.getQuestion(content.getId());
+		if (null == oldContent) {
+			throw new NotFoundException();
+		}
+
+		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
+		if (user == null || session == null || !session.isCreator(user)) {
+			throw new UnauthorizedException();
+		}
+
+		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 Content result = contentRepository.updateQuestion(content);
+
+		if (!oldContent.isActive() && content.isActive()) {
+			final UnlockQuestionEvent event = new UnlockQuestionEvent(this, session, result);
+			this.publisher.publishEvent(event);
+		} else if (oldContent.isActive() && !content.isActive()) {
+			final LockQuestionEvent event = new LockQuestionEvent(this, session, result);
+			this.publisher.publishEvent(event);
+		}
+		return result;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public Answer saveAnswer(final String questionId, final de.thm.arsnova.entities.transport.Answer answer) {
+		final User user = getCurrentUser();
+		final Content content = getQuestion(questionId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
+
+		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 (content.isFixedAnswer() && content.getText() != null) {
+				theAnswer.setAnswerTextRaw(theAnswer.getAnswerText());
+
+				if (content.isStrictMode()) {
+					content.checkTextStrictOptions(theAnswer);
+				}
+				theAnswer.setQuestionValue(content.evaluateCorrectAnswerFixedText(theAnswer.getAnswerTextRaw()));
+				theAnswer.setSuccessfulFreeTextAnswer(content.isSuccessfulFreeTextAnswer(theAnswer.getAnswerTextRaw()));
+			}
+		}
+
+		return answerRepository.saveAnswer(theAnswer, user, content, session);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public Answer updateAnswer(final Answer answer) {
+		final User user = userService.getCurrentUser();
+		final Answer realAnswer = this.getMyAnswer(answer.getQuestionId());
+		if (user == null || realAnswer == null || !user.getUsername().equals(realAnswer.getUser())) {
+			throw new UnauthorizedException();
+		}
+
+		final Content content = getQuestion(answer.getQuestionId());
+		if ("freetext".equals(content.getQuestionType())) {
+			imageUtils.generateThumbnailImage(realAnswer);
+			content.checkTextStrictOptions(realAnswer);
+		}
+		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;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public void deleteAnswer(final String questionId, final String answerId) {
+		final Content content = contentRepository.getQuestion(questionId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+		final User user = userService.getCurrentUser();
+		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
+		if (user == null || session == null || !session.isCreator(user)) {
+			throw new UnauthorizedException();
+		}
+		answerRepository.deleteAnswer(answerId);
+
+		this.publisher.publishEvent(new DeleteAnswerEvent(this, session, content));
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Content> getLectureQuestions(final String sessionkey) {
+		final Session session = getSession(sessionkey);
+		final User user = userService.getCurrentUser();
+		if (session.isCreator(user)) {
+			return contentRepository.getLectureQuestionsForTeachers(session);
+		} else {
+			return contentRepository.getLectureQuestionsForUsers(session);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Content> getFlashcards(final String sessionkey) {
+		final Session session = getSession(sessionkey);
+		final User user = userService.getCurrentUser();
+		if (session.isCreator(user)) {
+			return contentRepository.getFlashcardsForTeachers(session);
+		} else {
+			return contentRepository.getFlashcardsForUsers(session);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Content> getPreparationQuestions(final String sessionkey) {
+		final Session session = getSession(sessionkey);
+		final User user = userService.getCurrentUser();
+		if (session.isCreator(user)) {
+			return contentRepository.getPreparationQuestionsForTeachers(session);
+		} else {
+			return contentRepository.getPreparationQuestionsForUsers(session);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	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 contents;
+	}
+
+	private Session getSession(final String sessionkey) {
+		final Session session = sessionRepository.getSessionFromKeyword(sessionkey);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		return session;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int getLectureQuestionCount(final String sessionkey) {
+		return contentRepository.getLectureQuestionCount(getSession(sessionkey));
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int getFlashcardCount(final String sessionkey) {
+		return contentRepository.getFlashcardCount(getSession(sessionkey));
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int getPreparationQuestionCount(final String sessionkey) {
+		return contentRepository.getPreparationQuestionCount(getSession(sessionkey));
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int countLectureQuestionAnswers(final String sessionkey) {
+		return this.countLectureQuestionAnswersInternal(sessionkey);
+	}
+
+	/*
+	 * The "internal" suffix means it is called by internal services that have no authentication!
+	 * TODO: Find a better way of doing this...
+	 */
+	@Override
+	public int countLectureQuestionAnswersInternal(final String sessionkey) {
+		return answerRepository.countLectureQuestionAnswers(getSession(sessionkey));
+	}
+
+	@Override
+	public Map<String, Object> getAnswerAndAbstentionCountInternal(final String questionId) {
+		final Content content = getQuestion(questionId);
+		HashMap<String, Object> map = new HashMap<>();
+
+		if (content == null) {
+			return null;
+		}
+
+		map.put("_id", questionId);
+		map.put("answers", answerRepository.getAnswerCount(content, content.getPiRound()));
+		map.put("abstentions", answerRepository.getAbstentionAnswerCount(questionId));
+
+		return map;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int countPreparationQuestionAnswers(final String sessionkey) {
+		return this.countPreparationQuestionAnswersInternal(sessionkey);
+	}
+
+	/*
+	 * The "internal" suffix means it is called by internal services that have no authentication!
+	 * TODO: Find a better way of doing this...
+	 */
+	@Override
+	public int countPreparationQuestionAnswersInternal(final String sessionkey) {
+		return answerRepository.countPreparationQuestionAnswers(getSession(sessionkey));
+	}
+
+	/*
+	 * The "internal" suffix means it is called by internal services that have no authentication!
+	 * TODO: Find a better way of doing this...
+	 */
+	@Override
+	public int countFlashcardsForUserInternal(final String sessionkey) {
+		return contentRepository.getFlashcardsForUsers(getSession(sessionkey)).size();
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public void deleteLectureQuestions(final String sessionkey) {
+		final Session session = getSessionWithAuthCheck(sessionkey);
+		contentRepository.deleteAllLectureQuestionsWithAnswers(session);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public void deleteFlashcards(final String sessionkey) {
+		final Session session = getSessionWithAuthCheck(sessionkey);
+		contentRepository.deleteAllFlashcardsWithAnswers(session);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public void deletePreparationQuestions(final String sessionkey) {
+		final Session session = getSessionWithAuthCheck(sessionkey);
+		contentRepository.deleteAllPreparationQuestionsWithAnswers(session);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<String> getUnAnsweredLectureQuestionIds(final String sessionkey) {
+		final User user = getCurrentUser();
+		return this.getUnAnsweredLectureQuestionIds(sessionkey, user);
+	}
+
+	@Override
+	public List<String> getUnAnsweredLectureQuestionIds(final String sessionkey, final User user) {
+		final Session session = getSession(sessionkey);
+		return contentRepository.getUnAnsweredLectureQuestionIds(session, user);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<String> getUnAnsweredPreparationQuestionIds(final String sessionkey) {
+		final User user = getCurrentUser();
+		return this.getUnAnsweredPreparationQuestionIds(sessionkey, user);
+	}
+
+	@Override
+	public List<String> getUnAnsweredPreparationQuestionIds(final String sessionkey, final User user) {
+		final Session session = getSession(sessionkey);
+		return contentRepository.getUnAnsweredPreparationQuestionIds(session, user);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public void publishAll(final String sessionkey, final boolean publish) {
+		final User user = getCurrentUser();
+		final Session session = getSession(sessionkey);
+		if (!session.isCreator(user)) {
+			throw new UnauthorizedException();
+		}
+		final List<Content> contents = contentRepository.publishAllQuestions(session, publish);
+		ArsnovaEvent event;
+		if (publish) {
+			event = new UnlockQuestionsEvent(this, session, contents);
+		} else {
+			event = new LockQuestionsEvent(this, session, contents);
+		}
+		this.publisher.publishEvent(event);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	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();
+		}
+		contentRepository.publishQuestions(session, publish, contents);
+		ArsnovaEvent event;
+		if (publish) {
+			event = new UnlockQuestionsEvent(this, session, contents);
+		} else {
+			event = new LockQuestionsEvent(this, session, contents);
+		}
+		this.publisher.publishEvent(event);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public void deleteAllQuestionsAnswers(final String sessionkey) {
+		final User user = getCurrentUser();
+		final Session session = getSession(sessionkey);
+		if (!session.isCreator(user)) {
+			throw new UnauthorizedException();
+		}
+		answerRepository.deleteAllQuestionsAnswers(session);
+
+		this.publisher.publishEvent(new DeleteAllQuestionsAnswersEvent(this, session));
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
+	public void deleteAllPreparationAnswers(String sessionkey) {
+		final Session session = getSession(sessionkey);
+		answerRepository.deleteAllPreparationAnswers(session);
+
+		this.publisher.publishEvent(new DeleteAllPreparationAnswersEvent(this, session));
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
+	public void deleteAllLectureAnswers(String sessionkey) {
+		final Session session = getSession(sessionkey);
+		answerRepository.deleteAllLectureAnswers(session);
+
+		this.publisher.publishEvent(new DeleteAllLectureAnswersEvent(this, session));
+	}
+
+	@Override
+	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
+		this.publisher = publisher;
+	}
+
+	@Override
+	public String getImage(String questionId, String answerId) {
+		final List<Answer> answers = getAnswers(questionId, -1, -1);
+		Answer answer = null;
+
+		for (Answer a : answers) {
+			if (answerId.equals(a.getId())) {
+				answer = a;
+				break;
+			}
+		}
+
+		if (answer == null) {
+			throw new NotFoundException();
+		}
+
+		return answer.getAnswerImage();
+	}
+
+	@Override
+	public String getQuestionImage(String questionId) {
+		Content content = contentRepository.getQuestion(questionId);
+		String imageData = content.getImage();
+
+		if (imageData == null) {
+			imageData = "";
+		}
+
+		return imageData;
+	}
+
+	@Override
+	public String getQuestionFcImage(String questionId) {
+		Content content = contentRepository.getQuestion(questionId);
+		String imageData = content.getFcImage();
+
+		if (imageData == null) {
+			imageData = "";
+		}
+
+		return imageData;
+	}
+}
diff --git a/src/main/java/de/thm/arsnova/services/FeedbackService.java b/src/main/java/de/thm/arsnova/services/FeedbackService.java
index 0a4a69599..8e0d8842e 100644
--- a/src/main/java/de/thm/arsnova/services/FeedbackService.java
+++ b/src/main/java/de/thm/arsnova/services/FeedbackService.java
@@ -17,172 +17,26 @@
  */
 package de.thm.arsnova.services;
 
-import de.thm.arsnova.FeedbackStorage;
 import de.thm.arsnova.entities.Feedback;
-import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.User;
-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;
-import org.springframework.context.ApplicationEventPublisherAware;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.PostConstruct;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 /**
- * Performs all feedback related operations.
+ * The functionality the feedback service should provide.
  */
-@Service
-public class FeedbackService implements IFeedbackService, ApplicationEventPublisherAware {
-
-	private static final int DEFAULT_SCHEDULER_DELAY = 5000;
-	private static final double Z_THRESHOLD = 0.1;
-
-	/**
-	 * minutes, after which the feedback is deleted
-	 */
-	@Value("${feedback.cleanup}")
-	private int cleanupFeedbackDelay;
-
-	@Autowired
-	private SessionRepository sessionRepository;
-
-	private FeedbackStorage feedbackStorage;
-
-	private ApplicationEventPublisher publisher;
-
-	@PostConstruct
-	public void init() {
-		feedbackStorage = new FeedbackStorage();
-	}
-
-	@Override
-	@Scheduled(fixedDelay = DEFAULT_SCHEDULER_DELAY)
-	public void cleanFeedbackVotes() {
-		Map<Session, List<User>> deletedFeedbackOfUsersInSession = feedbackStorage.cleanFeedbackVotes(cleanupFeedbackDelay);
-		/*
-		 * mapping (Session -> Users) is not suitable for web sockets, because we want to sent all affected
-		 * sessions to a single user in one go instead of sending multiple messages for each session. Hence,
-		 * we need the mapping (User -> Sessions)
-		 */
-		final Map<User, Set<Session>> affectedSessionsOfUsers = new HashMap<>();
-
-		for (Map.Entry<Session, List<User>> entry : deletedFeedbackOfUsersInSession.entrySet()) {
-			final Session session = entry.getKey();
-			final List<User> users = entry.getValue();
-			for (User user : users) {
-				Set<Session> affectedSessions;
-				if (affectedSessionsOfUsers.containsKey(user)) {
-					affectedSessions = affectedSessionsOfUsers.get(user);
-				} else {
-					affectedSessions = new HashSet<>();
-				}
-				affectedSessions.add(session);
-				affectedSessionsOfUsers.put(user, affectedSessions);
-			}
-		}
-		// Send feedback reset event to all affected users
-		for (Map.Entry<User, Set<Session>> entry : affectedSessionsOfUsers.entrySet()) {
-			final User user = entry.getKey();
-			final Set<Session> arsSessions = entry.getValue();
-			this.publisher.publishEvent(new DeleteFeedbackForSessionsEvent(this, arsSessions, user));
-		}
-		// For each session that has deleted feedback, send the new feedback to all clients
-		for (Session session : deletedFeedbackOfUsersInSession.keySet()) {
-			this.publisher.publishEvent(new NewFeedbackEvent(this, session));
-		}
-	}
-
-	@Override
-	public void cleanFeedbackVotesInSession(final String keyword, final int cleanupFeedbackDelayInMins) {
-		final Session session = sessionRepository.getSessionFromKeyword(keyword);
-		List<User> affectedUsers = feedbackStorage.cleanFeedbackVotesInSession(session, cleanupFeedbackDelayInMins);
-		Set<Session> sessionSet = new HashSet<>();
-		sessionSet.add(session);
-
-		// Send feedback reset event to all affected users
-		for (User user : affectedUsers) {
-			this.publisher.publishEvent(new DeleteFeedbackForSessionsEvent(this, sessionSet, user));
-		}
-		// send the new feedback to all clients in affected session
-		this.publisher.publishEvent(new NewFeedbackEvent(this, session));
-	}
-
-	@Override
-	public Feedback getFeedback(final String keyword) {
-		final Session session = sessionRepository.getSessionFromKeyword(keyword);
-		if (session == null) {
-			throw new NotFoundException();
-		}
-		return feedbackStorage.getFeedback(session);
-	}
-
-	@Override
-	public int getFeedbackCount(final String keyword) {
-		final Feedback feedback = this.getFeedback(keyword);
-		final List<Integer> values = feedback.getValues();
-		return values.get(Feedback.FEEDBACK_FASTER) + values.get(Feedback.FEEDBACK_OK)
-				+ values.get(Feedback.FEEDBACK_SLOWER) + values.get(Feedback.FEEDBACK_AWAY);
-	}
+public interface FeedbackService {
+	void cleanFeedbackVotes();
 
-	@Override
-	public double getAverageFeedback(final String sessionkey) {
-		final Session session = sessionRepository.getSessionFromKeyword(sessionkey);
-		if (session == null) {
-			throw new NotFoundException();
-		}
-		final Feedback feedback = feedbackStorage.getFeedback(session);
-		final List<Integer> values = feedback.getValues();
-		final double count = values.get(Feedback.FEEDBACK_FASTER) + values.get(Feedback.FEEDBACK_OK)
-				+ values.get(Feedback.FEEDBACK_SLOWER) + values.get(Feedback.FEEDBACK_AWAY);
-		final double sum = values.get(Feedback.FEEDBACK_OK) + values.get(Feedback.FEEDBACK_SLOWER) * 2
-				+ values.get(Feedback.FEEDBACK_AWAY) * 3;
+	void cleanFeedbackVotesInSession(String keyword, int cleanupFeedbackDelayInMins);
 
-		if (Math.abs(count) < Z_THRESHOLD) {
-			throw new NoContentException();
-		}
-		return sum / count;
-	}
+	Feedback getFeedback(String keyword);
 
-	@Override
-	public long getAverageFeedbackRounded(final String sessionkey) {
-		return Math.round(getAverageFeedback(sessionkey));
-	}
+	int getFeedbackCount(String keyword);
 
-	@Override
-	public boolean saveFeedback(final String keyword, final int value, final User user) {
-		final Session session = sessionRepository.getSessionFromKeyword(keyword);
-		if (session == null) {
-			throw new NotFoundException();
-		}
-		feedbackStorage.saveFeedback(session, value, user);
+	double getAverageFeedback(String sessionkey);
 
-		this.publisher.publishEvent(new NewFeedbackEvent(this, session));
-		return true;
-	}
+	long getAverageFeedbackRounded(String sessionkey);
 
-	@Override
-	public Integer getMyFeedback(final String keyword, final User user) {
-		final Session session = sessionRepository.getSessionFromKeyword(keyword);
-		if (session == null) {
-			throw new NotFoundException();
-		}
-		return feedbackStorage.getMyFeedback(session, user);
-	}
+	boolean saveFeedback(String keyword, int value, User user);
 
-	@Override
-	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
-		this.publisher = publisher;
-	}
+	Integer getMyFeedback(String keyword, User user);
 }
diff --git a/src/main/java/de/thm/arsnova/services/FeedbackServiceImpl.java b/src/main/java/de/thm/arsnova/services/FeedbackServiceImpl.java
new file mode 100644
index 000000000..8fe6eef74
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/FeedbackServiceImpl.java
@@ -0,0 +1,184 @@
+/*
+ * 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.entities.Feedback;
+import de.thm.arsnova.entities.Session;
+import de.thm.arsnova.entities.User;
+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.Value;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Performs all feedback related operations.
+ */
+@Service
+public class FeedbackServiceImpl implements FeedbackService, ApplicationEventPublisherAware {
+
+	private static final int DEFAULT_SCHEDULER_DELAY = 5000;
+	private static final double Z_THRESHOLD = 0.1;
+
+	/**
+	 * minutes, after which the feedback is deleted
+	 */
+	@Value("${feedback.cleanup}")
+	private int cleanupFeedbackDelay;
+
+	private SessionRepository sessionRepository;
+
+	private FeedbackStorageService feedbackStorage;
+
+	private ApplicationEventPublisher publisher;
+
+	public FeedbackServiceImpl(FeedbackStorageService feedbackStorage, SessionRepository sessionRepository) {
+		this.feedbackStorage = feedbackStorage;
+		this.sessionRepository = sessionRepository;
+	}
+
+	@Override
+	@Scheduled(fixedDelay = DEFAULT_SCHEDULER_DELAY)
+	public void cleanFeedbackVotes() {
+		Map<Session, List<User>> deletedFeedbackOfUsersInSession = feedbackStorage.cleanFeedbackVotes(cleanupFeedbackDelay);
+		/*
+		 * mapping (Session -> Users) is not suitable for web sockets, because we want to sent all affected
+		 * sessions to a single user in one go instead of sending multiple messages for each session. Hence,
+		 * we need the mapping (User -> Sessions)
+		 */
+		final Map<User, Set<Session>> affectedSessionsOfUsers = new HashMap<>();
+
+		for (Map.Entry<Session, List<User>> entry : deletedFeedbackOfUsersInSession.entrySet()) {
+			final Session session = entry.getKey();
+			final List<User> users = entry.getValue();
+			for (User user : users) {
+				Set<Session> affectedSessions;
+				if (affectedSessionsOfUsers.containsKey(user)) {
+					affectedSessions = affectedSessionsOfUsers.get(user);
+				} else {
+					affectedSessions = new HashSet<>();
+				}
+				affectedSessions.add(session);
+				affectedSessionsOfUsers.put(user, affectedSessions);
+			}
+		}
+		// Send feedback reset event to all affected users
+		for (Map.Entry<User, Set<Session>> entry : affectedSessionsOfUsers.entrySet()) {
+			final User user = entry.getKey();
+			final Set<Session> arsSessions = entry.getValue();
+			this.publisher.publishEvent(new DeleteFeedbackForSessionsEvent(this, arsSessions, user));
+		}
+		// For each session that has deleted feedback, send the new feedback to all clients
+		for (Session session : deletedFeedbackOfUsersInSession.keySet()) {
+			this.publisher.publishEvent(new NewFeedbackEvent(this, session));
+		}
+	}
+
+	@Override
+	public void cleanFeedbackVotesInSession(final String keyword, final int cleanupFeedbackDelayInMins) {
+		final Session session = sessionRepository.getSessionFromKeyword(keyword);
+		List<User> affectedUsers = feedbackStorage.cleanFeedbackVotesInSession(session, cleanupFeedbackDelayInMins);
+		Set<Session> sessionSet = new HashSet<>();
+		sessionSet.add(session);
+
+		// Send feedback reset event to all affected users
+		for (User user : affectedUsers) {
+			this.publisher.publishEvent(new DeleteFeedbackForSessionsEvent(this, sessionSet, user));
+		}
+		// send the new feedback to all clients in affected session
+		this.publisher.publishEvent(new NewFeedbackEvent(this, session));
+	}
+
+	@Override
+	public Feedback getFeedback(final String keyword) {
+		final Session session = sessionRepository.getSessionFromKeyword(keyword);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		return feedbackStorage.getFeedback(session);
+	}
+
+	@Override
+	public int getFeedbackCount(final String keyword) {
+		final Feedback feedback = this.getFeedback(keyword);
+		final List<Integer> values = feedback.getValues();
+		return values.get(Feedback.FEEDBACK_FASTER) + values.get(Feedback.FEEDBACK_OK)
+				+ values.get(Feedback.FEEDBACK_SLOWER) + values.get(Feedback.FEEDBACK_AWAY);
+	}
+
+	@Override
+	public double getAverageFeedback(final String sessionkey) {
+		final Session session = sessionRepository.getSessionFromKeyword(sessionkey);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		final Feedback feedback = feedbackStorage.getFeedback(session);
+		final List<Integer> values = feedback.getValues();
+		final double count = values.get(Feedback.FEEDBACK_FASTER) + values.get(Feedback.FEEDBACK_OK)
+				+ values.get(Feedback.FEEDBACK_SLOWER) + values.get(Feedback.FEEDBACK_AWAY);
+		final double sum = values.get(Feedback.FEEDBACK_OK) + values.get(Feedback.FEEDBACK_SLOWER) * 2
+				+ values.get(Feedback.FEEDBACK_AWAY) * 3;
+
+		if (Math.abs(count) < Z_THRESHOLD) {
+			throw new NoContentException();
+		}
+		return sum / count;
+	}
+
+	@Override
+	public long getAverageFeedbackRounded(final String sessionkey) {
+		return Math.round(getAverageFeedback(sessionkey));
+	}
+
+	@Override
+	public boolean saveFeedback(final String keyword, final int value, final User user) {
+		final Session session = sessionRepository.getSessionFromKeyword(keyword);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		feedbackStorage.saveFeedback(session, value, user);
+
+		this.publisher.publishEvent(new NewFeedbackEvent(this, session));
+		return true;
+	}
+
+	@Override
+	public Integer getMyFeedback(final String keyword, final User user) {
+		final Session session = sessionRepository.getSessionFromKeyword(keyword);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		return feedbackStorage.getMyFeedback(session, user);
+	}
+
+	@Override
+	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
+		this.publisher = publisher;
+	}
+}
diff --git a/src/main/java/de/thm/arsnova/services/FeedbackStorageService.java b/src/main/java/de/thm/arsnova/services/FeedbackStorageService.java
new file mode 100644
index 000000000..5451ccfac
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/FeedbackStorageService.java
@@ -0,0 +1,16 @@
+package de.thm.arsnova.services;
+
+import de.thm.arsnova.entities.Feedback;
+import de.thm.arsnova.entities.Session;
+import de.thm.arsnova.entities.User;
+
+import java.util.List;
+import java.util.Map;
+
+public interface FeedbackStorageService {
+	Feedback getFeedback(Session session);
+	Integer getMyFeedback(Session session, User u);
+	void saveFeedback(Session session, int value, User user);
+	Map<Session, List<User>> cleanFeedbackVotes(int cleanupFeedbackDelay);
+	List<User> cleanFeedbackVotesInSession(Session session, int cleanupFeedbackDelayInMins);
+}
diff --git a/src/main/java/de/thm/arsnova/FeedbackStorage.java b/src/main/java/de/thm/arsnova/services/FeedbackStorageServiceImpl.java
similarity index 95%
rename from src/main/java/de/thm/arsnova/FeedbackStorage.java
rename to src/main/java/de/thm/arsnova/services/FeedbackStorageServiceImpl.java
index bb72be0d5..4dc11ef63 100644
--- a/src/main/java/de/thm/arsnova/FeedbackStorage.java
+++ b/src/main/java/de/thm/arsnova/services/FeedbackStorageServiceImpl.java
@@ -15,11 +15,12 @@
  * 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;
+package de.thm.arsnova.services;
 
 import de.thm.arsnova.entities.Feedback;
 import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.User;
+import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Isolation;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -34,7 +35,8 @@ import java.util.concurrent.TimeUnit;
 /**
  * In-memory storage of feedback data.
  */
-public class FeedbackStorage {
+@Service
+public class FeedbackStorageServiceImpl implements FeedbackStorageService {
 	private static class FeedbackStorageObject {
 		private final int value;
 		private final Date timestamp;
@@ -60,6 +62,7 @@ public class FeedbackStorage {
 	private final Map<Session, Map<User, FeedbackStorageObject>> data =
 			new ConcurrentHashMap<>();
 
+	@Override
 	public Feedback getFeedback(final Session session) {
 		int a = 0;
 		int b = 0;
@@ -91,6 +94,7 @@ public class FeedbackStorage {
 		return new Feedback(a, b, c, d);
 	}
 
+	@Override
 	public Integer getMyFeedback(final Session session, final User u) {
 		if (data.get(session) == null) {
 			return null;
@@ -105,6 +109,7 @@ public class FeedbackStorage {
 		return null;
 	}
 
+	@Override
 	@Transactional(isolation = Isolation.READ_COMMITTED)
 	public void saveFeedback(final Session session, final int value, final User user) {
 		if (data.get(session) == null) {
@@ -114,6 +119,7 @@ public class FeedbackStorage {
 		data.get(session).put(user, new FeedbackStorageObject(value, user));
 	}
 
+	@Override
 	@Transactional(isolation = Isolation.READ_COMMITTED)
 	public Map<Session, List<User>> cleanFeedbackVotes(final int cleanupFeedbackDelay) {
 		final Map<Session, List<User>> removedFeedbackOfUsersInSession = new HashMap<>();
@@ -128,6 +134,7 @@ public class FeedbackStorage {
 		return removedFeedbackOfUsersInSession;
 	}
 
+	@Override
 	@Transactional(isolation = Isolation.READ_COMMITTED)
 	public List<User> cleanFeedbackVotesInSession(final Session session, final int cleanupFeedbackDelayInMins) {
 		final long timelimitInMillis = TimeUnit.MILLISECONDS.convert(cleanupFeedbackDelayInMins, TimeUnit.MINUTES);
diff --git a/src/main/java/de/thm/arsnova/services/IContentService.java b/src/main/java/de/thm/arsnova/services/IContentService.java
deleted file mode 100644
index 9b4e4f97b..000000000
--- a/src/main/java/de/thm/arsnova/services/IContentService.java
+++ /dev/null
@@ -1,171 +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.entities.Answer;
-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;
-import java.util.Map;
-
-/**
- * The functionality the question service should provide.
- */
-public interface IContentService {
-	Content saveQuestion(Content content);
-
-	boolean saveQuestion(Comment comment);
-
-	Content getQuestion(String id);
-
-	List<Content> getSkillQuestions(String sessionkey);
-
-	int getSkillQuestionCount(String sessionkey);
-
-	void deleteQuestion(String questionId);
-
-	void deleteAllQuestions(String sessionKeyword);
-
-	void startNewPiRound(String questionId, User user);
-
-	void startNewPiRoundDelayed(String questionId, int time);
-
-	void cancelPiRoundChange(String questionId);
-
-	void cancelDelayedPiRoundChange(String questionId);
-
-	void resetPiRoundState(String questionId);
-
-	List<String> getUnAnsweredQuestionIds(String sessionKey);
-
-	Answer getMyAnswer(String questionId);
-
-	void readFreetextAnswer(String answerId, User user);
-
-	List<Answer> getAnswers(String questionId, int piRound, int offset, int limit);
-
-	List<Answer> getAnswers(String questionId, int offset, int limit);
-
-	List<Answer> getAllAnswers(String questionId, int offset, int limit);
-
-	int getAnswerCount(String questionId);
-
-	int getAnswerCount(String questionId, int piRound);
-
-	List<Answer> getFreetextAnswers(String questionId, int offset, int limit);
-
-	List<Answer> getMyAnswers(String sessionKey);
-
-	int getTotalAnswerCount(String sessionKey);
-
-	int getTotalAnswerCountByQuestion(String questionId);
-
-	int getInterposedCount(String sessionKey);
-
-	CommentReadingCount getInterposedReadingCount(String sessionKey, String username);
-
-	List<Comment> getInterposedQuestions(String sessionKey, int offset, int limit);
-
-	Comment readInterposedQuestion(String commentId);
-
-	Comment readInterposedQuestionInternal(String commentId, User user);
-
-	Content update(Content content);
-
-	Content update(Content content, User user);
-
-	void deleteAnswers(String questionId);
-
-	Answer saveAnswer(String questionId, de.thm.arsnova.entities.transport.Answer answer);
-
-	Answer updateAnswer(Answer answer);
-
-	void deleteAnswer(String questionId, String answerId);
-
-	void deleteInterposedQuestion(String commentId);
-
-	List<Content> getLectureQuestions(String sessionkey);
-
-	List<Content> getFlashcards(String sessionkey);
-
-	List<Content> getPreparationQuestions(String sessionkey);
-
-	int getLectureQuestionCount(String sessionkey);
-
-	int getFlashcardCount(String sessionkey);
-
-	int getPreparationQuestionCount(String sessionkey);
-
-	Map<String, Object> getAnswerAndAbstentionCountInternal(String questionid);
-
-	int countLectureQuestionAnswers(String sessionkey);
-
-	int countLectureQuestionAnswersInternal(String sessionkey);
-
-	int countPreparationQuestionAnswers(String sessionkey);
-
-	int countPreparationQuestionAnswersInternal(String sessionkey);
-
-	int countFlashcardsForUserInternal(String sessionkey);
-
-	void deleteLectureQuestions(String sessionkey);
-
-	void deleteFlashcards(String sessionkey);
-
-	void deletePreparationQuestions(String sessionkey);
-
-	List<String> getUnAnsweredLectureQuestionIds(String sessionkey);
-
-	List<String> getUnAnsweredLectureQuestionIds(String sessionKey, User user);
-
-	List<String> getUnAnsweredPreparationQuestionIds(String sessionkey);
-
-	List<String> getUnAnsweredPreparationQuestionIds(String sessionKey, User user);
-
-	void deleteAllInterposedQuestions(String sessionKeyword);
-
-	void publishAll(String sessionkey, boolean publish);
-
-	void publishQuestions(String sessionkey, boolean publish, List<Content> contents);
-
-	void deleteAllQuestionsAnswers(String sessionkey);
-
-	void deleteAllPreparationAnswers(String sessionkey);
-
-	void deleteAllLectureAnswers(String sessionkey);
-
-	int getAbstentionAnswerCount(String questionId);
-
-	String getImage(String questionId, String answerId);
-
-	void setVotingAdmission(String questionId, boolean disableVoting);
-
-	void setVotingAdmissions(String sessionkey, boolean disableVoting, List<Content> contents);
-
-	void setVotingAdmissionForAllQuestions(String sessionkey, boolean disableVoting);
-
-	String getQuestionImage(String questionId);
-
-	String getQuestionFcImage(String questionId);
-
-	List<Content> replaceImageData(List<Content> contents);
-
-}
diff --git a/src/main/java/de/thm/arsnova/services/IFeedbackService.java b/src/main/java/de/thm/arsnova/services/IFeedbackService.java
deleted file mode 100644
index dc0210164..000000000
--- a/src/main/java/de/thm/arsnova/services/IFeedbackService.java
+++ /dev/null
@@ -1,42 +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.entities.Feedback;
-import de.thm.arsnova.entities.User;
-
-/**
- * The functionality the feedback service should provide.
- */
-public interface IFeedbackService {
-	void cleanFeedbackVotes();
-
-	void cleanFeedbackVotesInSession(String keyword, int cleanupFeedbackDelayInMins);
-
-	Feedback getFeedback(String keyword);
-
-	int getFeedbackCount(String keyword);
-
-	double getAverageFeedback(String sessionkey);
-
-	long getAverageFeedbackRounded(String sessionkey);
-
-	boolean saveFeedback(String keyword, int value, User user);
-
-	Integer getMyFeedback(String keyword, User user);
-}
diff --git a/src/main/java/de/thm/arsnova/services/IMotdService.java b/src/main/java/de/thm/arsnova/services/IMotdService.java
deleted file mode 100644
index a9488e413..000000000
--- a/src/main/java/de/thm/arsnova/services/IMotdService.java
+++ /dev/null
@@ -1,59 +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.entities.Motd;
-import de.thm.arsnova.entities.MotdList;
-
-import java.util.Date;
-import java.util.List;
-
-/**
- * The functionality the motd service should provide.
- */
-public interface IMotdService {
-	Motd getMotd(String keyword);
-
-	List<Motd> getAdminMotds();  //all w/o the sessionmotds
-
-	List<Motd> getAllSessionMotds(final String sessionkey);
-
-	List<Motd> getCurrentMotds(final Date clientdate, final String audience, final String sessionkey);
-
-	List<Motd> filterMotdsByDate(List<Motd> list, Date clientdate);
-
-	List<Motd> filterMotdsByList(List<Motd> list, MotdList motdList);
-
-	void deleteMotd(Motd motd);
-
-	void deleteSessionMotd(final String sessionkey, Motd motd);
-
-	Motd saveMotd(Motd motd);
-
-	Motd saveSessionMotd(final String sessionkey, final Motd motd);
-
-	Motd updateMotd(Motd motd);
-
-	Motd updateSessionMotd(final String sessionkey, Motd motd);
-
-	MotdList getMotdListForUser(final String username);
-
-	MotdList saveUserMotdList(MotdList motdList);
-
-	MotdList updateUserMotdList(MotdList userMotdList);
-}
diff --git a/src/main/java/de/thm/arsnova/services/ISessionService.java b/src/main/java/de/thm/arsnova/services/ISessionService.java
deleted file mode 100644
index c6796f7ce..000000000
--- a/src/main/java/de/thm/arsnova/services/ISessionService.java
+++ /dev/null
@@ -1,100 +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.connector.model.Course;
-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.transport.ImportExportSession;
-import de.thm.arsnova.entities.transport.LearningProgressValues;
-
-import java.util.List;
-import java.util.UUID;
-
-/**
- * The functionality the session service should provide.
- */
-public interface ISessionService {
-	Session getSession(String keyword);
-
-	Session getSessionForAdmin(final String keyword);
-
-	Session getSessionInternal(String keyword, User user);
-
-	Session saveSession(Session session);
-
-	boolean sessionKeyAvailable(String keyword);
-
-	String generateKeyword();
-
-	List<Session> getUserSessions(String username);
-
-	List<Session> getUserVisitedSessions(String username);
-
-	List<Session> getMySessions(int offset, int limit);
-
-	List<Session> getMyVisitedSessions(int offset, int limit);
-
-	int countSessions(List<Course> courses);
-
-	int activeUsers(String sessionkey);
-
-	Session setActive(String sessionkey, Boolean lock);
-
-	Session joinSession(String keyword, UUID socketId);
-
-	Session updateSession(String sessionkey, Session session);
-
-	Session changeSessionCreator(String sessionkey, String newCreator);
-
-	Session updateSessionInternal(Session session, User user);
-
-	void deleteSession(String sessionkey);
-
-	LearningProgressValues getLearningProgress(String sessionkey, String progressType, String questionVariant);
-
-	LearningProgressValues getMyLearningProgress(String sessionkey, String progressType, String questionVariant);
-
-	List<SessionInfo> getMySessionsInfo(int offset, int limit);
-
-	List<SessionInfo> getPublicPoolSessionsInfo();
-
-	List<SessionInfo> getMyPublicPoolSessionsInfo();
-
-	List<SessionInfo> getMyVisitedSessionsInfo(int offset, int limit);
-
-	SessionInfo importSession(ImportExportSession session);
-
-	ImportExportSession exportSession(String sessionkey, Boolean withAnswerStatistics, Boolean withFeedbackQuestions);
-
-	SessionInfo copySessionToPublicPool(String sessionkey, de.thm.arsnova.entities.transport.ImportExportSession.PublicPool pp);
-
-	SessionFeature getSessionFeatures(String sessionkey);
-
-	SessionFeature changeSessionFeatures(String sessionkey, SessionFeature features);
-
-	boolean lockFeedbackInput(String sessionkey, Boolean lock);
-
-	boolean flipFlashcards(String sessionkey, Boolean flip);
-
-	void deleteInactiveSessions();
-
-	void deleteInactiveVisitedSessionLists();
-}
diff --git a/src/main/java/de/thm/arsnova/services/IStatisticsService.java b/src/main/java/de/thm/arsnova/services/IStatisticsService.java
deleted file mode 100644
index 86f72807b..000000000
--- a/src/main/java/de/thm/arsnova/services/IStatisticsService.java
+++ /dev/null
@@ -1,27 +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.entities.Statistics;
-
-/**
- * The functionality the statistics service should provide.
- */
-public interface IStatisticsService {
-	Statistics getStatistics();
-}
diff --git a/src/main/java/de/thm/arsnova/services/IUserService.java b/src/main/java/de/thm/arsnova/services/IUserService.java
deleted file mode 100644
index 37b4d0b60..000000000
--- a/src/main/java/de/thm/arsnova/services/IUserService.java
+++ /dev/null
@@ -1,70 +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.entities.DbUser;
-import de.thm.arsnova.entities.User;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-
-/**
- * The functionality the user service should provide.
- */
-public interface IUserService {
-	User getCurrentUser();
-
-	boolean isBannedFromLogin(String addr);
-
-	void increaseFailedLoginCount(String addr);
-
-	User getUser2SocketId(UUID socketId);
-
-	void putUser2SocketId(UUID socketId, User user);
-
-	void removeUser2SocketId(UUID socketId);
-
-	Set<Map.Entry<UUID, User>> socketId2User();
-
-	boolean isUserInSession(User user, String keyword);
-
-	Set<User> getUsersInSession(String keyword);
-
-	String getSessionForUser(String username);
-
-	void addUserToSessionBySocketId(UUID socketId, String keyword);
-
-	void removeUserFromSessionBySocketId(UUID socketId);
-
-	void removeUserFromMaps(User user);
-
-	int loggedInUsers();
-
-	DbUser getDbUser(String username);
-
-	DbUser createDbUser(String username, String password);
-
-	DbUser updateDbUser(DbUser dbUser);
-
-	DbUser deleteDbUser(String username);
-
-	void initiatePasswordReset(String username);
-
-	boolean resetPassword(DbUser dbUser, String key, String password);
-}
diff --git a/src/main/java/de/thm/arsnova/services/MotdService.java b/src/main/java/de/thm/arsnova/services/MotdService.java
index 674525ec2..9df6a5afd 100644
--- a/src/main/java/de/thm/arsnova/services/MotdService.java
+++ b/src/main/java/de/thm/arsnova/services/MotdService.java
@@ -9,189 +9,51 @@
  *
  * 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
+ * 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/>.
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 package de.thm.arsnova.services;
 
 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;
-
-import java.util.ArrayList;
+
 import java.util.Date;
-import java.util.HashSet;
 import java.util.List;
-import java.util.StringTokenizer;
+
 /**
- * Performs all question, interposed question, and answer related operations.
+ * The functionality the motd service should provide.
  */
-@Service
-public class MotdService implements IMotdService {
-	@Autowired
-	private IUserService userService;
-
-	@Autowired
-	private ISessionService sessionService;
-
-	@Autowired
-	private MotdRepository motdRepository;
-
-	@Autowired
-	private MotdListRepository motdListRepository;
-
-  @Override
-  @PreAuthorize("isAuthenticated()")
-  public Motd getMotd(final String key) {
-    return motdRepository.getMotdByKey(key);
-  }
-
-  @Override
-  @PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')")
-  public List<Motd> getAdminMotds() {
-    return motdRepository.getAdminMotds();
-  }
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
-	public List<Motd> getAllSessionMotds(final String 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 = 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);
-	}
-
-  @Override
-  public List<Motd> filterMotdsByDate(List<Motd> list, Date clientdate) {
-		List<Motd> returns = new ArrayList<>();
-		for (Motd motd : list) {
-			if (motd.getStartdate().before(clientdate) && motd.getEnddate().after(clientdate)) {
-				returns.add(motd);
-			}
-		}
-		return returns;
-  }
-
-	@Override
-	public List<Motd> filterMotdsByList(List<Motd> list, MotdList motdlist) {
-		if (motdlist != null && motdlist.getMotdkeys() != null && !motdlist.getMotdkeys().isEmpty()) {
-			List<Motd> returns = new ArrayList<>();
-			HashSet<String> keys = new HashSet<>(500);  // Or a more realistic size
-			StringTokenizer st = new StringTokenizer(motdlist.getMotdkeys(), ",");
-			while (st.hasMoreTokens()) {
-				keys.add(st.nextToken());
-			}
-			for (Motd motd : list) {
-				if (!keys.contains(motd.getMotdkey())) {
-					returns.add(motd);
-				}
-			}
-			return returns;
-		} else {
-			return list;
-		}
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')")
-	public Motd saveMotd(final Motd motd) {
-		return createOrUpdateMotd(motd);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
-	public Motd saveSessionMotd(final String sessionkey, final Motd motd) {
-		Session session = sessionService.getSession(sessionkey);
-		motd.setSessionId(session.getId());
-
-
-		return createOrUpdateMotd(motd);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')")
-	public Motd updateMotd(final Motd motd) {
-		return createOrUpdateMotd(motd);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
-	public Motd updateSessionMotd(final String sessionkey, final Motd motd) {
-		return createOrUpdateMotd(motd);
-	}
-
-	private Motd createOrUpdateMotd(final Motd motd) {
-		if (motd.getMotdkey() != null) {
-			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 motdRepository.createOrUpdateMotd(motd);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')")
-	public void deleteMotd(Motd motd) {
-		motdRepository.deleteMotd(motd);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
-	public void deleteSessionMotd(final String sessionkey, Motd motd) {
-		motdRepository.deleteMotd(motd);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public MotdList getMotdListForUser(final String username) {
-		final User user = userService.getCurrentUser();
-		if (username.equals(user.getUsername()) && !"guest".equals(user.getType())) {
-			return motdListRepository.getMotdListForUser(username);
-		}
-		return null;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public MotdList saveUserMotdList(MotdList motdList) {
-		final User user = userService.getCurrentUser();
-		if (user.getUsername().equals(motdList.getUsername())) {
-			return motdListRepository.createOrUpdateMotdList(motdList);
-		}
-		return null;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public MotdList updateUserMotdList(MotdList motdList) {
-		final User user = userService.getCurrentUser();
-		if (user.getUsername().equals(motdList.getUsername())) {
-			return motdListRepository.createOrUpdateMotdList(motdList);
-		}
-		return null;
-	}
+public interface MotdService {
+	Motd getMotd(String keyword);
+
+	List<Motd> getAdminMotds();  //all w/o the sessionmotds
+
+	List<Motd> getAllSessionMotds(final String sessionkey);
+
+	List<Motd> getCurrentMotds(final Date clientdate, final String audience, final String sessionkey);
+
+	List<Motd> filterMotdsByDate(List<Motd> list, Date clientdate);
+
+	List<Motd> filterMotdsByList(List<Motd> list, MotdList motdList);
+
+	void deleteMotd(Motd motd);
+
+	void deleteSessionMotd(final String sessionkey, Motd motd);
+
+	Motd saveMotd(Motd motd);
+
+	Motd saveSessionMotd(final String sessionkey, final Motd motd);
+
+	Motd updateMotd(Motd motd);
+
+	Motd updateSessionMotd(final String sessionkey, Motd motd);
+
+	MotdList getMotdListForUser(final String username);
+
+	MotdList saveUserMotdList(MotdList motdList);
+
+	MotdList updateUserMotdList(MotdList userMotdList);
 }
diff --git a/src/main/java/de/thm/arsnova/services/MotdServiceImpl.java b/src/main/java/de/thm/arsnova/services/MotdServiceImpl.java
new file mode 100644
index 000000000..d373b001f
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/MotdServiceImpl.java
@@ -0,0 +1,197 @@
+/*
+ * 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.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;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.StringTokenizer;
+/**
+ * Performs all question, interposed question, and answer related operations.
+ */
+@Service
+public class MotdServiceImpl implements MotdService {
+	@Autowired
+	private UserService userService;
+
+	@Autowired
+	private SessionService sessionService;
+
+	@Autowired
+	private MotdRepository motdRepository;
+
+	@Autowired
+	private MotdListRepository motdListRepository;
+
+  @Override
+  @PreAuthorize("isAuthenticated()")
+  public Motd getMotd(final String key) {
+    return motdRepository.getMotdByKey(key);
+  }
+
+  @Override
+  @PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')")
+  public List<Motd> getAdminMotds() {
+    return motdRepository.getAdminMotds();
+  }
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
+	public List<Motd> getAllSessionMotds(final String 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 = 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);
+	}
+
+  @Override
+  public List<Motd> filterMotdsByDate(List<Motd> list, Date clientdate) {
+		List<Motd> returns = new ArrayList<>();
+		for (Motd motd : list) {
+			if (motd.getStartdate().before(clientdate) && motd.getEnddate().after(clientdate)) {
+				returns.add(motd);
+			}
+		}
+		return returns;
+  }
+
+	@Override
+	public List<Motd> filterMotdsByList(List<Motd> list, MotdList motdlist) {
+		if (motdlist != null && motdlist.getMotdkeys() != null && !motdlist.getMotdkeys().isEmpty()) {
+			List<Motd> returns = new ArrayList<>();
+			HashSet<String> keys = new HashSet<>(500);  // Or a more realistic size
+			StringTokenizer st = new StringTokenizer(motdlist.getMotdkeys(), ",");
+			while (st.hasMoreTokens()) {
+				keys.add(st.nextToken());
+			}
+			for (Motd motd : list) {
+				if (!keys.contains(motd.getMotdkey())) {
+					returns.add(motd);
+				}
+			}
+			return returns;
+		} else {
+			return list;
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')")
+	public Motd saveMotd(final Motd motd) {
+		return createOrUpdateMotd(motd);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
+	public Motd saveSessionMotd(final String sessionkey, final Motd motd) {
+		Session session = sessionService.getSession(sessionkey);
+		motd.setSessionId(session.getId());
+
+
+		return createOrUpdateMotd(motd);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')")
+	public Motd updateMotd(final Motd motd) {
+		return createOrUpdateMotd(motd);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
+	public Motd updateSessionMotd(final String sessionkey, final Motd motd) {
+		return createOrUpdateMotd(motd);
+	}
+
+	private Motd createOrUpdateMotd(final Motd motd) {
+		if (motd.getMotdkey() != null) {
+			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 motdRepository.createOrUpdateMotd(motd);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')")
+	public void deleteMotd(Motd motd) {
+		motdRepository.deleteMotd(motd);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
+	public void deleteSessionMotd(final String sessionkey, Motd motd) {
+		motdRepository.deleteMotd(motd);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public MotdList getMotdListForUser(final String username) {
+		final User user = userService.getCurrentUser();
+		if (username.equals(user.getUsername()) && !"guest".equals(user.getType())) {
+			return motdListRepository.getMotdListForUser(username);
+		}
+		return null;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public MotdList saveUserMotdList(MotdList motdList) {
+		final User user = userService.getCurrentUser();
+		if (user.getUsername().equals(motdList.getUsername())) {
+			return motdListRepository.createOrUpdateMotdList(motdList);
+		}
+		return null;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public MotdList updateUserMotdList(MotdList motdList) {
+		final User user = userService.getCurrentUser();
+		if (user.getUsername().equals(motdList.getUsername())) {
+			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 0257be71d..25e7fe199 100644
--- a/src/main/java/de/thm/arsnova/services/SessionService.java
+++ b/src/main/java/de/thm/arsnova/services/SessionService.java
@@ -17,499 +17,84 @@
  */
 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.domain.ILearningProgressFactory;
-import de.thm.arsnova.domain.LearningProgress;
-import de.thm.arsnova.entities.LearningProgressOptions;
 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.transport.ImportExportSession;
-import de.thm.arsnova.entities.transport.LearningProgressValues;
-import de.thm.arsnova.events.DeleteSessionEvent;
-import de.thm.arsnova.events.FeatureChangeEvent;
-import de.thm.arsnova.events.FlipFlashcardsEvent;
-import de.thm.arsnova.events.LockFeedbackEvent;
-import de.thm.arsnova.events.NewSessionEvent;
-import de.thm.arsnova.events.StatusSessionEvent;
-import de.thm.arsnova.exceptions.BadRequestException;
-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;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.ApplicationEventPublisher;
-import org.springframework.context.ApplicationEventPublisherAware;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.stereotype.Service;
-
-import java.io.Serializable;
-import java.util.Comparator;
+import de.thm.arsnova.entities.transport.ScoreStatistics;
+
 import java.util.List;
 import java.util.UUID;
 
 /**
- * Performs all session related operations.
+ * The functionality the session service should provide.
  */
-@Service
-public class SessionService implements ISessionService, ApplicationEventPublisherAware {
+public interface SessionService {
+	Session getSession(String keyword);
+
+	Session getSessionForAdmin(final String keyword);
+
+	Session getSessionInternal(String keyword, User user);
+
+	Session saveSession(Session session);
+
+	boolean sessionKeyAvailable(String keyword);
+
+	String generateKeyword();
+
+	List<Session> getUserSessions(String username);
+
+	List<Session> getUserVisitedSessions(String username);
+
+	List<Session> getMySessions(int offset, int limit);
+
+	List<Session> getMyVisitedSessions(int offset, int limit);
+
+	int countSessions(List<Course> courses);
+
+	int activeUsers(String sessionkey);
+
+	Session setActive(String sessionkey, Boolean lock);
+
+	Session joinSession(String keyword, UUID socketId);
+
+	Session updateSession(String sessionkey, Session session);
+
+	Session changeSessionCreator(String sessionkey, String newCreator);
+
+	Session updateSessionInternal(Session session, User user);
+
+	void deleteSession(String sessionkey);
+
+	ScoreStatistics getLearningProgress(String sessionkey, String type, String questionVariant);
+
+	ScoreStatistics getMyLearningProgress(String sessionkey, String type, String questionVariant);
+
+	List<SessionInfo> getMySessionsInfo(int offset, int limit);
+
+	List<SessionInfo> getPublicPoolSessionsInfo();
+
+	List<SessionInfo> getMyPublicPoolSessionsInfo();
+
+	List<SessionInfo> getMyVisitedSessionsInfo(int offset, int limit);
+
+	SessionInfo importSession(ImportExportSession session);
+
+	ImportExportSession exportSession(String sessionkey, Boolean withAnswerStatistics, Boolean withFeedbackQuestions);
+
+	SessionInfo copySessionToPublicPool(String sessionkey, de.thm.arsnova.entities.transport.ImportExportSession.PublicPool pp);
+
+	SessionFeature getSessionFeatures(String sessionkey);
 
-	@Autowired
-	private SessionRepository sessionRepository;
+	SessionFeature changeSessionFeatures(String sessionkey, SessionFeature features);
 
-	public static class SessionNameComparator implements Comparator<Session>, Serializable {
-		private static final long serialVersionUID = 1L;
+	boolean lockFeedbackInput(String sessionkey, Boolean lock);
 
-		@Override
-		public int compare(final Session session1, final Session session2) {
-			return session1.getName().compareToIgnoreCase(session2.getName());
-		}
-	}
+	boolean flipFlashcards(String sessionkey, Boolean flip);
 
-	public static class SessionInfoNameComparator implements Comparator<SessionInfo>, Serializable {
-		private static final long serialVersionUID = 1L;
+	void deleteInactiveSessions();
 
-		@Override
-		public int compare(final SessionInfo session1, final SessionInfo session2) {
-			return session1.getName().compareToIgnoreCase(session2.getName());
-		}
-	}
-
-	public static class SessionShortNameComparator implements Comparator<Session>, Serializable {
-		private static final long serialVersionUID = 1L;
-
-		@Override
-		public int compare(final Session session1, final Session session2) {
-			return session1.getShortName().compareToIgnoreCase(session2.getShortName());
-		}
-	}
-
-	public static class SessionInfoShortNameComparator implements Comparator<SessionInfo>, Serializable {
-		private static final long serialVersionUID = 1L;
-
-		@Override
-		public int compare(final SessionInfo session1, final SessionInfo session2) {
-			return session1.getShortName().compareToIgnoreCase(session2.getShortName());
-		}
-	}
-
-	private static final long SESSION_INACTIVITY_CHECK_INTERVAL_MS = 30 * 60 * 1000L;
-
-	@Autowired
-	private VisitedSessionRepository visitedSessionRepository;
-
-	@Autowired
-	private IUserService userService;
-
-	@Autowired
-	private IFeedbackService feedbackService;
-
-	@Autowired
-	private ILearningProgressFactory learningProgressFactory;
-
-	@Autowired(required = false)
-	private ConnectorClient connectorClient;
-
-	@Autowired
-	private ImageUtils imageUtils;
-
-	@Value("${session.guest-session.cleanup-days:0}")
-	private int guestSessionInactivityThresholdDays;
-
-	@Value("${pp.logofilesize_b}")
-	private int uploadFileSizeByte;
-
-	private ApplicationEventPublisher publisher;
-
-	private static final Logger logger = LoggerFactory.getLogger(SessionService.class);
-
-	@Scheduled(fixedDelay = SESSION_INACTIVITY_CHECK_INTERVAL_MS)
-	public void deleteInactiveSessions() {
-		if (guestSessionInactivityThresholdDays > 0) {
-			logger.info("Delete inactive sessions.");
-			long unixTime = System.currentTimeMillis();
-			long lastActivityBefore = unixTime - guestSessionInactivityThresholdDays * 24 * 60 * 60 * 1000L;
-			sessionRepository.deleteInactiveGuestSessions(lastActivityBefore);
-		}
-	}
-
-	@Scheduled(fixedDelay = SESSION_INACTIVITY_CHECK_INTERVAL_MS)
-	public void deleteInactiveVisitedSessionLists() {
-		if (guestSessionInactivityThresholdDays > 0) {
-			logger.info("Delete lists of visited session for inactive users.");
-			long unixTime = System.currentTimeMillis();
-			long lastActivityBefore = unixTime - guestSessionInactivityThresholdDays * 24 * 60 * 60 * 1000L;
-			visitedSessionRepository.deleteInactiveGuestVisitedSessionLists(lastActivityBefore);
-		}
-	}
-
-	@Override
-	public Session joinSession(final String keyword, final UUID socketId) {
-		/* Socket.IO solution */
-
-		Session session = null != keyword ? sessionRepository.getSessionFromKeyword(keyword) : null;
-
-		if (null == session) {
-			userService.removeUserFromSessionBySocketId(socketId);
-			return null;
-		}
-		final User user = userService.getUser2SocketId(socketId);
-
-		userService.addUserToSessionBySocketId(socketId, keyword);
-
-		if (session.getCreator().equals(user.getUsername())) {
-			sessionRepository.updateSessionOwnerActivity(session);
-		}
-		sessionRepository.registerAsOnlineUser(user, session);
-
-		if (connectorClient != null && session.isCourseSession()) {
-			final String courseid = session.getCourseId();
-			if (!connectorClient.getMembership(user.getUsername(), courseid).isMember()) {
-				throw new ForbiddenException("User is no course member.");
-			}
-		}
-
-		return session;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public Session getSession(final String keyword) {
-		final User user = userService.getCurrentUser();
-		return Session.anonymizedCopy(this.getSessionInternal(keyword, user));
-	}
-
-	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
-	public Session getSessionForAdmin(final String keyword) {
-		return sessionRepository.getSessionFromKeyword(keyword);
-	}
-
-	/*
-	 * The "internal" suffix means it is called by internal services that have no authentication!
-	 * TODO: Find a better way of doing this...
-	 */
-	@Override
-	public Session getSessionInternal(final String keyword, final User user) {
-		final Session session = sessionRepository.getSessionFromKeyword(keyword);
-		if (session == null) {
-			throw new NotFoundException();
-		}
-		if (!session.isActive()) {
-			if (user.hasRole(UserSessionService.Role.STUDENT)) {
-				throw new ForbiddenException("User is not session creator.");
-			} else if (user.hasRole(UserSessionService.Role.SPEAKER) && !session.isCreator(user)) {
-				throw new ForbiddenException("User is not session creator.");
-			}
-		}
-		if (connectorClient != null && session.isCourseSession()) {
-			final String courseid = session.getCourseId();
-			if (!connectorClient.getMembership(user.getUsername(), courseid).isMember()) {
-				throw new ForbiddenException("User is no course member.");
-			}
-		}
-		return session;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
-	public List<Session> getUserSessions(String username) {
-		return sessionRepository.getSessionsForUsername(username, 0, 0);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<Session> getMySessions(final int offset, final int limit) {
-		return sessionRepository.getMySessions(userService.getCurrentUser(), offset, limit);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<SessionInfo> getPublicPoolSessionsInfo() {
-		return sessionRepository.getPublicPoolSessionsInfo();
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<SessionInfo> getMyPublicPoolSessionsInfo() {
-		return sessionRepository.getMyPublicPoolSessionsInfo(userService.getCurrentUser());
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<SessionInfo> getMySessionsInfo(final int offset, final int limit) {
-		final User user = userService.getCurrentUser();
-		return sessionRepository.getMySessionsInfo(user, offset, limit);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<Session> getMyVisitedSessions(final int offset, final int limit) {
-		return sessionRepository.getVisitedSessionsForUsername(userService.getCurrentUser().getUsername(), offset, limit);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(1, 'motd', 'admin')")
-	public List<Session> getUserVisitedSessions(String username) {
-		return sessionRepository.getVisitedSessionsForUsername(username, 0, 0);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<SessionInfo> getMyVisitedSessionsInfo(final int offset, final int limit) {
-		return sessionRepository.getMyVisitedSessionsInfo(userService.getCurrentUser(), offset, limit);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public Session saveSession(final Session session) {
-		if (connectorClient != null && session.getCourseId() != null) {
-			if (!connectorClient.getMembership(
-					userService.getCurrentUser().getUsername(), session.getCourseId()).isMember()
-					) {
-				throw new ForbiddenException();
-			}
-		}
-		handleLogo(session);
-
-		// set some default values
-		LearningProgressOptions lpo = new LearningProgressOptions();
-		lpo.setType("questions");
-		session.setLearningProgressOptions(lpo);
-
-		SessionFeature sf = new SessionFeature();
-		sf.setLecture(true);
-		sf.setFeedback(true);
-		sf.setInterposed(true);
-		sf.setJitt(true);
-		sf.setLearningProgress(true);
-		sf.setPi(true);
-		session.setFeatures(sf);
-
-		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 sessionRepository.sessionKeyAvailable(keyword);
-	}
-
-	@Override
-	public String generateKeyword() {
-		final int low = 10000000;
-		final int high = 100000000;
-		final String keyword = String
-				.valueOf((int) (Math.random() * (high - low) + low));
-
-		if (sessionKeyAvailable(keyword)) {
-			return keyword;
-		}
-		return generateKeyword();
-	}
-
-	@Override
-	public int countSessions(final List<Course> courses) {
-		final List<Session> sessions = sessionRepository.getCourseSessions(courses);
-		if (sessions == null) {
-			return 0;
-		}
-		return sessions.size();
-	}
-
-	@Override
-	public int activeUsers(final String sessionkey) {
-		return userService.getUsersInSession(sessionkey).size();
-	}
-
-	@Override
-	public Session setActive(final String sessionkey, final Boolean lock) {
-		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 sessionRepository.updateSession(session);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#session, 'owner')")
-	public Session updateSession(final String sessionkey, final Session session) {
-		final Session existingSession = sessionRepository.getSessionFromKeyword(sessionkey);
-
-		existingSession.setActive(session.isActive());
-		existingSession.setShortName(session.getShortName());
-		existingSession.setPpAuthorName(session.getPpAuthorName());
-		existingSession.setPpAuthorMail(session.getPpAuthorMail());
-		existingSession.setShortName(session.getShortName());
-		existingSession.setPpAuthorName(session.getPpAuthorName());
-		existingSession.setPpFaculty(session.getPpFaculty());
-		existingSession.setName(session.getName());
-		existingSession.setPpUniversity(session.getPpUniversity());
-		existingSession.setPpDescription(session.getPpDescription());
-		existingSession.setPpLevel(session.getPpLevel());
-		existingSession.setPpLicense(session.getPpLicense());
-		existingSession.setPpSubject(session.getPpSubject());
-		existingSession.setFeedbackLock(session.getFeedbackLock());
-
-		handleLogo(session);
-		existingSession.setPpLogo(session.getPpLogo());
-
-		return sessionRepository.updateSession(existingSession);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')")
-	public Session changeSessionCreator(String sessionkey, String newCreator) {
-		final Session existingSession = sessionRepository.getSessionFromKeyword(sessionkey);
-		if (existingSession == null) {
-			throw new NullPointerException("Could not load session " + sessionkey + ".");
-		}
-		return sessionRepository.changeSessionCreator(existingSession, newCreator);
-	}
-
-	/*
-	 * The "internal" suffix means it is called by internal services that have no authentication!
-	 * TODO: Find a better way of doing this...
-	 */
-	@Override
-	public Session updateSessionInternal(final Session session, final User user) {
-		if (session.isCreator(user)) {
-			return sessionRepository.updateSession(session);
-		}
-		return null;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
-	public void deleteSession(final String sessionkey) {
-		final Session session = sessionRepository.getSessionFromKeyword(sessionkey);
-
-		sessionRepository.deleteSession(session);
-
-		this.publisher.publishEvent(new DeleteSessionEvent(this, session));
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public LearningProgressValues getLearningProgress(final String sessionkey, final String progressType, final String questionVariant) {
-		final Session session = sessionRepository.getSessionFromKeyword(sessionkey);
-		LearningProgress learningProgress = learningProgressFactory.create(progressType, questionVariant);
-		return learningProgress.getCourseProgress(session);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public LearningProgressValues getMyLearningProgress(final String sessionkey, final String progressType, final String questionVariant) {
-		final Session session = sessionRepository.getSessionFromKeyword(sessionkey);
-		final User user = userService.getCurrentUser();
-		LearningProgress learningProgress = learningProgressFactory.create(progressType, questionVariant);
-		return learningProgress.getMyProgress(session, user);
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public SessionInfo importSession(ImportExportSession importSession) {
-		final User user = userService.getCurrentUser();
-		final SessionInfo info = sessionRepository.importSession(user, importSession);
-		if (info == null) {
-			throw new NullPointerException("Could not import session.");
-		}
-		return info;
-	}
-
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
-	public ImportExportSession exportSession(String sessionkey, Boolean withAnswerStatistics, Boolean 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 = sessionRepository.exportSession(sessionkey, false, false);
-		temp.getSession().setPublicPool(pp);
-		temp.getSession().setSessionType("public_pool");
-		final User user = userService.getCurrentUser();
-		return sessionRepository.importSession(user, temp);
-	}
-
-	@Override
-	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
-		this.publisher = publisher;
-	}
-
-	@Override
-	public SessionFeature getSessionFeatures(String sessionkey) {
-		return sessionRepository.getSessionFromKeyword(sessionkey).getFeatures();
-	}
-
-	@Override
-	public SessionFeature changeSessionFeatures(String sessionkey, SessionFeature features) {
-		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 sessionRepository.updateSession(session).getFeatures();
-	}
-
-	@Override
-	public boolean lockFeedbackInput(String sessionkey, Boolean lock) {
-		final Session session = sessionRepository.getSessionFromKeyword(sessionkey);
-		final User user = userService.getCurrentUser();
-		if (!session.isCreator(user)) {
-			throw new UnauthorizedException("User is not session creator.");
-		}
-		if (!lock) {
-			feedbackService.cleanFeedbackVotesInSession(sessionkey, 0);
-		}
-
-		session.setFeedbackLock(lock);
-		this.publisher.publishEvent(new LockFeedbackEvent(this, session));
-		return sessionRepository.updateSession(session).getFeedbackLock();
-	}
-
-	@Override
-	public boolean flipFlashcards(String sessionkey, Boolean flip) {
-		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 sessionRepository.updateSession(session).getFlipFlashcards();
-	}
-
-	private void handleLogo(Session session) {
-		if (session.getPpLogo() != null) {
-			if (session.getPpLogo().startsWith("http")) {
-				final String base64ImageString = imageUtils.encodeImageToString(session.getPpLogo());
-				if (base64ImageString == null) {
-					throw new BadRequestException("Could not encode image.");
-				}
-				session.setPpLogo(base64ImageString);
-			}
-
-			// base64 adds offset to filesize, formula taken from: http://en.wikipedia.org/wiki/Base64#MIME
-			final int fileSize = (int) ((session.getPpLogo().length() - 814) / 1.37);
-			if (fileSize > uploadFileSizeByte) {
-				throw new PayloadTooLargeException("Could not save file. File is too large with " + fileSize + " Byte.");
-			}
-		}
-	}
+	void deleteInactiveVisitedSessionLists();
 }
diff --git a/src/main/java/de/thm/arsnova/services/SessionServiceImpl.java b/src/main/java/de/thm/arsnova/services/SessionServiceImpl.java
new file mode 100644
index 000000000..1d63abda4
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/SessionServiceImpl.java
@@ -0,0 +1,515 @@
+/*
+ * 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.util.ImageUtils;
+import de.thm.arsnova.connector.client.ConnectorClient;
+import de.thm.arsnova.connector.model.Course;
+import de.thm.arsnova.services.score.ScoreCalculatorFactory;
+import de.thm.arsnova.services.score.ScoreCalculator;
+import de.thm.arsnova.entities.ScoreOptions;
+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.transport.ImportExportSession;
+import de.thm.arsnova.entities.transport.ScoreStatistics;
+import de.thm.arsnova.events.DeleteSessionEvent;
+import de.thm.arsnova.events.FeatureChangeEvent;
+import de.thm.arsnova.events.FlipFlashcardsEvent;
+import de.thm.arsnova.events.LockFeedbackEvent;
+import de.thm.arsnova.events.NewSessionEvent;
+import de.thm.arsnova.events.StatusSessionEvent;
+import de.thm.arsnova.exceptions.BadRequestException;
+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;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Service;
+
+import java.io.Serializable;
+import java.util.Comparator;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Performs all session related operations.
+ */
+@Service
+public class SessionServiceImpl implements SessionService, ApplicationEventPublisherAware {
+
+	@Autowired
+	private SessionRepository sessionRepository;
+
+	public static class SessionNameComparator implements Comparator<Session>, Serializable {
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		public int compare(final Session session1, final Session session2) {
+			return session1.getName().compareToIgnoreCase(session2.getName());
+		}
+	}
+
+	public static class SessionInfoNameComparator implements Comparator<SessionInfo>, Serializable {
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		public int compare(final SessionInfo session1, final SessionInfo session2) {
+			return session1.getName().compareToIgnoreCase(session2.getName());
+		}
+	}
+
+	public static class SessionShortNameComparator implements Comparator<Session>, Serializable {
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		public int compare(final Session session1, final Session session2) {
+			return session1.getShortName().compareToIgnoreCase(session2.getShortName());
+		}
+	}
+
+	public static class SessionInfoShortNameComparator implements Comparator<SessionInfo>, Serializable {
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		public int compare(final SessionInfo session1, final SessionInfo session2) {
+			return session1.getShortName().compareToIgnoreCase(session2.getShortName());
+		}
+	}
+
+	private static final long SESSION_INACTIVITY_CHECK_INTERVAL_MS = 30 * 60 * 1000L;
+
+	@Autowired
+	private VisitedSessionRepository visitedSessionRepository;
+
+	@Autowired
+	private UserService userService;
+
+	@Autowired
+	private FeedbackService feedbackService;
+
+	@Autowired
+	private ScoreCalculatorFactory scoreCalculatorFactory;
+
+	@Autowired(required = false)
+	private ConnectorClient connectorClient;
+
+	@Autowired
+	private ImageUtils imageUtils;
+
+	@Value("${session.guest-session.cleanup-days:0}")
+	private int guestSessionInactivityThresholdDays;
+
+	@Value("${pp.logofilesize_b}")
+	private int uploadFileSizeByte;
+
+	private ApplicationEventPublisher publisher;
+
+	private static final Logger logger = LoggerFactory.getLogger(SessionServiceImpl.class);
+
+	@Scheduled(fixedDelay = SESSION_INACTIVITY_CHECK_INTERVAL_MS)
+	public void deleteInactiveSessions() {
+		if (guestSessionInactivityThresholdDays > 0) {
+			logger.info("Delete inactive sessions.");
+			long unixTime = System.currentTimeMillis();
+			long lastActivityBefore = unixTime - guestSessionInactivityThresholdDays * 24 * 60 * 60 * 1000L;
+			sessionRepository.deleteInactiveGuestSessions(lastActivityBefore);
+		}
+	}
+
+	@Scheduled(fixedDelay = SESSION_INACTIVITY_CHECK_INTERVAL_MS)
+	public void deleteInactiveVisitedSessionLists() {
+		if (guestSessionInactivityThresholdDays > 0) {
+			logger.info("Delete lists of visited session for inactive users.");
+			long unixTime = System.currentTimeMillis();
+			long lastActivityBefore = unixTime - guestSessionInactivityThresholdDays * 24 * 60 * 60 * 1000L;
+			visitedSessionRepository.deleteInactiveGuestVisitedSessionLists(lastActivityBefore);
+		}
+	}
+
+	@Override
+	public Session joinSession(final String keyword, final UUID socketId) {
+		/* Socket.IO solution */
+
+		Session session = null != keyword ? sessionRepository.getSessionFromKeyword(keyword) : null;
+
+		if (null == session) {
+			userService.removeUserFromSessionBySocketId(socketId);
+			return null;
+		}
+		final User user = userService.getUser2SocketId(socketId);
+
+		userService.addUserToSessionBySocketId(socketId, keyword);
+
+		if (session.getCreator().equals(user.getUsername())) {
+			sessionRepository.updateSessionOwnerActivity(session);
+		}
+		sessionRepository.registerAsOnlineUser(user, session);
+
+		if (connectorClient != null && session.isCourseSession()) {
+			final String courseid = session.getCourseId();
+			if (!connectorClient.getMembership(user.getUsername(), courseid).isMember()) {
+				throw new ForbiddenException("User is no course member.");
+			}
+		}
+
+		return session;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public Session getSession(final String keyword) {
+		final User user = userService.getCurrentUser();
+		return Session.anonymizedCopy(this.getSessionInternal(keyword, user));
+	}
+
+	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
+	public Session getSessionForAdmin(final String keyword) {
+		return sessionRepository.getSessionFromKeyword(keyword);
+	}
+
+	/*
+	 * The "internal" suffix means it is called by internal services that have no authentication!
+	 * TODO: Find a better way of doing this...
+	 */
+	@Override
+	public Session getSessionInternal(final String keyword, final User user) {
+		final Session session = sessionRepository.getSessionFromKeyword(keyword);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		if (!session.isActive()) {
+			if (user.hasRole(UserSessionService.Role.STUDENT)) {
+				throw new ForbiddenException("User is not session creator.");
+			} else if (user.hasRole(UserSessionService.Role.SPEAKER) && !session.isCreator(user)) {
+				throw new ForbiddenException("User is not session creator.");
+			}
+		}
+		if (connectorClient != null && session.isCourseSession()) {
+			final String courseid = session.getCourseId();
+			if (!connectorClient.getMembership(user.getUsername(), courseid).isMember()) {
+				throw new ForbiddenException("User is no course member.");
+			}
+		}
+		return session;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
+	public List<Session> getUserSessions(String username) {
+		return sessionRepository.getSessionsForUsername(username, 0, 0);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Session> getMySessions(final int offset, final int limit) {
+		return sessionRepository.getMySessions(userService.getCurrentUser(), offset, limit);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<SessionInfo> getPublicPoolSessionsInfo() {
+		return sessionRepository.getPublicPoolSessionsInfo();
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<SessionInfo> getMyPublicPoolSessionsInfo() {
+		return sessionRepository.getMyPublicPoolSessionsInfo(userService.getCurrentUser());
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<SessionInfo> getMySessionsInfo(final int offset, final int limit) {
+		final User user = userService.getCurrentUser();
+		return sessionRepository.getMySessionsInfo(user, offset, limit);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Session> getMyVisitedSessions(final int offset, final int limit) {
+		return sessionRepository.getVisitedSessionsForUsername(userService.getCurrentUser().getUsername(), offset, limit);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(1, 'motd', 'admin')")
+	public List<Session> getUserVisitedSessions(String username) {
+		return sessionRepository.getVisitedSessionsForUsername(username, 0, 0);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<SessionInfo> getMyVisitedSessionsInfo(final int offset, final int limit) {
+		return sessionRepository.getMyVisitedSessionsInfo(userService.getCurrentUser(), offset, limit);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public Session saveSession(final Session session) {
+		if (connectorClient != null && session.getCourseId() != null) {
+			if (!connectorClient.getMembership(
+					userService.getCurrentUser().getUsername(), session.getCourseId()).isMember()
+					) {
+				throw new ForbiddenException();
+			}
+		}
+		handleLogo(session);
+
+		// set some default values
+		ScoreOptions lpo = new ScoreOptions();
+		lpo.setType("questions");
+		session.setLearningProgressOptions(lpo);
+
+		SessionFeature sf = new SessionFeature();
+		sf.setLecture(true);
+		sf.setFeedback(true);
+		sf.setInterposed(true);
+		sf.setJitt(true);
+		sf.setLearningProgress(true);
+		sf.setPi(true);
+		session.setFeatures(sf);
+
+		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 sessionRepository.sessionKeyAvailable(keyword);
+	}
+
+	@Override
+	public String generateKeyword() {
+		final int low = 10000000;
+		final int high = 100000000;
+		final String keyword = String
+				.valueOf((int) (Math.random() * (high - low) + low));
+
+		if (sessionKeyAvailable(keyword)) {
+			return keyword;
+		}
+		return generateKeyword();
+	}
+
+	@Override
+	public int countSessions(final List<Course> courses) {
+		final List<Session> sessions = sessionRepository.getCourseSessions(courses);
+		if (sessions == null) {
+			return 0;
+		}
+		return sessions.size();
+	}
+
+	@Override
+	public int activeUsers(final String sessionkey) {
+		return userService.getUsersInSession(sessionkey).size();
+	}
+
+	@Override
+	public Session setActive(final String sessionkey, final Boolean lock) {
+		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 sessionRepository.updateSession(session);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#session, 'owner')")
+	public Session updateSession(final String sessionkey, final Session session) {
+		final Session existingSession = sessionRepository.getSessionFromKeyword(sessionkey);
+
+		existingSession.setActive(session.isActive());
+		existingSession.setShortName(session.getShortName());
+		existingSession.setPpAuthorName(session.getPpAuthorName());
+		existingSession.setPpAuthorMail(session.getPpAuthorMail());
+		existingSession.setShortName(session.getShortName());
+		existingSession.setPpAuthorName(session.getPpAuthorName());
+		existingSession.setPpFaculty(session.getPpFaculty());
+		existingSession.setName(session.getName());
+		existingSession.setPpUniversity(session.getPpUniversity());
+		existingSession.setPpDescription(session.getPpDescription());
+		existingSession.setPpLevel(session.getPpLevel());
+		existingSession.setPpLicense(session.getPpLicense());
+		existingSession.setPpSubject(session.getPpSubject());
+		existingSession.setFeedbackLock(session.getFeedbackLock());
+
+		handleLogo(session);
+		existingSession.setPpLogo(session.getPpLogo());
+
+		return sessionRepository.updateSession(existingSession);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(1,'motd','admin')")
+	public Session changeSessionCreator(String sessionkey, String newCreator) {
+		final Session existingSession = sessionRepository.getSessionFromKeyword(sessionkey);
+		if (existingSession == null) {
+			throw new NullPointerException("Could not load session " + sessionkey + ".");
+		}
+		return sessionRepository.changeSessionCreator(existingSession, newCreator);
+	}
+
+	/*
+	 * The "internal" suffix means it is called by internal services that have no authentication!
+	 * TODO: Find a better way of doing this...
+	 */
+	@Override
+	public Session updateSessionInternal(final Session session, final User user) {
+		if (session.isCreator(user)) {
+			return sessionRepository.updateSession(session);
+		}
+		return null;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
+	public void deleteSession(final String sessionkey) {
+		final Session session = sessionRepository.getSessionFromKeyword(sessionkey);
+
+		sessionRepository.deleteSession(session);
+
+		this.publisher.publishEvent(new DeleteSessionEvent(this, session));
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public ScoreStatistics getLearningProgress(final String sessionkey, final String type, final String questionVariant) {
+		final Session session = sessionRepository.getSessionFromKeyword(sessionkey);
+		ScoreCalculator scoreCalculator = scoreCalculatorFactory.create(type, questionVariant);
+		return scoreCalculator.getCourseProgress(session);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public ScoreStatistics getMyLearningProgress(final String sessionkey, final String type, final String questionVariant) {
+		final Session session = sessionRepository.getSessionFromKeyword(sessionkey);
+		final User user = userService.getCurrentUser();
+		ScoreCalculator scoreCalculator = scoreCalculatorFactory.create(type, questionVariant);
+		return scoreCalculator.getMyProgress(session, user);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public SessionInfo importSession(ImportExportSession importSession) {
+		final User user = userService.getCurrentUser();
+		final SessionInfo info = sessionRepository.importSession(user, importSession);
+		if (info == null) {
+			throw new NullPointerException("Could not import session.");
+		}
+		return info;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#sessionkey, 'session', 'owner')")
+	public ImportExportSession exportSession(String sessionkey, Boolean withAnswerStatistics, Boolean 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 = sessionRepository.exportSession(sessionkey, false, false);
+		temp.getSession().setPublicPool(pp);
+		temp.getSession().setSessionType("public_pool");
+		final User user = userService.getCurrentUser();
+		return sessionRepository.importSession(user, temp);
+	}
+
+	@Override
+	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
+		this.publisher = publisher;
+	}
+
+	@Override
+	public SessionFeature getSessionFeatures(String sessionkey) {
+		return sessionRepository.getSessionFromKeyword(sessionkey).getFeatures();
+	}
+
+	@Override
+	public SessionFeature changeSessionFeatures(String sessionkey, SessionFeature features) {
+		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 sessionRepository.updateSession(session).getFeatures();
+	}
+
+	@Override
+	public boolean lockFeedbackInput(String sessionkey, Boolean lock) {
+		final Session session = sessionRepository.getSessionFromKeyword(sessionkey);
+		final User user = userService.getCurrentUser();
+		if (!session.isCreator(user)) {
+			throw new UnauthorizedException("User is not session creator.");
+		}
+		if (!lock) {
+			feedbackService.cleanFeedbackVotesInSession(sessionkey, 0);
+		}
+
+		session.setFeedbackLock(lock);
+		this.publisher.publishEvent(new LockFeedbackEvent(this, session));
+		return sessionRepository.updateSession(session).getFeedbackLock();
+	}
+
+	@Override
+	public boolean flipFlashcards(String sessionkey, Boolean flip) {
+		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 sessionRepository.updateSession(session).getFlipFlashcards();
+	}
+
+	private void handleLogo(Session session) {
+		if (session.getPpLogo() != null) {
+			if (session.getPpLogo().startsWith("http")) {
+				final String base64ImageString = imageUtils.encodeImageToString(session.getPpLogo());
+				if (base64ImageString == null) {
+					throw new BadRequestException("Could not encode image.");
+				}
+				session.setPpLogo(base64ImageString);
+			}
+
+			// base64 adds offset to filesize, formula taken from: http://en.wikipedia.org/wiki/Base64#MIME
+			final int fileSize = (int) ((session.getPpLogo().length() - 814) / 1.37);
+			if (fileSize > uploadFileSizeByte) {
+				throw new PayloadTooLargeException("Could not save file. File is too large with " + fileSize + " Byte.");
+			}
+		}
+	}
+}
diff --git a/src/main/java/de/thm/arsnova/services/StatisticsService.java b/src/main/java/de/thm/arsnova/services/StatisticsService.java
index ddf091b72..f2742a5cd 100644
--- a/src/main/java/de/thm/arsnova/services/StatisticsService.java
+++ b/src/main/java/de/thm/arsnova/services/StatisticsService.java
@@ -18,33 +18,10 @@
 package de.thm.arsnova.services;
 
 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;
 
 /**
- * Performs all statistics related operations. To reduce pressure on the database, data is cached for a fixed amount of
- * time.
+ * The functionality the statistics service should provide.
  */
-@Service
-public class StatisticsService implements IStatisticsService {
-	@Autowired
-	private StatisticsRepository statisticsRepository;
-
-	@Autowired
-	private IUserService userService;
-
-	private Statistics statistics = new Statistics();
-
-	@Scheduled(initialDelay = 0, fixedRate = 10000)
-	private void refreshStatistics() {
-		statistics = statisticsRepository.getStatistics();
-	}
-
-	@Override
-	public Statistics getStatistics() {
-		statistics.setActiveUsers(userService.loggedInUsers());
-		return statistics;
-	}
+public interface StatisticsService {
+	Statistics getStatistics();
 }
diff --git a/src/main/java/de/thm/arsnova/services/StatisticsServiceImpl.java b/src/main/java/de/thm/arsnova/services/StatisticsServiceImpl.java
new file mode 100644
index 000000000..8dda84238
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/StatisticsServiceImpl.java
@@ -0,0 +1,50 @@
+/*
+ * 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.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;
+
+/**
+ * Performs all statistics related operations. To reduce pressure on the database, data is cached for a fixed amount of
+ * time.
+ */
+@Service
+public class StatisticsServiceImpl implements StatisticsService {
+	@Autowired
+	private StatisticsRepository statisticsRepository;
+
+	@Autowired
+	private UserService userService;
+
+	private Statistics statistics = new Statistics();
+
+	@Scheduled(initialDelay = 0, fixedRate = 10000)
+	private void refreshStatistics() {
+		statistics = statisticsRepository.getStatistics();
+	}
+
+	@Override
+	public Statistics getStatistics() {
+		statistics.setActiveUsers(userService.loggedInUsers());
+		return statistics;
+	}
+}
diff --git a/src/main/java/de/thm/arsnova/services/UserService.java b/src/main/java/de/thm/arsnova/services/UserService.java
index fe8fdbc63..5052c4a1a 100644
--- a/src/main/java/de/thm/arsnova/services/UserService.java
+++ b/src/main/java/de/thm/arsnova/services/UserService.java
@@ -17,529 +17,54 @@
  */
 package de.thm.arsnova.services;
 
-import com.codahale.metrics.annotation.Gauge;
 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;
-import org.pac4j.oauth.profile.google2.Google2Profile;
-import org.pac4j.oauth.profile.twitter.TwitterProfile;
-import org.pac4j.springframework.security.authentication.Pac4jAuthenticationToken;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.mail.MailException;
-import org.springframework.mail.javamail.JavaMailSender;
-import org.springframework.mail.javamail.MimeMessageHelper;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.security.authentication.AnonymousAuthenticationToken;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.cas.authentication.CasAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.crypto.keygen.BytesKeyGenerator;
-import org.springframework.security.crypto.keygen.KeyGenerators;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Isolation;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.util.UriUtils;
-import org.stagemonitor.core.metrics.MonitorGauges;
 
-import javax.annotation.PreDestroy;
-import javax.mail.MessagingException;
-import javax.mail.internet.MimeMessage;
-import java.io.UnsupportedEncodingException;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.regex.Pattern;
 
 /**
- * Performs all user related operations.
+ * The functionality the user service should provide.
  */
-@Service
-@MonitorGauges
-public class UserService implements IUserService {
+public interface UserService {
+	User getCurrentUser();
 
-	private static final int LOGIN_TRY_RESET_DELAY_MS = 30 * 1000;
+	boolean isBannedFromLogin(String addr);
 
-	private static final int LOGIN_BAN_RESET_DELAY_MS = 2 * 60 * 1000;
+	void increaseFailedLoginCount(String addr);
 
-	private static final int REPEATED_PASSWORD_RESET_DELAY_MS = 3 * 60 * 1000;
+	User getUser2SocketId(UUID socketId);
 
-	private static final int PASSWORD_RESET_KEY_DURABILITY_MS = 2 * 60 * 60 * 1000;
+	void putUser2SocketId(UUID socketId, User user);
 
-	private static final long ACTIVATION_KEY_CHECK_INTERVAL_MS = 30 * 60 * 1000L;
-	private static final long ACTIVATION_KEY_DURABILITY_MS = 6 * 60 * 60 * 1000L;
+	void removeUser2SocketId(UUID socketId);
 
-	private static final Logger logger = LoggerFactory.getLogger(UserService.class);
+	Set<Map.Entry<UUID, User>> socketId2User();
 
-	private static final ConcurrentHashMap<UUID, User> socketid2user = new ConcurrentHashMap<>();
+	boolean isUserInSession(User user, String keyword);
 
-	/* used for Socket.IO online check solution (new) */
-	private static final ConcurrentHashMap<User, String> user2session = new ConcurrentHashMap<>();
+	Set<User> getUsersInSession(String keyword);
 
-	@Autowired
-	private UserRepository userRepository;
+	String getSessionForUser(String username);
 
-	@Autowired
-	private JavaMailSender mailSender;
+	void addUserToSessionBySocketId(UUID socketId, String keyword);
 
-	@Value("${root-url}")
-	private String rootUrl;
+	void removeUserFromSessionBySocketId(UUID socketId);
 
-	@Value("${customization.path}")
-	private String customizationPath;
+	void removeUserFromMaps(User user);
 
-	@Value("${security.user-db.allowed-email-domains}")
-	private String allowedEmailDomains;
+	int loggedInUsers();
 
-	@Value("${security.user-db.activation-path}")
-	private String activationPath;
+	DbUser getDbUser(String username);
 
-	@Value("${security.user-db.reset-password-path}")
-	private String resetPasswordPath;
+	DbUser createDbUser(String username, String password);
 
-	@Value("${mail.sender.address}")
-	private String mailSenderAddress;
+	DbUser updateDbUser(DbUser dbUser);
 
-	@Value("${mail.sender.name}")
-	private String mailSenderName;
+	DbUser deleteDbUser(String username);
 
-	@Value("${security.user-db.registration-mail.subject}")
-	private String regMailSubject;
+	void initiatePasswordReset(String username);
 
-	@Value("${security.user-db.registration-mail.body}")
-	private String regMailBody;
-
-	@Value("${security.user-db.reset-password-mail.subject}")
-	private String resetPasswordMailSubject;
-
-	@Value("${security.user-db.reset-password-mail.body}")
-	private String resetPasswordMailBody;
-
-	@Value("${security.authentication.login-try-limit}")
-	private int loginTryLimit;
-
-	@Value("${security.admin-accounts}")
-	private String[] adminAccounts;
-
-	private Pattern mailPattern;
-	private BytesKeyGenerator keygen;
-	private BCryptPasswordEncoder encoder;
-	private ConcurrentHashMap<String, Byte> loginTries;
-	private Set<String> loginBans;
-
-	{
-		loginTries = new ConcurrentHashMap<>();
-		loginBans = Collections.synchronizedSet(new HashSet<String>());
-	}
-
-	@Scheduled(fixedDelay = LOGIN_TRY_RESET_DELAY_MS)
-	public void resetLoginTries() {
-		if (!loginTries.isEmpty()) {
-			logger.debug("Reset failed login counters.");
-			loginTries.clear();
-		}
-	}
-
-	@Scheduled(fixedDelay = LOGIN_BAN_RESET_DELAY_MS)
-	public void resetLoginBans() {
-		if (!loginBans.isEmpty()) {
-			logger.info("Reset temporary login bans.");
-			loginBans.clear();
-		}
-	}
-
-	@Scheduled(fixedDelay = ACTIVATION_KEY_CHECK_INTERVAL_MS)
-	public void deleteInactiveUsers() {
-		logger.info("Delete inactive users.");
-		long unixTime = System.currentTimeMillis();
-		long lastActivityBefore = unixTime - ACTIVATION_KEY_DURABILITY_MS;
-		userRepository.deleteInactiveUsers(lastActivityBefore);
-	}
-
-	@Override
-	public User getCurrentUser() {
-		final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
-		if (authentication == null || authentication.getPrincipal() == null) {
-			return null;
-		}
-
-		User user = null;
-
-		if (authentication instanceof Pac4jAuthenticationToken) {
-			user = getOAuthUser(authentication);
-		} else if (authentication instanceof CasAuthenticationToken) {
-			final CasAuthenticationToken token = (CasAuthenticationToken) authentication;
-			user = new User(token.getAssertion().getPrincipal());
-		} else if (authentication instanceof AnonymousAuthenticationToken) {
-			final AnonymousAuthenticationToken token = (AnonymousAuthenticationToken) authentication;
-			user = new User(token);
-		} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
-			final UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
-			user = new User(token);
-			if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_GUEST"))) {
-				user.setType(User.GUEST);
-			} else if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_DB_USER"))) {
-				user.setType(User.ARSNOVA);
-			}
-		}
-
-		if (user == null || "anonymous".equals(user.getUsername())) {
-			throw new UnauthorizedException();
-		}
-
-		user.setAdmin(Arrays.asList(adminAccounts).contains(user.getUsername()));
-
-		return user;
-	}
-
-	private User getOAuthUser(final Authentication authentication) {
-		User user = null;
-		final Pac4jAuthenticationToken token = (Pac4jAuthenticationToken) authentication;
-		if (token.getProfile() instanceof Google2Profile) {
-			final Google2Profile profile = (Google2Profile) token.getProfile();
-			user = new User(profile);
-		} else if (token.getProfile() instanceof TwitterProfile) {
-			final TwitterProfile profile = (TwitterProfile) token.getProfile();
-			user = new User(profile);
-		} else if (token.getProfile() instanceof FacebookProfile) {
-			final FacebookProfile profile = (FacebookProfile) token.getProfile();
-			user = new User(profile);
-		}
-		return user;
-	}
-
-	@Override
-	public boolean isBannedFromLogin(String addr) {
-		return loginBans.contains(addr);
-	}
-
-	@Override
-	public void increaseFailedLoginCount(String addr) {
-		Byte tries = loginTries.get(addr);
-		if (null == tries) {
-			tries = 0;
-		}
-		if (tries < loginTryLimit) {
-			loginTries.put(addr, ++tries);
-			if (loginTryLimit == tries) {
-				logger.info("Temporarily banned {} from login.", addr);
-				loginBans.add(addr);
-			}
-		}
-	}
-
-	@Override
-	public User getUser2SocketId(final UUID socketId) {
-		return socketid2user.get(socketId);
-	}
-
-	@Override
-	public void putUser2SocketId(final UUID socketId, final User user) {
-		socketid2user.put(socketId, user);
-	}
-
-	@Override
-	public Set<Map.Entry<UUID, User>> socketId2User() {
-		return socketid2user.entrySet();
-	}
-
-	@Override
-	public void removeUser2SocketId(final UUID socketId) {
-		socketid2user.remove(socketId);
-	}
-
-	@Override
-	public boolean isUserInSession(final User user, final String keyword) {
-		if (keyword == null) {
-			return false;
-		}
-		String session = user2session.get(user);
-
-		return session != null && keyword.equals(session);
-	}
-
-	@Override
-	public Set<User> getUsersInSession(final String keyword) {
-		final Set<User> result = new HashSet<>();
-		for (final Entry<User, String> e : user2session.entrySet()) {
-			if (e.getValue().equals(keyword)) {
-				result.add(e.getKey());
-			}
-		}
-
-		return result;
-	}
-
-	@Override
-	@Transactional(isolation = Isolation.READ_COMMITTED)
-	public void addUserToSessionBySocketId(final UUID socketId, final String keyword) {
-		final User user = socketid2user.get(socketId);
-		user2session.put(user, keyword);
-	}
-
-	@Override
-	@Transactional(isolation = Isolation.READ_COMMITTED)
-	public void removeUserFromSessionBySocketId(final UUID socketId) {
-		final User user = socketid2user.get(socketId);
-		if (null == user) {
-			logger.warn("No user exists for socket {}.", socketId);
-
-			return;
-		}
-		user2session.remove(user);
-	}
-
-	@Override
-	public String getSessionForUser(final String username) {
-		for (final Entry<User, String> entry  : user2session.entrySet()) {
-			if (entry.getKey().getUsername().equals(username)) {
-				return entry.getValue();
-			}
-		}
-
-		return null;
-	}
-
-	@PreDestroy
-	public void destroy() {
-		logger.error("Destroy UserService");
-	}
-
-	@Override
-	public void removeUserFromMaps(final User user) {
-		if (user != null) {
-			user2session.remove(user);
-		}
-	}
-
-	@Override
-	@Gauge
-	public int loggedInUsers() {
-		return user2session.size();
-	}
-
-	@Override
-	public DbUser getDbUser(String username) {
-		return userRepository.findUserByUsername(username.toLowerCase());
-	}
-
-	@Override
-	public DbUser createDbUser(String username, String password) {
-		String lcUsername = username.toLowerCase();
-
-		if (null == keygen) {
-			keygen = KeyGenerators.secureRandom(32);
-		}
-
-		if (null == mailPattern) {
-			parseMailAddressPattern();
-		}
-
-		if (null == mailPattern || !mailPattern.matcher(lcUsername).matches()) {
-			logger.info("User registration failed. {} does not match pattern.", lcUsername);
-
-			return null;
-		}
-
-		if (null != userRepository.findUserByUsername(lcUsername)) {
-			logger.info("User registration failed. {} already exists.", lcUsername);
-
-			return null;
-		}
-
-		DbUser dbUser = new DbUser();
-		dbUser.setUsername(lcUsername);
-		dbUser.setPassword(encodePassword(password));
-		dbUser.setActivationKey(RandomStringUtils.randomAlphanumeric(32));
-		dbUser.setCreation(System.currentTimeMillis());
-
-		DbUser result = userRepository.createOrUpdateUser(dbUser);
-		if (null != result) {
-			sendActivationEmail(result);
-		} else {
-			logger.error("User registration failed. {} could not be created.", lcUsername);
-		}
-
-		return result;
-	}
-
-	private String encodePassword(String password) {
-		if (null == encoder) {
-			encoder = new BCryptPasswordEncoder(12);
-		}
-
-		return encoder.encode(password);
-	}
-
-	private void sendActivationEmail(DbUser dbUser) {
-		String activationUrl;
-		try {
-			activationUrl = MessageFormat.format(
-				"{0}{1}/{2}?action=activate&username={3}&key={4}",
-				rootUrl,
-				customizationPath,
-				activationPath,
-				UriUtils.encodeQueryParam(dbUser.getUsername(), "UTF-8"),
-				dbUser.getActivationKey()
-			);
-		} catch (UnsupportedEncodingException e) {
-			logger.error("Sending of activation mail failed.", e);
-
-			return;
-		}
-
-		sendEmail(dbUser, regMailSubject, MessageFormat.format(regMailBody, activationUrl));
-	}
-
-	private void parseMailAddressPattern() {
-		/* TODO: Add Unicode support */
-
-		List<String> domainList = Arrays.asList(allowedEmailDomains.split(","));
-
-		if (!domainList.isEmpty()) {
-			List<String> patterns = new ArrayList<>();
-			if (domainList.contains("*")) {
-				patterns.add("([a-z0-9-]+\\.)+[a-z0-9-]+");
-			} else {
-				Pattern patternPattern = Pattern.compile("[a-z0-9.*-]+", Pattern.CASE_INSENSITIVE);
-				for (String patternStr : domainList) {
-					if (patternPattern.matcher(patternStr).matches()) {
-						patterns.add(patternStr.replaceAll("[.]", "[.]").replaceAll("[*]", "[a-z0-9-]+?"));
-					}
-				}
-			}
-
-			mailPattern = Pattern.compile("[a-z0-9._-]+?@(" + StringUtils.join(patterns, "|") + ")", Pattern.CASE_INSENSITIVE);
-			logger.info("Allowed e-mail addresses (pattern) for registration: '{}'.", mailPattern.pattern());
-		}
-	}
-
-	@Override
-	public DbUser updateDbUser(DbUser dbUser) {
-		if (null != dbUser.getId()) {
-			return userRepository.createOrUpdateUser(dbUser);
-		}
-
-		return null;
-	}
-
-	@Override
-	public DbUser deleteDbUser(String username) {
-		User user = getCurrentUser();
-		if (!user.getUsername().equals(username.toLowerCase())
-				&& !SecurityContextHolder.getContext().getAuthentication().getAuthorities()
-						.contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) {
-			throw new UnauthorizedException();
-		}
-
-		DbUser dbUser = getDbUser(username);
-		if (null == dbUser) {
-			throw new NotFoundException();
-		}
-
-		userRepository.deleteUser(dbUser);
-
-		return dbUser;
-	}
-
-	@Override
-	public void initiatePasswordReset(String username) {
-		DbUser dbUser = getDbUser(username);
-		if (null == dbUser) {
-			logger.info("Password reset failed. User {} does not exist.", username);
-
-			throw new NotFoundException();
-		}
-		if (System.currentTimeMillis() < dbUser.getPasswordResetTime() + REPEATED_PASSWORD_RESET_DELAY_MS) {
-			logger.info("Password reset failed. The reset delay for User {} is still active.", username);
-
-			throw new BadRequestException();
-		}
-
-		dbUser.setPasswordResetKey(RandomStringUtils.randomAlphanumeric(32));
-		dbUser.setPasswordResetTime(System.currentTimeMillis());
-
-		if (null == userRepository.createOrUpdateUser(dbUser)) {
-			logger.error("Password reset failed. {} could not be updated.", username);
-		}
-
-		String resetPasswordUrl;
-		try {
-			resetPasswordUrl = MessageFormat.format(
-				"{0}{1}/{2}?action=resetpassword&username={3}&key={4}",
-				rootUrl,
-				customizationPath,
-				resetPasswordPath,
-				UriUtils.encodeQueryParam(dbUser.getUsername(), "UTF-8"),
-				dbUser.getPasswordResetKey()
-			);
-		} catch (UnsupportedEncodingException e) {
-			logger.error("Sending of password reset mail failed.", e);
-
-			return;
-		}
-
-		sendEmail(dbUser, resetPasswordMailSubject, MessageFormat.format(resetPasswordMailBody, resetPasswordUrl));
-	}
-
-	@Override
-	public boolean resetPassword(DbUser dbUser, String key, String password) {
-		if (null == key || "".equals(key) || !key.equals(dbUser.getPasswordResetKey())) {
-			logger.info("Password reset failed. Invalid key provided for User {}.", dbUser.getUsername());
-
-			return false;
-		}
-		if (System.currentTimeMillis() > dbUser.getPasswordResetTime() + PASSWORD_RESET_KEY_DURABILITY_MS) {
-			logger.info("Password reset failed. Key provided for User {} is no longer valid.", dbUser.getUsername());
-
-			dbUser.setPasswordResetKey(null);
-			dbUser.setPasswordResetTime(0);
-			updateDbUser(dbUser);
-
-			return false;
-		}
-
-		dbUser.setPassword(encodePassword(password));
-		dbUser.setPasswordResetKey(null);
-		if (null == updateDbUser(dbUser)) {
-			logger.error("Password reset failed. {} could not be updated.", dbUser.getUsername());
-		}
-
-		return true;
-	}
-
-	private void sendEmail(DbUser dbUser, String subject, String body) {
-		MimeMessage msg = mailSender.createMimeMessage();
-		MimeMessageHelper helper = new MimeMessageHelper(msg, "UTF-8");
-		try {
-			helper.setFrom(mailSenderName + "<" + mailSenderAddress + ">");
-			helper.setTo(dbUser.getUsername());
-			helper.setSubject(subject);
-			helper.setText(body);
-
-			logger.info("Sending mail \"{}\" from \"{}\" to \"{}\"", subject, msg.getFrom(), dbUser.getUsername());
-			mailSender.send(msg);
-		} catch (MailException | MessagingException e) {
-			logger.warn("Mail \"{}\" could not be sent.", subject, e);
-		}
-	}
+	boolean resetPassword(DbUser dbUser, String key, String password);
 }
diff --git a/src/main/java/de/thm/arsnova/services/UserServiceImpl.java b/src/main/java/de/thm/arsnova/services/UserServiceImpl.java
new file mode 100644
index 000000000..17ff05b0b
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/UserServiceImpl.java
@@ -0,0 +1,545 @@
+/*
+ * 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 com.codahale.metrics.annotation.Gauge;
+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;
+import org.pac4j.oauth.profile.google2.Google2Profile;
+import org.pac4j.oauth.profile.twitter.TwitterProfile;
+import org.pac4j.springframework.security.authentication.Pac4jAuthenticationToken;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.mail.MailException;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.cas.authentication.CasAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.keygen.BytesKeyGenerator;
+import org.springframework.security.crypto.keygen.KeyGenerators;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.util.UriUtils;
+import org.stagemonitor.core.metrics.MonitorGauges;
+
+import javax.annotation.PreDestroy;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import java.io.UnsupportedEncodingException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+
+/**
+ * Performs all user related operations.
+ */
+@Service
+@MonitorGauges
+public class UserServiceImpl implements UserService {
+
+	private static final int LOGIN_TRY_RESET_DELAY_MS = 30 * 1000;
+
+	private static final int LOGIN_BAN_RESET_DELAY_MS = 2 * 60 * 1000;
+
+	private static final int REPEATED_PASSWORD_RESET_DELAY_MS = 3 * 60 * 1000;
+
+	private static final int PASSWORD_RESET_KEY_DURABILITY_MS = 2 * 60 * 60 * 1000;
+
+	private static final long ACTIVATION_KEY_CHECK_INTERVAL_MS = 30 * 60 * 1000L;
+	private static final long ACTIVATION_KEY_DURABILITY_MS = 6 * 60 * 60 * 1000L;
+
+	private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
+
+	private static final ConcurrentHashMap<UUID, User> socketid2user = new ConcurrentHashMap<>();
+
+	/* used for Socket.IO online check solution (new) */
+	private static final ConcurrentHashMap<User, String> user2session = new ConcurrentHashMap<>();
+
+	@Autowired
+	private UserRepository userRepository;
+
+	@Autowired
+	private JavaMailSender mailSender;
+
+	@Value("${root-url}")
+	private String rootUrl;
+
+	@Value("${customization.path}")
+	private String customizationPath;
+
+	@Value("${security.user-db.allowed-email-domains}")
+	private String allowedEmailDomains;
+
+	@Value("${security.user-db.activation-path}")
+	private String activationPath;
+
+	@Value("${security.user-db.reset-password-path}")
+	private String resetPasswordPath;
+
+	@Value("${mail.sender.address}")
+	private String mailSenderAddress;
+
+	@Value("${mail.sender.name}")
+	private String mailSenderName;
+
+	@Value("${security.user-db.registration-mail.subject}")
+	private String regMailSubject;
+
+	@Value("${security.user-db.registration-mail.body}")
+	private String regMailBody;
+
+	@Value("${security.user-db.reset-password-mail.subject}")
+	private String resetPasswordMailSubject;
+
+	@Value("${security.user-db.reset-password-mail.body}")
+	private String resetPasswordMailBody;
+
+	@Value("${security.authentication.login-try-limit}")
+	private int loginTryLimit;
+
+	@Value("${security.admin-accounts}")
+	private String[] adminAccounts;
+
+	private Pattern mailPattern;
+	private BytesKeyGenerator keygen;
+	private BCryptPasswordEncoder encoder;
+	private ConcurrentHashMap<String, Byte> loginTries;
+	private Set<String> loginBans;
+
+	{
+		loginTries = new ConcurrentHashMap<>();
+		loginBans = Collections.synchronizedSet(new HashSet<String>());
+	}
+
+	@Scheduled(fixedDelay = LOGIN_TRY_RESET_DELAY_MS)
+	public void resetLoginTries() {
+		if (!loginTries.isEmpty()) {
+			logger.debug("Reset failed login counters.");
+			loginTries.clear();
+		}
+	}
+
+	@Scheduled(fixedDelay = LOGIN_BAN_RESET_DELAY_MS)
+	public void resetLoginBans() {
+		if (!loginBans.isEmpty()) {
+			logger.info("Reset temporary login bans.");
+			loginBans.clear();
+		}
+	}
+
+	@Scheduled(fixedDelay = ACTIVATION_KEY_CHECK_INTERVAL_MS)
+	public void deleteInactiveUsers() {
+		logger.info("Delete inactive users.");
+		long unixTime = System.currentTimeMillis();
+		long lastActivityBefore = unixTime - ACTIVATION_KEY_DURABILITY_MS;
+		userRepository.deleteInactiveUsers(lastActivityBefore);
+	}
+
+	@Override
+	public User getCurrentUser() {
+		final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+		if (authentication == null || authentication.getPrincipal() == null) {
+			return null;
+		}
+
+		User user = null;
+
+		if (authentication instanceof Pac4jAuthenticationToken) {
+			user = getOAuthUser(authentication);
+		} else if (authentication instanceof CasAuthenticationToken) {
+			final CasAuthenticationToken token = (CasAuthenticationToken) authentication;
+			user = new User(token.getAssertion().getPrincipal());
+		} else if (authentication instanceof AnonymousAuthenticationToken) {
+			final AnonymousAuthenticationToken token = (AnonymousAuthenticationToken) authentication;
+			user = new User(token);
+		} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
+			final UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
+			user = new User(token);
+			if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_GUEST"))) {
+				user.setType(User.GUEST);
+			} else if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_DB_USER"))) {
+				user.setType(User.ARSNOVA);
+			}
+		}
+
+		if (user == null || "anonymous".equals(user.getUsername())) {
+			throw new UnauthorizedException();
+		}
+
+		user.setAdmin(Arrays.asList(adminAccounts).contains(user.getUsername()));
+
+		return user;
+	}
+
+	private User getOAuthUser(final Authentication authentication) {
+		User user = null;
+		final Pac4jAuthenticationToken token = (Pac4jAuthenticationToken) authentication;
+		if (token.getProfile() instanceof Google2Profile) {
+			final Google2Profile profile = (Google2Profile) token.getProfile();
+			user = new User(profile);
+		} else if (token.getProfile() instanceof TwitterProfile) {
+			final TwitterProfile profile = (TwitterProfile) token.getProfile();
+			user = new User(profile);
+		} else if (token.getProfile() instanceof FacebookProfile) {
+			final FacebookProfile profile = (FacebookProfile) token.getProfile();
+			user = new User(profile);
+		}
+		return user;
+	}
+
+	@Override
+	public boolean isBannedFromLogin(String addr) {
+		return loginBans.contains(addr);
+	}
+
+	@Override
+	public void increaseFailedLoginCount(String addr) {
+		Byte tries = loginTries.get(addr);
+		if (null == tries) {
+			tries = 0;
+		}
+		if (tries < loginTryLimit) {
+			loginTries.put(addr, ++tries);
+			if (loginTryLimit == tries) {
+				logger.info("Temporarily banned {} from login.", addr);
+				loginBans.add(addr);
+			}
+		}
+	}
+
+	@Override
+	public User getUser2SocketId(final UUID socketId) {
+		return socketid2user.get(socketId);
+	}
+
+	@Override
+	public void putUser2SocketId(final UUID socketId, final User user) {
+		socketid2user.put(socketId, user);
+	}
+
+	@Override
+	public Set<Map.Entry<UUID, User>> socketId2User() {
+		return socketid2user.entrySet();
+	}
+
+	@Override
+	public void removeUser2SocketId(final UUID socketId) {
+		socketid2user.remove(socketId);
+	}
+
+	@Override
+	public boolean isUserInSession(final User user, final String keyword) {
+		if (keyword == null) {
+			return false;
+		}
+		String session = user2session.get(user);
+
+		return session != null && keyword.equals(session);
+	}
+
+	@Override
+	public Set<User> getUsersInSession(final String keyword) {
+		final Set<User> result = new HashSet<>();
+		for (final Entry<User, String> e : user2session.entrySet()) {
+			if (e.getValue().equals(keyword)) {
+				result.add(e.getKey());
+			}
+		}
+
+		return result;
+	}
+
+	@Override
+	@Transactional(isolation = Isolation.READ_COMMITTED)
+	public void addUserToSessionBySocketId(final UUID socketId, final String keyword) {
+		final User user = socketid2user.get(socketId);
+		user2session.put(user, keyword);
+	}
+
+	@Override
+	@Transactional(isolation = Isolation.READ_COMMITTED)
+	public void removeUserFromSessionBySocketId(final UUID socketId) {
+		final User user = socketid2user.get(socketId);
+		if (null == user) {
+			logger.warn("No user exists for socket {}.", socketId);
+
+			return;
+		}
+		user2session.remove(user);
+	}
+
+	@Override
+	public String getSessionForUser(final String username) {
+		for (final Entry<User, String> entry  : user2session.entrySet()) {
+			if (entry.getKey().getUsername().equals(username)) {
+				return entry.getValue();
+			}
+		}
+
+		return null;
+	}
+
+	@PreDestroy
+	public void destroy() {
+		logger.error("Destroy UserServiceImpl");
+	}
+
+	@Override
+	public void removeUserFromMaps(final User user) {
+		if (user != null) {
+			user2session.remove(user);
+		}
+	}
+
+	@Override
+	@Gauge
+	public int loggedInUsers() {
+		return user2session.size();
+	}
+
+	@Override
+	public DbUser getDbUser(String username) {
+		return userRepository.findUserByUsername(username.toLowerCase());
+	}
+
+	@Override
+	public DbUser createDbUser(String username, String password) {
+		String lcUsername = username.toLowerCase();
+
+		if (null == keygen) {
+			keygen = KeyGenerators.secureRandom(32);
+		}
+
+		if (null == mailPattern) {
+			parseMailAddressPattern();
+		}
+
+		if (null == mailPattern || !mailPattern.matcher(lcUsername).matches()) {
+			logger.info("User registration failed. {} does not match pattern.", lcUsername);
+
+			return null;
+		}
+
+		if (null != userRepository.findUserByUsername(lcUsername)) {
+			logger.info("User registration failed. {} already exists.", lcUsername);
+
+			return null;
+		}
+
+		DbUser dbUser = new DbUser();
+		dbUser.setUsername(lcUsername);
+		dbUser.setPassword(encodePassword(password));
+		dbUser.setActivationKey(RandomStringUtils.randomAlphanumeric(32));
+		dbUser.setCreation(System.currentTimeMillis());
+
+		DbUser result = userRepository.createOrUpdateUser(dbUser);
+		if (null != result) {
+			sendActivationEmail(result);
+		} else {
+			logger.error("User registration failed. {} could not be created.", lcUsername);
+		}
+
+		return result;
+	}
+
+	private String encodePassword(String password) {
+		if (null == encoder) {
+			encoder = new BCryptPasswordEncoder(12);
+		}
+
+		return encoder.encode(password);
+	}
+
+	private void sendActivationEmail(DbUser dbUser) {
+		String activationUrl;
+		try {
+			activationUrl = MessageFormat.format(
+				"{0}{1}/{2}?action=activate&username={3}&key={4}",
+				rootUrl,
+				customizationPath,
+				activationPath,
+				UriUtils.encodeQueryParam(dbUser.getUsername(), "UTF-8"),
+				dbUser.getActivationKey()
+			);
+		} catch (UnsupportedEncodingException e) {
+			logger.error("Sending of activation mail failed.", e);
+
+			return;
+		}
+
+		sendEmail(dbUser, regMailSubject, MessageFormat.format(regMailBody, activationUrl));
+	}
+
+	private void parseMailAddressPattern() {
+		/* TODO: Add Unicode support */
+
+		List<String> domainList = Arrays.asList(allowedEmailDomains.split(","));
+
+		if (!domainList.isEmpty()) {
+			List<String> patterns = new ArrayList<>();
+			if (domainList.contains("*")) {
+				patterns.add("([a-z0-9-]+\\.)+[a-z0-9-]+");
+			} else {
+				Pattern patternPattern = Pattern.compile("[a-z0-9.*-]+", Pattern.CASE_INSENSITIVE);
+				for (String patternStr : domainList) {
+					if (patternPattern.matcher(patternStr).matches()) {
+						patterns.add(patternStr.replaceAll("[.]", "[.]").replaceAll("[*]", "[a-z0-9-]+?"));
+					}
+				}
+			}
+
+			mailPattern = Pattern.compile("[a-z0-9._-]+?@(" + StringUtils.join(patterns, "|") + ")", Pattern.CASE_INSENSITIVE);
+			logger.info("Allowed e-mail addresses (pattern) for registration: '{}'.", mailPattern.pattern());
+		}
+	}
+
+	@Override
+	public DbUser updateDbUser(DbUser dbUser) {
+		if (null != dbUser.getId()) {
+			return userRepository.createOrUpdateUser(dbUser);
+		}
+
+		return null;
+	}
+
+	@Override
+	public DbUser deleteDbUser(String username) {
+		User user = getCurrentUser();
+		if (!user.getUsername().equals(username.toLowerCase())
+				&& !SecurityContextHolder.getContext().getAuthentication().getAuthorities()
+						.contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) {
+			throw new UnauthorizedException();
+		}
+
+		DbUser dbUser = getDbUser(username);
+		if (null == dbUser) {
+			throw new NotFoundException();
+		}
+
+		userRepository.deleteUser(dbUser);
+
+		return dbUser;
+	}
+
+	@Override
+	public void initiatePasswordReset(String username) {
+		DbUser dbUser = getDbUser(username);
+		if (null == dbUser) {
+			logger.info("Password reset failed. User {} does not exist.", username);
+
+			throw new NotFoundException();
+		}
+		if (System.currentTimeMillis() < dbUser.getPasswordResetTime() + REPEATED_PASSWORD_RESET_DELAY_MS) {
+			logger.info("Password reset failed. The reset delay for User {} is still active.", username);
+
+			throw new BadRequestException();
+		}
+
+		dbUser.setPasswordResetKey(RandomStringUtils.randomAlphanumeric(32));
+		dbUser.setPasswordResetTime(System.currentTimeMillis());
+
+		if (null == userRepository.createOrUpdateUser(dbUser)) {
+			logger.error("Password reset failed. {} could not be updated.", username);
+		}
+
+		String resetPasswordUrl;
+		try {
+			resetPasswordUrl = MessageFormat.format(
+				"{0}{1}/{2}?action=resetpassword&username={3}&key={4}",
+				rootUrl,
+				customizationPath,
+				resetPasswordPath,
+				UriUtils.encodeQueryParam(dbUser.getUsername(), "UTF-8"),
+				dbUser.getPasswordResetKey()
+			);
+		} catch (UnsupportedEncodingException e) {
+			logger.error("Sending of password reset mail failed.", e);
+
+			return;
+		}
+
+		sendEmail(dbUser, resetPasswordMailSubject, MessageFormat.format(resetPasswordMailBody, resetPasswordUrl));
+	}
+
+	@Override
+	public boolean resetPassword(DbUser dbUser, String key, String password) {
+		if (null == key || "".equals(key) || !key.equals(dbUser.getPasswordResetKey())) {
+			logger.info("Password reset failed. Invalid key provided for User {}.", dbUser.getUsername());
+
+			return false;
+		}
+		if (System.currentTimeMillis() > dbUser.getPasswordResetTime() + PASSWORD_RESET_KEY_DURABILITY_MS) {
+			logger.info("Password reset failed. Key provided for User {} is no longer valid.", dbUser.getUsername());
+
+			dbUser.setPasswordResetKey(null);
+			dbUser.setPasswordResetTime(0);
+			updateDbUser(dbUser);
+
+			return false;
+		}
+
+		dbUser.setPassword(encodePassword(password));
+		dbUser.setPasswordResetKey(null);
+		if (null == updateDbUser(dbUser)) {
+			logger.error("Password reset failed. {} could not be updated.", dbUser.getUsername());
+		}
+
+		return true;
+	}
+
+	private void sendEmail(DbUser dbUser, String subject, String body) {
+		MimeMessage msg = mailSender.createMimeMessage();
+		MimeMessageHelper helper = new MimeMessageHelper(msg, "UTF-8");
+		try {
+			helper.setFrom(mailSenderName + "<" + mailSenderAddress + ">");
+			helper.setTo(dbUser.getUsername());
+			helper.setSubject(subject);
+			helper.setText(body);
+
+			logger.info("Sending mail \"{}\" from \"{}\" to \"{}\"", subject, msg.getFrom(), dbUser.getUsername());
+			mailSender.send(msg);
+		} catch (MailException | MessagingException e) {
+			logger.warn("Mail \"{}\" could not be sent.", subject, e);
+		}
+	}
+}
diff --git a/src/main/java/de/thm/arsnova/domain/QuestionBasedLearningProgress.java b/src/main/java/de/thm/arsnova/services/score/QuestionBasedScoreCalculator.java
similarity index 87%
rename from src/main/java/de/thm/arsnova/domain/QuestionBasedLearningProgress.java
rename to src/main/java/de/thm/arsnova/services/score/QuestionBasedScoreCalculator.java
index 37b6c6397..2e28d96b4 100644
--- a/src/main/java/de/thm/arsnova/domain/QuestionBasedLearningProgress.java
+++ b/src/main/java/de/thm/arsnova/services/score/QuestionBasedScoreCalculator.java
@@ -15,28 +15,28 @@
  * 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.domain;
+package de.thm.arsnova.services.score;
 
 import de.thm.arsnova.entities.User;
-import de.thm.arsnova.entities.transport.LearningProgressValues;
+import de.thm.arsnova.entities.transport.ScoreStatistics;
 import de.thm.arsnova.persistance.SessionStatisticsRepository;
 
 /**
  * Calculates learning progress based on overall correctness of an answer. A question is answered correctly if and
  * only if the maximum question value possible has been achieved.
  */
-public class QuestionBasedLearningProgress extends VariantLearningProgress {
+public class QuestionBasedScoreCalculator extends VariantScoreCalculator {
 
-	public QuestionBasedLearningProgress(SessionStatisticsRepository sessionStatisticsRepository) {
+	public QuestionBasedScoreCalculator(SessionStatisticsRepository sessionStatisticsRepository) {
 		super(sessionStatisticsRepository);
 	}
 
 	@Override
-	protected LearningProgressValues createCourseProgress() {
+	protected ScoreStatistics createCourseProgress() {
 		final int courseProgress = calculateCourseProgress();
 		final int numerator = courseScore.getQuestionCount() * courseProgress / 100;
 		final int denominator = courseScore.getQuestionCount();
-		LearningProgressValues lpv = new LearningProgressValues();
+		ScoreStatistics lpv = new ScoreStatistics();
 		lpv.setCourseProgress(courseProgress);
 		lpv.setNumQuestions(courseScore.getQuestionCount());
 		lpv.setNumUsers(courseScore.getTotalUserCount());
@@ -71,10 +71,10 @@ public class QuestionBasedLearningProgress extends VariantLearningProgress {
 	}
 
 	@Override
-	protected LearningProgressValues createMyProgress(User user) {
+	protected ScoreStatistics createMyProgress(User user) {
 		final int numerator = numQuestionsCorrectForUser(user);
 		final int denominator = courseScore.getQuestionCount();
-		LearningProgressValues lpv = new LearningProgressValues();
+		ScoreStatistics lpv = new ScoreStatistics();
 		lpv.setCourseProgress(calculateCourseProgress());
 		lpv.setMyProgress(myPercentage(numerator, denominator));
 		lpv.setNumQuestions(courseScore.getQuestionCount());
diff --git a/src/main/java/de/thm/arsnova/domain/QuestionScore.java b/src/main/java/de/thm/arsnova/services/score/QuestionScore.java
similarity index 96%
rename from src/main/java/de/thm/arsnova/domain/QuestionScore.java
rename to src/main/java/de/thm/arsnova/services/score/QuestionScore.java
index 32b5b6f52..88ecf31bd 100644
--- a/src/main/java/de/thm/arsnova/domain/QuestionScore.java
+++ b/src/main/java/de/thm/arsnova/services/score/QuestionScore.java
@@ -15,7 +15,7 @@
  * 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.domain;
+package de.thm.arsnova.services.score;
 
 import de.thm.arsnova.entities.User;
 
@@ -25,7 +25,7 @@ import java.util.List;
 import java.util.Set;
 
 /**
- * Calculates learning progress score for a specific question.
+ * Calculates score for a specific question.
  */
 public class QuestionScore implements Iterable<UserScore> {
 
diff --git a/src/main/java/de/thm/arsnova/domain/CourseScore.java b/src/main/java/de/thm/arsnova/services/score/Score.java
similarity index 87%
rename from src/main/java/de/thm/arsnova/domain/CourseScore.java
rename to src/main/java/de/thm/arsnova/services/score/Score.java
index 40a3cf091..f7fdcb69d 100644
--- a/src/main/java/de/thm/arsnova/domain/CourseScore.java
+++ b/src/main/java/de/thm/arsnova/services/score/Score.java
@@ -15,7 +15,7 @@
  * 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.domain;
+package de.thm.arsnova.services.score;
 
 import de.thm.arsnova.entities.User;
 
@@ -27,17 +27,17 @@ import java.util.Map.Entry;
 import java.util.Set;
 
 /**
- * Calculates the learning progress for users and their courses.
+ * Calculates the score for users and their sessions.
  */
-public class CourseScore implements Iterable<QuestionScore> {
+public class Score implements Iterable<QuestionScore> {
 
 	private final Map<String, QuestionScore> scores;
 
-	public CourseScore() {
+	public Score() {
 		this(new HashMap<String, QuestionScore>());
 	}
 
-	public CourseScore(Map<String, QuestionScore> theScores) {
+	public Score(Map<String, QuestionScore> theScores) {
 		this.scores = theScores;
 	}
 
@@ -51,7 +51,7 @@ public class CourseScore implements Iterable<QuestionScore> {
 	public void addAnswer(String questionId, int piRound, String username, int userscore) {
 		if (!scores.containsKey(questionId)) {
 			// Precondition failed, ignore this element.
-			// Most likely this is a question that has no learning progress value.
+			// Most likely this is a question that has no score value.
 			return;
 		}
 		if (username == null || username.isEmpty()) {
@@ -62,7 +62,7 @@ public class CourseScore implements Iterable<QuestionScore> {
 		questionScore.add(piRound, username, userscore);
 	}
 
-	public CourseScore filterVariant(String questionVariant) {
+	public Score filterVariant(String questionVariant) {
 		Map<String, QuestionScore> newScores = new HashMap<>();
 		for (Entry<String, QuestionScore> entry : this.scores.entrySet()) {
 			String questionId = entry.getKey();
@@ -71,7 +71,7 @@ public class CourseScore implements Iterable<QuestionScore> {
 				newScores.put(questionId, questionScore);
 			}
 		}
-		return new CourseScore(newScores);
+		return new Score(newScores);
 	}
 
 	public int getMaximumScore() {
diff --git a/src/main/java/de/thm/arsnova/domain/PointBasedLearningProgress.java b/src/main/java/de/thm/arsnova/services/score/ScoreBasedScoreCalculator.java
similarity index 80%
rename from src/main/java/de/thm/arsnova/domain/PointBasedLearningProgress.java
rename to src/main/java/de/thm/arsnova/services/score/ScoreBasedScoreCalculator.java
index 21f2fbd3d..309c9bef0 100644
--- a/src/main/java/de/thm/arsnova/domain/PointBasedLearningProgress.java
+++ b/src/main/java/de/thm/arsnova/services/score/ScoreBasedScoreCalculator.java
@@ -15,24 +15,24 @@
  * 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.domain;
+package de.thm.arsnova.services.score;
 
 import de.thm.arsnova.entities.User;
-import de.thm.arsnova.entities.transport.LearningProgressValues;
+import de.thm.arsnova.entities.transport.ScoreStatistics;
 import de.thm.arsnova.persistance.SessionStatisticsRepository;
 
 /**
- * Calculates learning progress based on a question's value.
+ * Calculates score based on a question's value.
  */
-public class PointBasedLearningProgress extends VariantLearningProgress {
+public class ScoreBasedScoreCalculator extends VariantScoreCalculator {
 
-	public PointBasedLearningProgress(SessionStatisticsRepository sessionStatisticsRepository) {
+	public ScoreBasedScoreCalculator(SessionStatisticsRepository sessionStatisticsRepository) {
 		super(sessionStatisticsRepository);
 	}
 
 	@Override
-	protected LearningProgressValues createCourseProgress() {
-		LearningProgressValues lpv = new LearningProgressValues();
+	protected ScoreStatistics createCourseProgress() {
+		ScoreStatistics lpv = new ScoreStatistics();
 		lpv.setCourseProgress(coursePercentage());
 		lpv.setNumQuestions(courseScore.getQuestionCount());
 		lpv.setNumUsers(courseScore.getTotalUserCount());
@@ -54,8 +54,8 @@ public class PointBasedLearningProgress extends VariantLearningProgress {
 	}
 
 	@Override
-	protected LearningProgressValues createMyProgress(User user) {
-		LearningProgressValues lpv = new LearningProgressValues();
+	protected ScoreStatistics createMyProgress(User user) {
+		ScoreStatistics lpv = new ScoreStatistics();
 		lpv.setCourseProgress(coursePercentage());
 		lpv.setNumQuestions(courseScore.getQuestionCount());
 		lpv.setNumUsers(courseScore.getTotalUserCount());
diff --git a/src/main/java/de/thm/arsnova/domain/LearningProgress.java b/src/main/java/de/thm/arsnova/services/score/ScoreCalculator.java
similarity index 70%
rename from src/main/java/de/thm/arsnova/domain/LearningProgress.java
rename to src/main/java/de/thm/arsnova/services/score/ScoreCalculator.java
index e3f0f3174..8dbaa8a7a 100644
--- a/src/main/java/de/thm/arsnova/domain/LearningProgress.java
+++ b/src/main/java/de/thm/arsnova/services/score/ScoreCalculator.java
@@ -15,18 +15,18 @@
  * 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.domain;
+package de.thm.arsnova.services.score;
 
 import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.User;
-import de.thm.arsnova.entities.transport.LearningProgressValues;
+import de.thm.arsnova.entities.transport.ScoreStatistics;
 
 /**
- * Defines the core functionality which the learning progress calculation should provide.
+ * Defines the core functionality which the score calculation should provide.
  */
-public interface LearningProgress {
+public interface ScoreCalculator {
 
-	LearningProgressValues getCourseProgress(Session session);
+	ScoreStatistics getCourseProgress(Session session);
 
-	LearningProgressValues getMyProgress(Session session, User user);
+	ScoreStatistics getMyProgress(Session session, User user);
 }
diff --git a/src/main/java/de/thm/arsnova/domain/ILearningProgressFactory.java b/src/main/java/de/thm/arsnova/services/score/ScoreCalculatorFactory.java
similarity index 84%
rename from src/main/java/de/thm/arsnova/domain/ILearningProgressFactory.java
rename to src/main/java/de/thm/arsnova/services/score/ScoreCalculatorFactory.java
index 072682946..f7125e807 100644
--- a/src/main/java/de/thm/arsnova/domain/ILearningProgressFactory.java
+++ b/src/main/java/de/thm/arsnova/services/score/ScoreCalculatorFactory.java
@@ -15,13 +15,13 @@
  * 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.domain;
+package de.thm.arsnova.services.score;
 
 /**
  * Interface for Spring dependency injection.
  */
-public interface ILearningProgressFactory {
+public interface ScoreCalculatorFactory {
 
-	LearningProgress create(String progressType, String questionVariant);
+	ScoreCalculator create(String type, String questionVariant);
 
 }
diff --git a/src/main/java/de/thm/arsnova/domain/LearningProgressFactory.java b/src/main/java/de/thm/arsnova/services/score/ScoreCalculatorFactoryImpl.java
similarity index 68%
rename from src/main/java/de/thm/arsnova/domain/LearningProgressFactory.java
rename to src/main/java/de/thm/arsnova/services/score/ScoreCalculatorFactoryImpl.java
index a5596c0b7..c6214cd66 100644
--- a/src/main/java/de/thm/arsnova/domain/LearningProgressFactory.java
+++ b/src/main/java/de/thm/arsnova/services/score/ScoreCalculatorFactoryImpl.java
@@ -15,7 +15,7 @@
  * 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.domain;
+package de.thm.arsnova.services.score;
 
 import de.thm.arsnova.events.*;
 import de.thm.arsnova.persistance.SessionStatisticsRepository;
@@ -26,12 +26,12 @@ import org.springframework.context.ApplicationEventPublisherAware;
 import org.springframework.stereotype.Component;
 
 /**
- * Creates a learning progress implementation.
+ * Creates a score calculator implementation.
  *
- * This class additionally clears all learning progress caches and reports this via event system.
+ * This class additionally clears all score caches and reports this via event system.
  */
 @Component
-public class LearningProgressFactory implements NovaEventVisitor, ILearningProgressFactory, ApplicationEventPublisherAware {
+public class ScoreCalculatorFactoryImpl implements ArsnovaEventVisitor, ScoreCalculatorFactory, ApplicationEventPublisherAware {
 
 	@Autowired
 	private SessionStatisticsRepository sessionStatisticsRepository;
@@ -39,15 +39,15 @@ public class LearningProgressFactory implements NovaEventVisitor, ILearningProgr
 	private ApplicationEventPublisher publisher;
 
 	@Override
-	public LearningProgress create(String progressType, String questionVariant) {
-		VariantLearningProgress learningProgress;
-		if ("questions".equals(progressType)) {
-			learningProgress = new QuestionBasedLearningProgress(sessionStatisticsRepository);
+	public ScoreCalculator create(String type, String questionVariant) {
+		VariantScoreCalculator scoreCalculator;
+		if ("questions".equals(type)) {
+			scoreCalculator = new QuestionBasedScoreCalculator(sessionStatisticsRepository);
 		} else {
-			learningProgress = new PointBasedLearningProgress(sessionStatisticsRepository);
+			scoreCalculator = new ScoreBasedScoreCalculator(sessionStatisticsRepository);
 		}
-		learningProgress.setQuestionVariant(questionVariant);
-		return learningProgress;
+		scoreCalculator.setQuestionVariant(questionVariant);
+		return scoreCalculator;
 	}
 
 	@Override
@@ -59,79 +59,79 @@ public class LearningProgressFactory implements NovaEventVisitor, ILearningProgr
 	@CacheEvict(value = "learningprogress", key = "#event.Session")
 	@Override
 	public void visit(NewQuestionEvent event) {
-		this.publisher.publishEvent(new ChangeLearningProgressEvent(this, event.getSession()));
+		this.publisher.publishEvent(new ChangeScoreEvent(this, event.getSession()));
 	}
 
 	@CacheEvict(value = "learningprogress", key = "#event.Session")
 	@Override
 	public void visit(UnlockQuestionEvent event) {
-		this.publisher.publishEvent(new ChangeLearningProgressEvent(this, event.getSession()));
+		this.publisher.publishEvent(new ChangeScoreEvent(this, event.getSession()));
 	}
 
 	@CacheEvict(value = "learningprogress", key = "#event.Session")
 	@Override
 	public void visit(UnlockQuestionsEvent event) {
-		this.publisher.publishEvent(new ChangeLearningProgressEvent(this, event.getSession()));
+		this.publisher.publishEvent(new ChangeScoreEvent(this, event.getSession()));
 	}
 
 	@CacheEvict(value = "learningprogress", key = "#event.Session")
 	@Override
 	public void visit(LockQuestionEvent event) {
-		this.publisher.publishEvent(new ChangeLearningProgressEvent(this, event.getSession()));
+		this.publisher.publishEvent(new ChangeScoreEvent(this, event.getSession()));
 	}
 
 	@CacheEvict(value = "learningprogress", key = "#event.Session")
 	@Override
 	public void visit(LockQuestionsEvent event) {
-		this.publisher.publishEvent(new ChangeLearningProgressEvent(this, event.getSession()));
+		this.publisher.publishEvent(new ChangeScoreEvent(this, event.getSession()));
 	}
 
 	@CacheEvict(value = "learningprogress", key = "#event.Session")
 	@Override
 	public void visit(NewAnswerEvent event) {
-		this.publisher.publishEvent(new ChangeLearningProgressEvent(this, event.getSession()));
+		this.publisher.publishEvent(new ChangeScoreEvent(this, event.getSession()));
 	}
 
 	@CacheEvict(value = "learningprogress", key = "#event.Session")
 	@Override
 	public void visit(DeleteAnswerEvent event) {
-		this.publisher.publishEvent(new ChangeLearningProgressEvent(this, event.getSession()));
+		this.publisher.publishEvent(new ChangeScoreEvent(this, event.getSession()));
 	}
 
 	@CacheEvict(value = "learningprogress", key = "#event.Session")
 	@Override
 	public void visit(DeleteQuestionEvent event) {
-		this.publisher.publishEvent(new ChangeLearningProgressEvent(this, event.getSession()));
+		this.publisher.publishEvent(new ChangeScoreEvent(this, event.getSession()));
 	}
 
 	@CacheEvict(value = "learningprogress", key = "#event.Session")
 	@Override
 	public void visit(DeleteAllQuestionsEvent event) {
-		this.publisher.publishEvent(new ChangeLearningProgressEvent(this, event.getSession()));
+		this.publisher.publishEvent(new ChangeScoreEvent(this, event.getSession()));
 	}
 
 	@CacheEvict(value = "learningprogress", key = "#event.Session")
 	@Override
 	public void visit(DeleteAllQuestionsAnswersEvent event) {
-		this.publisher.publishEvent(new ChangeLearningProgressEvent(this, event.getSession()));
+		this.publisher.publishEvent(new ChangeScoreEvent(this, event.getSession()));
 	}
 
 	@CacheEvict(value = "learningprogress", key = "#event.Session")
 	@Override
 	public void visit(DeleteAllPreparationAnswersEvent event) {
-		this.publisher.publishEvent(new ChangeLearningProgressEvent(this, event.getSession()));
+		this.publisher.publishEvent(new ChangeScoreEvent(this, event.getSession()));
 	}
 
 	@CacheEvict(value = "learningprogress", key = "#event.Session")
 	@Override
 	public void visit(DeleteAllLectureAnswersEvent event) {
-		this.publisher.publishEvent(new ChangeLearningProgressEvent(this, event.getSession()));
+		this.publisher.publishEvent(new ChangeScoreEvent(this, event.getSession()));
 	}
 
 	@CacheEvict(value = "learningprogress", key = "#event.Session")
 	@Override
 	public void visit(PiRoundResetEvent event) {
-		this.publisher.publishEvent(new ChangeLearningProgressEvent(this, event.getSession()));
+		this.publisher.publishEvent(new ChangeScoreEvent(this, event.getSession()));
 	}
 
 	@Override
@@ -144,7 +144,7 @@ public class LearningProgressFactory implements NovaEventVisitor, ILearningProgr
 	public void visit(StatusSessionEvent statusSessionEvent) { }
 
 	@Override
-	public void visit(ChangeLearningProgressEvent changeLearningProgress) { }
+	public void visit(ChangeScoreEvent changeLearningProgress) { }
 
 	@Override
 	public void visit(PiRoundDelayedStartEvent piRoundDelayedStartEvent) { }
diff --git a/src/main/java/de/thm/arsnova/domain/LearningProgressListener.java b/src/main/java/de/thm/arsnova/services/score/ScoreCalculatorListener.java
similarity index 70%
rename from src/main/java/de/thm/arsnova/domain/LearningProgressListener.java
rename to src/main/java/de/thm/arsnova/services/score/ScoreCalculatorListener.java
index 2a11b6d4c..ded6ef1d0 100644
--- a/src/main/java/de/thm/arsnova/domain/LearningProgressListener.java
+++ b/src/main/java/de/thm/arsnova/services/score/ScoreCalculatorListener.java
@@ -15,28 +15,28 @@
  * 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.domain;
+package de.thm.arsnova.services.score;
 
-import de.thm.arsnova.events.NovaEvent;
-import de.thm.arsnova.events.NovaEventVisitor;
+import de.thm.arsnova.events.ArsnovaEvent;
+import de.thm.arsnova.events.ArsnovaEventVisitor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationListener;
 import org.springframework.stereotype.Component;
 
 /**
- * Listener registration for the learning progress.
+ * Listener registration for the score.
  *
  * Note that this class is necessary in order for the annotations to work.
  */
 @Component
-public class LearningProgressListener implements ApplicationListener<NovaEvent> {
+public class ScoreCalculatorListener implements ApplicationListener<ArsnovaEvent> {
 
 	@Autowired
-	private ILearningProgressFactory learningProgressFactory;
+	private ScoreCalculatorFactory scoreCalculatorFactory;
 
 	@Override
-	public void onApplicationEvent(NovaEvent event) {
-		event.accept((NovaEventVisitor) learningProgressFactory);
+	public void onApplicationEvent(ArsnovaEvent event) {
+		event.accept((ArsnovaEventVisitor) scoreCalculatorFactory);
 	}
 
 }
diff --git a/src/main/java/de/thm/arsnova/domain/UserScore.java b/src/main/java/de/thm/arsnova/services/score/UserScore.java
similarity index 97%
rename from src/main/java/de/thm/arsnova/domain/UserScore.java
rename to src/main/java/de/thm/arsnova/services/score/UserScore.java
index dddd611df..9a31de8e2 100644
--- a/src/main/java/de/thm/arsnova/domain/UserScore.java
+++ b/src/main/java/de/thm/arsnova/services/score/UserScore.java
@@ -15,7 +15,7 @@
  * 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.domain;
+package de.thm.arsnova.services.score;
 
 import de.thm.arsnova.entities.User;
 
diff --git a/src/main/java/de/thm/arsnova/domain/VariantLearningProgress.java b/src/main/java/de/thm/arsnova/services/score/VariantScoreCalculator.java
similarity index 71%
rename from src/main/java/de/thm/arsnova/domain/VariantLearningProgress.java
rename to src/main/java/de/thm/arsnova/services/score/VariantScoreCalculator.java
index 7e1c06970..3fb7266b1 100644
--- a/src/main/java/de/thm/arsnova/domain/VariantLearningProgress.java
+++ b/src/main/java/de/thm/arsnova/services/score/VariantScoreCalculator.java
@@ -15,25 +15,25 @@
  * 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.domain;
+package de.thm.arsnova.services.score;
 
 import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.User;
-import de.thm.arsnova.entities.transport.LearningProgressValues;
+import de.thm.arsnova.entities.transport.ScoreStatistics;
 import de.thm.arsnova.persistance.SessionStatisticsRepository;
 
 /**
- * Base class for the learning progress feature that allows filtering on the question variant.
+ * Base class for the score feature that allows filtering on the question variant.
  */
-abstract class VariantLearningProgress implements LearningProgress {
+abstract class VariantScoreCalculator implements ScoreCalculator {
 
-	protected CourseScore courseScore;
+	protected Score courseScore;
 
 	private String questionVariant;
 
 	private final SessionStatisticsRepository sessionStatisticsRepository;
 
-	public VariantLearningProgress(final SessionStatisticsRepository sessionStatisticsRepository) {
+	public VariantScoreCalculator(final SessionStatisticsRepository sessionStatisticsRepository) {
 		this.sessionStatisticsRepository = sessionStatisticsRepository;
 	}
 
@@ -46,16 +46,16 @@ abstract class VariantLearningProgress implements LearningProgress {
 	}
 
 	@Override
-	public LearningProgressValues getCourseProgress(Session session) {
+	public ScoreStatistics getCourseProgress(Session session) {
 		this.loadProgress(session);
 		this.filterVariant();
 		return this.createCourseProgress();
 	}
 
-	protected abstract LearningProgressValues createCourseProgress();
+	protected abstract ScoreStatistics createCourseProgress();
 
 	@Override
-	public LearningProgressValues getMyProgress(Session session, User user) {
+	public ScoreStatistics getMyProgress(Session session, User user) {
 		this.loadProgress(session);
 		this.filterVariant();
 		return this.createMyProgress(user);
@@ -67,6 +67,6 @@ abstract class VariantLearningProgress implements LearningProgress {
 		}
 	}
 
-	protected abstract LearningProgressValues createMyProgress(User user);
+	protected abstract ScoreStatistics createMyProgress(User user);
 
 }
diff --git a/src/main/java/de/thm/arsnova/ImageUtils.java b/src/main/java/de/thm/arsnova/util/ImageUtils.java
similarity index 99%
rename from src/main/java/de/thm/arsnova/ImageUtils.java
rename to src/main/java/de/thm/arsnova/util/ImageUtils.java
index 6349cc844..4c676add1 100644
--- a/src/main/java/de/thm/arsnova/ImageUtils.java
+++ b/src/main/java/de/thm/arsnova/util/ImageUtils.java
@@ -15,7 +15,7 @@
  * 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;
+package de.thm.arsnova.util;
 
 import de.thm.arsnova.entities.Answer;
 import org.apache.commons.codec.binary.Base64;
diff --git a/src/main/java/de/thm/arsnova/PaginationListDecorator.java b/src/main/java/de/thm/arsnova/util/PaginationListDecorator.java
similarity index 99%
rename from src/main/java/de/thm/arsnova/PaginationListDecorator.java
rename to src/main/java/de/thm/arsnova/util/PaginationListDecorator.java
index 6f9fcf448..1a764ce6c 100644
--- a/src/main/java/de/thm/arsnova/PaginationListDecorator.java
+++ b/src/main/java/de/thm/arsnova/util/PaginationListDecorator.java
@@ -15,7 +15,7 @@
  * 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;
+package de.thm.arsnova.util;
 
 import java.util.Collection;
 import java.util.Iterator;
diff --git a/src/main/java/de/thm/arsnova/socket/ARSnovaSocket.java b/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServer.java
similarity index 92%
rename from src/main/java/de/thm/arsnova/socket/ARSnovaSocket.java
rename to src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServer.java
index 313e330cd..800b9314d 100644
--- a/src/main/java/de/thm/arsnova/socket/ARSnovaSocket.java
+++ b/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServer.java
@@ -15,14 +15,14 @@
  * 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.socket;
+package de.thm.arsnova.websocket;
 
 /**
  * This interface is used to auto-wire the Socket Server.
  *
  * Extend this interface as you see fit.
  */
-public interface ARSnovaSocket {
+public interface ArsnovaSocketioServer {
 
 	boolean isUseSSL();
 
diff --git a/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java b/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java
similarity index 95%
rename from src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java
rename to src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java
index b348dc32c..6cd3bf2a8 100644
--- a/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java
+++ b/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java
@@ -15,7 +15,7 @@
  * 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.socket;
+package de.thm.arsnova.websocket;
 
 import com.codahale.metrics.annotation.Timed;
 import com.corundumstudio.socketio.AckRequest;
@@ -30,18 +30,18 @@ import com.corundumstudio.socketio.protocol.Packet;
 import com.corundumstudio.socketio.protocol.PacketType;
 import de.thm.arsnova.entities.Comment;
 import de.thm.arsnova.entities.User;
-import de.thm.arsnova.entities.transport.LearningProgressOptions;
+import de.thm.arsnova.entities.transport.ScoreOptions;
 import de.thm.arsnova.events.*;
 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.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.Content;
-import de.thm.arsnova.socket.message.Session;
+import de.thm.arsnova.services.FeedbackService;
+import de.thm.arsnova.services.ContentService;
+import de.thm.arsnova.services.SessionService;
+import de.thm.arsnova.services.UserService;
+import de.thm.arsnova.websocket.message.Feedback;
+import de.thm.arsnova.websocket.message.Content;
+import de.thm.arsnova.websocket.message.Session;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -64,21 +64,21 @@ import java.util.UUID;
  * Web socket implementation based on Socket.io.
  */
 @Component
-public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
+public class ArsnovaSocketioServerImpl implements ArsnovaSocketioServer, ArsnovaEventVisitor {
 
 	@Autowired
-	private IFeedbackService feedbackService;
+	private FeedbackService feedbackService;
 
 	@Autowired
-	private IUserService userService;
+	private UserService userService;
 
 	@Autowired
-	private ISessionService sessionService;
+	private SessionService sessionService;
 
 	@Autowired
-	private IContentService contentService;
+	private ContentService contentService;
 
-	private static final Logger logger = LoggerFactory.getLogger(ARSnovaSocketIOServer.class);
+	private static final Logger logger = LoggerFactory.getLogger(ArsnovaSocketioServerImpl.class);
 
 	private int portNumber;
 	private String hostIp;
@@ -88,7 +88,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 	private final Configuration config;
 	private SocketIOServer server;
 
-	public ARSnovaSocketIOServer() {
+	public ArsnovaSocketioServerImpl() {
 		config = new Configuration();
 	}
 
@@ -212,17 +212,17 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 
 		server.addEventListener(
 				"setLearningProgressOptions",
-				LearningProgressOptions.class,
-				new DataListener<LearningProgressOptions>() {
+				ScoreOptions.class,
+				new DataListener<ScoreOptions>() {
 			@Override
 			@Timed(name = "setLearningProgressOptionsEvent.onData")
-			public void onData(SocketIOClient client, LearningProgressOptions progressOptions, AckRequest ack) {
+			public void onData(SocketIOClient client, ScoreOptions scoreOptions, AckRequest ack) {
 				final User user = userService.getUser2SocketId(client.getSessionId());
-				final de.thm.arsnova.entities.Session session = sessionService.getSessionInternal(progressOptions.getSessionKeyword(), user);
+				final de.thm.arsnova.entities.Session session = sessionService.getSessionInternal(scoreOptions.getSessionKeyword(), user);
 				if (session.isCreator(user)) {
-					session.setLearningProgressOptions(progressOptions.toEntity());
+					session.setLearningProgressOptions(scoreOptions.toEntity());
 					sessionService.updateSessionInternal(session, user);
-					broadcastInSession(session.getKeyword(), "learningProgressOptions", progressOptions.toEntity());
+					broadcastInSession(session.getKeyword(), "learningProgressOptions", scoreOptions.toEntity());
 				}
 			}
 		});
@@ -665,7 +665,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 	}
 
 	@Override
-	public void visit(ChangeLearningProgressEvent event) {
+	public void visit(ChangeScoreEvent event) {
 		broadcastInSession(event.getSession().getKeyword(), "learningProgressChange", null);
 	}
 
diff --git a/src/main/java/de/thm/arsnova/socket/ARSnovaSocketListener.java b/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerListener.java
similarity index 75%
rename from src/main/java/de/thm/arsnova/socket/ARSnovaSocketListener.java
rename to src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerListener.java
index 6c740888f..8bcfb88e0 100644
--- a/src/main/java/de/thm/arsnova/socket/ARSnovaSocketListener.java
+++ b/src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerListener.java
@@ -15,10 +15,10 @@
  * 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.socket;
+package de.thm.arsnova.websocket;
 
-import de.thm.arsnova.events.NovaEvent;
-import de.thm.arsnova.events.NovaEventVisitor;
+import de.thm.arsnova.events.ArsnovaEvent;
+import de.thm.arsnova.events.ArsnovaEventVisitor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationListener;
 import org.springframework.stereotype.Component;
@@ -28,14 +28,14 @@ import org.springframework.stereotype.Component;
  * This would result in Spring method annotations not working.
  */
 @Component
-public class ARSnovaSocketListener implements ApplicationListener<NovaEvent> {
+public class ArsnovaSocketioServerListener implements ApplicationListener<ArsnovaEvent> {
 
 	@Autowired
-	private ARSnovaSocket socketServer;
+	private ArsnovaSocketioServer socketioServer;
 
 	@Override
-	public void onApplicationEvent(NovaEvent event) {
-		event.accept((NovaEventVisitor) socketServer);
+	public void onApplicationEvent(ArsnovaEvent event) {
+		event.accept((ArsnovaEventVisitor) socketioServer);
 	}
 
 }
diff --git a/src/main/java/de/thm/arsnova/socket/message/Content.java b/src/main/java/de/thm/arsnova/websocket/message/Content.java
similarity index 96%
rename from src/main/java/de/thm/arsnova/socket/message/Content.java
rename to src/main/java/de/thm/arsnova/websocket/message/Content.java
index f462baa7b..73e84c3ec 100644
--- a/src/main/java/de/thm/arsnova/socket/message/Content.java
+++ b/src/main/java/de/thm/arsnova/websocket/message/Content.java
@@ -15,7 +15,7 @@
  * 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.socket.message;
+package de.thm.arsnova.websocket.message;
 
 /**
  * Represents a question.
diff --git a/src/main/java/de/thm/arsnova/socket/message/Feedback.java b/src/main/java/de/thm/arsnova/websocket/message/Feedback.java
similarity index 96%
rename from src/main/java/de/thm/arsnova/socket/message/Feedback.java
rename to src/main/java/de/thm/arsnova/websocket/message/Feedback.java
index 211e10d6a..0f7966b14 100644
--- a/src/main/java/de/thm/arsnova/socket/message/Feedback.java
+++ b/src/main/java/de/thm/arsnova/websocket/message/Feedback.java
@@ -15,7 +15,7 @@
  * 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.socket.message;
+package de.thm.arsnova.websocket.message;
 
 /**
  * The feedback values.
diff --git a/src/main/java/de/thm/arsnova/socket/message/Session.java b/src/main/java/de/thm/arsnova/websocket/message/Session.java
similarity index 95%
rename from src/main/java/de/thm/arsnova/socket/message/Session.java
rename to src/main/java/de/thm/arsnova/websocket/message/Session.java
index 88f7c53d2..9e13bb223 100644
--- a/src/main/java/de/thm/arsnova/socket/message/Session.java
+++ b/src/main/java/de/thm/arsnova/websocket/message/Session.java
@@ -15,7 +15,7 @@
  * 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.socket.message;
+package de.thm.arsnova.websocket.message;
 
 /**
  * Represents a session.
diff --git a/src/main/java/de/thm/arsnova/socket/message/package-info.java b/src/main/java/de/thm/arsnova/websocket/message/package-info.java
similarity index 60%
rename from src/main/java/de/thm/arsnova/socket/message/package-info.java
rename to src/main/java/de/thm/arsnova/websocket/message/package-info.java
index 98beea04a..0af4ab419 100644
--- a/src/main/java/de/thm/arsnova/socket/message/package-info.java
+++ b/src/main/java/de/thm/arsnova/websocket/message/package-info.java
@@ -1,4 +1,4 @@
 /**
  * Contains classes that are used as web socket messages
  */
-package de.thm.arsnova.socket.message;
+package de.thm.arsnova.websocket.message;
diff --git a/src/main/java/de/thm/arsnova/socket/package-info.java b/src/main/java/de/thm/arsnova/websocket/package-info.java
similarity index 66%
rename from src/main/java/de/thm/arsnova/socket/package-info.java
rename to src/main/java/de/thm/arsnova/websocket/package-info.java
index d4f65e727..3fd6e43c0 100644
--- a/src/main/java/de/thm/arsnova/socket/package-info.java
+++ b/src/main/java/de/thm/arsnova/websocket/package-info.java
@@ -1,4 +1,4 @@
 /**
  * Classes and interfaces for communication over web sockets
  */
-package de.thm.arsnova.socket;
+package de.thm.arsnova.websocket;
diff --git a/src/site/markdown/development/event-system.md b/src/site/markdown/development/event-system.md
index d9f4459a1..71132e71b 100644
--- a/src/site/markdown/development/event-system.md
+++ b/src/site/markdown/development/event-system.md
@@ -11,18 +11,18 @@ A class is able to send events by implementing the `ApplicationEventPublisherAwa
 ```java
 publisher.publishEvent(theEvent);
 ```
-where `theEvent` is an object of type `ApplicationEvent`. For ARSnova, the base class `NovaEvent` should be used instead. All of ARSnova's internal events are subtypes of `NovaEvent`.
+where `theEvent` is an object of type `ApplicationEvent`. For ARSnova, the base class `ArsovaEvent` should be used instead. All of ARSnova's internal events are subtypes of `ArsovaEvent`.
 
 _Note_: Events are sent and received on the same thread, i.e., it is a synchronous operation.
 
 
 ## How to receive events?
 
-Events are received by implementing the `ApplicationListener<NovaEvent>` interface. The associated method gets passed in a `NovaEvent`, which is the base class of all of ARSnova's events. However, this type itself is not very useful. The real type can be revealed using double dispatch, which is the basis of the Visitor pattern. Therefore, the event should be forwarded to a class that implements the `NovaEventVisitor` interface. This could be the same class that received the event.
+Events are received by implementing the `ApplicationListener<ArsovaEvent>` interface. The associated method gets passed in a `ArsovaEvent`, which is the base class of all of ARSnova's events. However, this type itself is not very useful. The real type can be revealed using double dispatch, which is the basis of the Visitor pattern. Therefore, the event should be forwarded to a class that implements the `ArsovaEvent` interface. This could be the same class that received the event.
 
 _Note_: If the class implementing the Visitor needs to have some of Spring's annotations on the event methods, like, for example, to cache some values using `@Cacheable`, the Listener and the Visitor must be different objects.
 
 
 ## How to create custom events?
 
-Subclass either `NovaEvent` or `SessionEvent`. The former is for generic events that are not tied to a specific session, while the latter is for cases where the event only makes sense in the context of a session.
+Subclass either `ArsovaEvent` or `SessionEvent`. The former is for generic events that are not tied to a specific session, while the latter is for cases where the event only makes sense in the context of a session.
diff --git a/src/test/java/de/thm/arsnova/config/TestAppConfig.java b/src/test/java/de/thm/arsnova/config/TestAppConfig.java
index f2759881e..c3517272d 100644
--- a/src/test/java/de/thm/arsnova/config/TestAppConfig.java
+++ b/src/test/java/de/thm/arsnova/config/TestAppConfig.java
@@ -1,8 +1,8 @@
 package de.thm.arsnova.config;
 
 import de.thm.arsnova.services.StubUserService;
-import de.thm.arsnova.socket.ARSnovaSocket;
-import de.thm.arsnova.socket.ARSnovaSocketIOServer;
+import de.thm.arsnova.websocket.ArsnovaSocketioServer;
+import de.thm.arsnova.websocket.ArsnovaSocketioServerImpl;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.beans.factory.config.CustomScopeConfigurer;
 import org.springframework.cache.annotation.EnableCaching;
@@ -10,6 +10,7 @@ import org.springframework.context.annotation.AdviceMode;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
 import org.springframework.context.annotation.Profile;
 import org.springframework.context.annotation.PropertySource;
 import org.springframework.context.annotation.aspectj.EnableSpringConfigured;
@@ -23,7 +24,6 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 		"de.thm.arsnova.aop",
 		"de.thm.arsnova.cache",
 		"de.thm.arsnova.controller",
-		"de.thm.arsnova.domain",
 		"de.thm.arsnova.dao",
 		"de.thm.arsnova.events",
 		"de.thm.arsnova.security",
@@ -59,9 +59,9 @@ public class TestAppConfig {
 	}
 
 	@Bean(name = "socketServer", initMethod = "startServer", destroyMethod = "stopServer")
-	public ARSnovaSocket socketTestServer() {
+	public ArsnovaSocketioServer socketTestServer() {
 		final int testSocketPort = 1234 + testPortOffset++ % 10;
-		final ARSnovaSocketIOServer socketServer = new ARSnovaSocketIOServer();
+		final ArsnovaSocketioServerImpl socketServer = new ArsnovaSocketioServerImpl();
 		socketServer.setHostIp(socketAddress);
 		socketServer.setPortNumber(socketPort + testSocketPort);
 
@@ -69,6 +69,7 @@ public class TestAppConfig {
 	}
 
 	@Bean
+	@Primary
 	public StubUserService stubUserService() {
 		return new StubUserService();
 	}
diff --git a/src/test/java/de/thm/arsnova/config/TestSecurityConfig.java b/src/test/java/de/thm/arsnova/config/TestSecurityConfig.java
index 6ad50678a..17716f734 100644
--- a/src/test/java/de/thm/arsnova/config/TestSecurityConfig.java
+++ b/src/test/java/de/thm/arsnova/config/TestSecurityConfig.java
@@ -17,7 +17,7 @@
  */
 package de.thm.arsnova.config;
 
-import de.thm.arsnova.CasUserDetailsService;
+import de.thm.arsnova.security.CasUserDetailsService;
 import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
 import org.pac4j.oauth.client.FacebookClient;
 import org.pac4j.oauth.client.Google2Client;
diff --git a/src/test/java/de/thm/arsnova/services/StubUserService.java b/src/test/java/de/thm/arsnova/services/StubUserService.java
index b072375b6..6a923b213 100644
--- a/src/test/java/de/thm/arsnova/services/StubUserService.java
+++ b/src/test/java/de/thm/arsnova/services/StubUserService.java
@@ -20,7 +20,7 @@ package de.thm.arsnova.services;
 import de.thm.arsnova.entities.User;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 
-public class StubUserService extends UserService {
+public class StubUserService extends UserServiceImpl {
 
 	private User stubUser = null;
 
diff --git a/src/test/java/de/thm/arsnova/domain/QuestionBasedLearningProgressTest.java b/src/test/java/de/thm/arsnova/services/score/QuestionBasedScoreCalculatorTest.java
similarity index 87%
rename from src/test/java/de/thm/arsnova/domain/QuestionBasedLearningProgressTest.java
rename to src/test/java/de/thm/arsnova/services/score/QuestionBasedScoreCalculatorTest.java
index 8adf79257..d46874fc7 100644
--- a/src/test/java/de/thm/arsnova/domain/QuestionBasedLearningProgressTest.java
+++ b/src/test/java/de/thm/arsnova/services/score/QuestionBasedScoreCalculatorTest.java
@@ -15,12 +15,15 @@
  * 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.domain;
+package de.thm.arsnova.services.score;
 
 import de.thm.arsnova.entities.TestUser;
 import de.thm.arsnova.entities.User;
-import de.thm.arsnova.entities.transport.LearningProgressValues;
+import de.thm.arsnova.entities.transport.ScoreStatistics;
 import de.thm.arsnova.persistance.SessionStatisticsRepository;
+import de.thm.arsnova.services.score.QuestionBasedScoreCalculator;
+import de.thm.arsnova.services.score.Score;
+import de.thm.arsnova.services.score.VariantScoreCalculator;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -28,10 +31,10 @@ import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-public class QuestionBasedLearningProgressTest {
+public class QuestionBasedScoreCalculatorTest {
 
-	private CourseScore courseScore;
-	private VariantLearningProgress lp;
+	private Score courseScore;
+	private VariantScoreCalculator lp;
 
 	private int id = 1;
 
@@ -49,10 +52,10 @@ public class QuestionBasedLearningProgressTest {
 
 	@Before
 	public void setUp() {
-		this.courseScore = new CourseScore();
+		this.courseScore = new Score();
 		SessionStatisticsRepository db = mock(SessionStatisticsRepository.class);
 		when(db.getLearningProgress(null)).thenReturn(courseScore);
-		this.lp = new QuestionBasedLearningProgress(db);
+		this.lp = new QuestionBasedScoreCalculator(db);
 	}
 
 	/**
@@ -66,11 +69,11 @@ public class QuestionBasedLearningProgressTest {
 		String questionId = this.addQuestion("lecture", questionMaxValue);
 		this.addAnswer(questionId, user, userScore);
 
-		LearningProgressValues expected = new LearningProgressValues();
+		ScoreStatistics expected = new ScoreStatistics();
 		expected.setCourseProgress(0);
 		expected.setMyProgress(0);
 		expected.setNumQuestions(0);
-		LearningProgressValues actual = lp.getMyProgress(null, user);
+		ScoreStatistics actual = lp.getMyProgress(null, user);
 
 		assertEquals(expected, actual);
 	}
@@ -83,11 +86,11 @@ public class QuestionBasedLearningProgressTest {
 		courseScore.addAnswer("question-without-correct-answers", 1, user.getUsername(), 0);
 		courseScore.addAnswer("question-with-correct-answers", 1, user.getUsername(), 50);
 
-		LearningProgressValues expected = new LearningProgressValues();
+		ScoreStatistics expected = new ScoreStatistics();
 		expected.setCourseProgress(100);
 		expected.setMyProgress(100);
 		expected.setNumQuestions(1);
-		LearningProgressValues actual = lp.getMyProgress(null, user);
+		ScoreStatistics actual = lp.getMyProgress(null, user);
 
 		assertEquals(expected, actual);
 	}
@@ -165,11 +168,11 @@ public class QuestionBasedLearningProgressTest {
 		this.addAnswer(q2, u2, 0);
 
 		lp.setQuestionVariant("lecture");
-		LearningProgressValues lectureProgress = lp.getCourseProgress(null);
-		LearningProgressValues myLectureProgress = lp.getMyProgress(null, u1);
+		ScoreStatistics lectureProgress = lp.getCourseProgress(null);
+		ScoreStatistics myLectureProgress = lp.getMyProgress(null, u1);
 		lp.setQuestionVariant("preparation");
-		LearningProgressValues prepProgress = lp.getCourseProgress(null);
-		LearningProgressValues myPrepProgress = lp.getMyProgress(null, u1);
+		ScoreStatistics prepProgress = lp.getCourseProgress(null);
+		ScoreStatistics myPrepProgress = lp.getMyProgress(null, u1);
 
 		assertEquals(100, lectureProgress.getCourseProgress());
 		assertEquals(100, myLectureProgress.getMyProgress());
@@ -190,8 +193,8 @@ public class QuestionBasedLearningProgressTest {
 		courseScore.addAnswer("q1", 1, u2.getUsername(), 100);
 		courseScore.addAnswer("q1", 2, u2.getUsername(), 25);
 
-		LearningProgressValues u1Progress = lp.getMyProgress(null, u1);
-		LearningProgressValues u2Progress = lp.getMyProgress(null, u2);
+		ScoreStatistics u1Progress = lp.getMyProgress(null, u1);
+		ScoreStatistics u2Progress = lp.getMyProgress(null, u2);
 
 		// only the answer for round 2 should be considered
 		assertEquals(50, u1Progress.getCourseProgress());
diff --git a/src/test/java/de/thm/arsnova/domain/PointBasedLearningProgressTest.java b/src/test/java/de/thm/arsnova/services/score/ScoreBasedScoreCalculatorTest.java
similarity index 84%
rename from src/test/java/de/thm/arsnova/domain/PointBasedLearningProgressTest.java
rename to src/test/java/de/thm/arsnova/services/score/ScoreBasedScoreCalculatorTest.java
index 6f42527dc..0909300b0 100644
--- a/src/test/java/de/thm/arsnova/domain/PointBasedLearningProgressTest.java
+++ b/src/test/java/de/thm/arsnova/services/score/ScoreBasedScoreCalculatorTest.java
@@ -15,12 +15,15 @@
  * 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.domain;
+package de.thm.arsnova.services.score;
 
 import de.thm.arsnova.entities.TestUser;
 import de.thm.arsnova.entities.User;
-import de.thm.arsnova.entities.transport.LearningProgressValues;
+import de.thm.arsnova.entities.transport.ScoreStatistics;
 import de.thm.arsnova.persistance.SessionStatisticsRepository;
+import de.thm.arsnova.services.score.Score;
+import de.thm.arsnova.services.score.ScoreBasedScoreCalculator;
+import de.thm.arsnova.services.score.VariantScoreCalculator;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -28,10 +31,10 @@ import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-public class PointBasedLearningProgressTest {
+public class ScoreBasedScoreCalculatorTest {
 
-	private CourseScore courseScore;
-	private VariantLearningProgress lp;
+	private Score courseScore;
+	private VariantScoreCalculator lp;
 
 	private int id = 1;
 
@@ -49,10 +52,10 @@ public class PointBasedLearningProgressTest {
 
 	@Before
 	public void setUp() {
-		this.courseScore = new CourseScore();
+		this.courseScore = new Score();
 		SessionStatisticsRepository db = mock(SessionStatisticsRepository.class);
 		when(db.getLearningProgress(null)).thenReturn(courseScore);
-		this.lp = new PointBasedLearningProgress(db);
+		this.lp = new ScoreBasedScoreCalculator(db);
 	}
 
 	@Test
@@ -68,11 +71,11 @@ public class PointBasedLearningProgressTest {
 		this.addAnswer(q2, u2, 0);
 
 		lp.setQuestionVariant("lecture");
-		LearningProgressValues lectureProgress = lp.getCourseProgress(null);
-		LearningProgressValues myLectureProgress = lp.getMyProgress(null, u1);
+		ScoreStatistics lectureProgress = lp.getCourseProgress(null);
+		ScoreStatistics myLectureProgress = lp.getMyProgress(null, u1);
 		lp.setQuestionVariant("preparation");
-		LearningProgressValues prepProgress = lp.getCourseProgress(null);
-		LearningProgressValues myPrepProgress = lp.getMyProgress(null, u1);
+		ScoreStatistics prepProgress = lp.getCourseProgress(null);
+		ScoreStatistics myPrepProgress = lp.getMyProgress(null, u1);
 
 		assertEquals(100, lectureProgress.getCourseProgress());
 		assertEquals(100, myLectureProgress.getMyProgress());
@@ -97,7 +100,7 @@ public class PointBasedLearningProgressTest {
 		this.addAnswer(q3, u2, 100);
 
 		lp.setQuestionVariant("lecture");
-		LearningProgressValues u1LectureProgress = lp.getMyProgress(null, u1);
+		ScoreStatistics u1LectureProgress = lp.getMyProgress(null, u1);
 		// 200 / 300 = 0,67
 		assertEquals(67, u1LectureProgress.getCourseProgress());
 		assertEquals(67, u1LectureProgress.getMyProgress());
@@ -116,8 +119,8 @@ public class PointBasedLearningProgressTest {
 		courseScore.addAnswer("q1", 1, u2.getUsername(), 75);
 		courseScore.addAnswer("q1", 2, u2.getUsername(), 25);
 
-		LearningProgressValues u1Progress = lp.getMyProgress(null, u1);
-		LearningProgressValues u2Progress = lp.getMyProgress(null, u2);
+		ScoreStatistics u1Progress = lp.getMyProgress(null, u1);
+		ScoreStatistics u2Progress = lp.getMyProgress(null, u2);
 
 		// only the answer for round 2 should be considered
 		assertEquals(50, u1Progress.getCourseProgress());
diff --git a/src/test/java/de/thm/arsnova/ImageUtilsTest.java b/src/test/java/de/thm/arsnova/util/ImageUtilsTest.java
similarity index 94%
rename from src/test/java/de/thm/arsnova/ImageUtilsTest.java
rename to src/test/java/de/thm/arsnova/util/ImageUtilsTest.java
index 5c27e1eb5..a7f9c67d0 100644
--- a/src/test/java/de/thm/arsnova/ImageUtilsTest.java
+++ b/src/test/java/de/thm/arsnova/util/ImageUtilsTest.java
@@ -15,12 +15,13 @@
  * 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;
+package de.thm.arsnova.util;
 
 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.util.ImageUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.test.context.ActiveProfiles;
@@ -28,8 +29,8 @@ import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.context.web.WebAppConfiguration;
 
-import static de.thm.arsnova.ImageUtils.IMAGE_PREFIX_MIDDLE;
-import static de.thm.arsnova.ImageUtils.IMAGE_PREFIX_START;
+import static de.thm.arsnova.util.ImageUtils.IMAGE_PREFIX_MIDDLE;
+import static de.thm.arsnova.util.ImageUtils.IMAGE_PREFIX_START;
 import static org.junit.Assert.*;
 
 @RunWith(SpringJUnit4ClassRunner.class)
-- 
GitLab