diff --git a/pom.xml b/pom.xml
index aa4a9b35e30ee8a46aa9f38665d607a728bd3f25..5687f3d07627427ee3d4861ca4a1031673fd2d50 100644
--- a/pom.xml
+++ b/pom.xml
@@ -143,6 +143,10 @@
 			<groupId>org.springframework</groupId>
 			<artifactId>spring-webmvc</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>org.springframework.data</groupId>
+			<artifactId>spring-data-commons</artifactId>
+		</dependency>
 		<dependency>
 			<groupId>org.springframework.integration</groupId>
 			<artifactId>spring-integration-mail</artifactId>
@@ -220,6 +224,11 @@
 			<artifactId>spring-test</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-test</artifactId>
+			<scope>test</scope>
+		</dependency>
 		<dependency>
 			<groupId>junit</groupId>
 			<artifactId>junit</artifactId>
diff --git a/src/main/java/de/thm/arsnova/aop/RangeAspect.java b/src/main/java/de/thm/arsnova/aop/RangeAspect.java
index 705b7529d44fd94f9082500236d10078210a671a..19734913d270bfad14d5bb6bdc85bae958d2eba5 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/aop/UserSessionAspect.java b/src/main/java/de/thm/arsnova/aop/UserSessionAspect.java
index e08c83d8806f8baa8af9f45ba0a7cff4f5107586..a5bda87fd1fcbe3668f10548b6123e9f2c8247c2 100644
--- a/src/main/java/de/thm/arsnova/aop/UserSessionAspect.java
+++ b/src/main/java/de/thm/arsnova/aop/UserSessionAspect.java
@@ -39,7 +39,7 @@ public class UserSessionAspect {
 	/** Sets current user and ARSnova session in session scoped UserSessionService
 	 */
 	@AfterReturning(
-			pointcut = "execution(public * de.thm.arsnova.services.SessionService.joinSession(..)) && args(keyword)",
+			pointcut = "execution(public * de.thm.arsnova.services.SessionService.join(..)) && args(keyword)",
 			returning = "session"
 			)
 	public void joinSessionAdvice(final JoinPoint jp, final String keyword, final Session session) {
diff --git a/src/main/java/de/thm/arsnova/cache/CacheBuster.java b/src/main/java/de/thm/arsnova/cache/CacheBuster.java
index c998bd4f30ddd4c69b9f6a0eaef4149e93e8fcb0..510f9e7404e435c2d8636d9456ef7f5991edcdad 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 0000000000000000000000000000000000000000..639c3474aeb241769814dc5cf7fee5ac038d9044
--- /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 = "answerlists", key = "#event.content.id")
+	@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 db365d064524b497694501240ab02a40e9d825d7..5bab8c63ce3364add9e12d43a375eef9602ff021 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 ab937cdd3354799182bf69959f94e03caeb8e376..0000000000000000000000000000000000000000
--- 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/cache/ScheduledCacheBuster.java b/src/main/java/de/thm/arsnova/cache/ScheduledCacheBuster.java
index 3a093d9784a8efb9ab3b55550a4a47f021a9b09d..7a611a10536f7e44389b7dc9c4fc7d0fa81e3416 100644
--- a/src/main/java/de/thm/arsnova/cache/ScheduledCacheBuster.java
+++ b/src/main/java/de/thm/arsnova/cache/ScheduledCacheBuster.java
@@ -38,31 +38,31 @@ public class ScheduledCacheBuster {
 	@Scheduled(initialDelay = 1000 * 25, fixedRate = 1000 * 60 * 60 * 6)
 	private void clearSessionCache() { }
 
-	@CacheEvict(value = "questions", allEntries = true)
+	@CacheEvict(value = "contents", allEntries = true)
 	@Scheduled(initialDelay = 1000 * 50, fixedRate = 1000 * 60 * 30)
 	private void clearQuestionCache() { }
 
-	@CacheEvict(value = "skillquestions", allEntries = true)
+	@CacheEvict(value = "contentlists", allEntries = true)
 	@Scheduled(initialDelay = 1000 * 75, fixedRate = 1000 * 60 * 30)
 	private void clearSkillQuestionCache() { }
 
-	@CacheEvict(value = "lecturequestions", allEntries = true)
+	@CacheEvict(value = "lecturecontentlists", allEntries = true)
 	@Scheduled(initialDelay = 1000 * 100, fixedRate = 1000 * 60 * 30)
 	private void clearLectureQuestionCache() { }
 
-	@CacheEvict(value = "preparationquestions", allEntries = true)
+	@CacheEvict(value = "preparationcontentlists", allEntries = true)
 	@Scheduled(initialDelay = 1000 * 125, fixedRate = 1000 * 60 * 30)
 	private void clearPreparationQuestionCache() { }
 
-	@CacheEvict(value = "flashcardquestions", allEntries = true)
+	@CacheEvict(value = "flashcardcontentlists", allEntries = true)
 	@Scheduled(initialDelay = 1000 * 150, fixedRate = 1000 * 60 * 30)
 	private void clearFlashcardQuestionCache() { }
 
-	@CacheEvict(value = "answers", allEntries = true)
+	@CacheEvict(value = "answerlists", allEntries = true)
 	@Scheduled(initialDelay = 1000 * 175, fixedRate = 1000 * 60 * 15)
 	private void clearAnswerCache() { }
 
-	@CacheEvict(value = "learningprogress", allEntries = true)
+	@CacheEvict(value = "score", allEntries = true)
 	@Scheduled(initialDelay = 1000 * 200, fixedRate = 1000 * 60 * 15)
 	private void clearLearningProgressCache() { }
 
diff --git a/src/main/java/de/thm/arsnova/config/AppConfig.java b/src/main/java/de/thm/arsnova/config/AppConfig.java
index ab92d140c2511cc0263d5e0ded6b945748949aea..73719ab35bd516a4d9b731928230e9283c2c64b8 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 fbf39de4f9e3131d076871a8169ff304b7f0f141..fe8fb597421ba6bf7dea5abccce3245122e879a8 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 e38c98630f2406217bcdc5f0c532f86db677b36c..428a6172b0e84c9b78b25e6ae1b9f79d019ea949 100644
--- a/src/main/java/de/thm/arsnova/controller/CommentController.java
+++ b/src/main/java/de/thm/arsnova/controller/CommentController.java
@@ -17,10 +17,10 @@
  */
 package de.thm.arsnova.controller;
 
+import de.thm.arsnova.entities.Comment;
 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.CommentService;
 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 CommentService commentService;
 
 	@ApiOperation(value = "Count all the comments in current session",
 			nickname = "getAudienceQuestionCount")
@@ -57,7 +57,7 @@ public class CommentController extends PaginationController {
 	@DeprecatedApi
 	@Deprecated
 	public int getInterposedCount(@ApiParam(value = "Session-Key from current session", required = true) @RequestParam final String sessionkey) {
-		return contentService.getInterposedCount(sessionkey);
+		return commentService.count(sessionkey);
 	}
 
 	@ApiOperation(value = "count all unread comments",
@@ -66,7 +66,7 @@ public class CommentController extends PaginationController {
 	@DeprecatedApi
 	@Deprecated
 	public CommentReadingCount getUnreadInterposedCount(@ApiParam(value = "Session-Key from current session", required = true) @RequestParam("sessionkey") final String sessionkey, String user) {
-		return contentService.getInterposedReadingCount(sessionkey, user);
+		return commentService.countRead(sessionkey, user);
 	}
 
 	@ApiOperation(value = "Retrieves all Comments for a Session",
@@ -74,14 +74,14 @@ public class CommentController extends PaginationController {
 	@RequestMapping(value = "/", method = RequestMethod.GET)
 	@Pagination
 	public List<Comment> getInterposedQuestions(@ApiParam(value = "Session-Key from current session", required = true) @RequestParam final String sessionkey) {
-		return Comment.fromList(contentService.getInterposedQuestions(sessionkey, offset, limit));
+		return commentService.getBySessionKey(sessionkey, offset, limit);
 	}
 
 	@ApiOperation(value = "Retrieves an Comment",
 			nickname = "getInterposedQuestion")
 	@RequestMapping(value = "/{questionId}", method = RequestMethod.GET)
 	public Comment getInterposedQuestion(@ApiParam(value = "ID of the Comment that needs to be deleted", required = true) @PathVariable final String questionId) {
-		return new Comment(contentService.readInterposedQuestion(questionId));
+		return commentService.getAndMarkRead(questionId);
 	}
 
 	@ApiOperation(value = "Creates a new Comment for a Session and returns the Comment's data",
@@ -95,7 +95,7 @@ public class CommentController extends PaginationController {
 			@ApiParam(value = "Session-Key from current session", required = true) @RequestParam final String sessionkey,
 			@ApiParam(value = "the body from the new comment", required = true) @RequestBody final de.thm.arsnova.entities.Comment comment
 			) {
-		if (contentService.saveQuestion(comment)) {
+		if (commentService.save(comment)) {
 			return;
 		}
 
@@ -106,6 +106,6 @@ public class CommentController extends PaginationController {
 			nickname = "deleteInterposedQuestion")
 	@RequestMapping(value = "/{questionId}", method = RequestMethod.DELETE)
 	public void deleteInterposedQuestion(@ApiParam(value = "ID of the comment that needs to be deleted", required = true) @PathVariable final String questionId) {
-		contentService.deleteInterposedQuestion(questionId);
+		commentService.delete(questionId);
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/controller/ContentController.java b/src/main/java/de/thm/arsnova/controller/ContentController.java
index 9695d365164c78e6bff74e820cd1d12da6c4dd3d..a0b323ad3723f946a95f80c2fb115c469c96fc70 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")
@@ -63,7 +63,7 @@ public class ContentController extends PaginationController {
 	})
 	@RequestMapping(value = "/{questionId}", method = RequestMethod.GET)
 	public Content getQuestion(@PathVariable final String questionId) {
-		final Content content = contentService.getQuestion(questionId);
+		final Content content = contentService.get(questionId);
 		if (content != null) {
 			return content;
 		}
@@ -79,7 +79,7 @@ public class ContentController extends PaginationController {
 	@RequestMapping(value = "/", method = RequestMethod.POST)
 	@ResponseStatus(HttpStatus.CREATED)
 	public Content postQuestion(@RequestBody final Content content) {
-		if (contentService.saveQuestion(content) != null) {
+		if (contentService.save(content) != null) {
 			return content;
 		}
 		throw new BadRequestException();
@@ -93,7 +93,7 @@ public class ContentController extends PaginationController {
 	@ResponseStatus(HttpStatus.CREATED)
 	public List<Content> bulkPostQuestions(@RequestBody final List<Content> contents) {
 		for (final Content content : contents) {
-			if (contentService.saveQuestion(content) == null) {
+			if (contentService.save(content) == null) {
 				throw new BadRequestException();
 			}
 		}
@@ -291,7 +291,7 @@ public class ContentController extends PaginationController {
 		} else if (preparationQuestionsOnly) {
 			contents = contentService.getPreparationQuestions(sessionkey);
 		} else {
-			contents = contentService.getSkillQuestions(sessionkey);
+			contents = contentService.getBySessionKey(sessionkey);
 		}
 		if (contents == null || contents.isEmpty()) {
 			response.setStatus(HttpStatus.NO_CONTENT.value());
@@ -315,12 +315,12 @@ public class ContentController extends PaginationController {
 			) {
 		if (lectureQuestionsOnly) {
 			contentService.deleteLectureQuestions(sessionkey);
-		} else if (flashcardsOnly) {
-			contentService.deleteFlashcards(sessionkey);
 		} else if (preparationQuestionsOnly) {
 			contentService.deletePreparationQuestions(sessionkey);
+		} else if (flashcardsOnly) {
+			contentService.deleteFlashcards(sessionkey);
 		} else {
-			contentService.deleteAllQuestions(sessionkey);
+			contentService.deleteAllContent(sessionkey);
 		}
 	}
 
@@ -336,13 +336,13 @@ public class ContentController extends PaginationController {
 			@RequestParam(value = "preparationquestionsonly", defaultValue = "false") final boolean preparationQuestionsOnly
 			) {
 		if (lectureQuestionsOnly) {
-			return contentService.getLectureQuestionCount(sessionkey);
-		} else if (flashcardsOnly) {
-			return contentService.getFlashcardCount(sessionkey);
+			return contentService.countLectureQuestions(sessionkey);
 		} else if (preparationQuestionsOnly) {
-			return contentService.getPreparationQuestionCount(sessionkey);
+			return contentService.countPreparationQuestions(sessionkey);
+		} else if (flashcardsOnly) {
+			return contentService.countFlashcards(sessionkey);
 		} else {
-			return contentService.getSkillQuestionCount(sessionkey);
+			return contentService.countBySessionKey(sessionkey);
 		}
 	}
 
@@ -352,7 +352,7 @@ public class ContentController extends PaginationController {
 	public void deleteAnswersAndQuestion(
 			@PathVariable final String questionId
 			) {
-		contentService.deleteQuestion(questionId);
+		contentService.delete(questionId);
 	}
 
 	@ApiOperation(value = "Get unanswered skill question ID by provided session ID",
@@ -458,7 +458,7 @@ public class ContentController extends PaginationController {
 	@RequestMapping(value = "/{questionId}/answer/", method = RequestMethod.POST)
 	public Answer saveAnswer(
 			@PathVariable final String questionId,
-			@RequestBody final de.thm.arsnova.entities.transport.Answer answer,
+			@RequestBody final Answer answer,
 			final HttpServletResponse response
 			) {
 		return contentService.saveAnswer(questionId, answer);
@@ -544,7 +544,7 @@ public class ContentController extends PaginationController {
 	@Deprecated
 	@RequestMapping(value = "/{questionId}/answercount", method = RequestMethod.GET)
 	public int getAnswerCount(@PathVariable final String questionId) {
-		return contentService.getAnswerCount(questionId);
+		return contentService.countAnswersByQuestionIdAndRound(questionId);
 	}
 
 	@ApiOperation(value = "Get the amount of answers for a question, identified by the question ID",
@@ -552,8 +552,8 @@ public class ContentController extends PaginationController {
 	@RequestMapping(value = "/{questionId}/allroundanswercount", method = RequestMethod.GET)
 	public List<Integer> getAllAnswerCount(@PathVariable final String questionId) {
 		return Arrays.asList(
-			contentService.getAnswerCount(questionId, 1),
-			contentService.getAnswerCount(questionId, 2)
+			contentService.countAnswersByQuestionIdAndRound(questionId, 1),
+			contentService.countAnswersByQuestionIdAndRound(questionId, 2)
 		);
 	}
 
@@ -561,7 +561,7 @@ public class ContentController extends PaginationController {
 			nickname = "getTotalAnswerCountByQuestion")
 	@RequestMapping(value = "/{questionId}/totalanswercount", method = RequestMethod.GET)
 	public int getTotalAnswerCountByQuestion(@PathVariable final String questionId) {
-		return contentService.getTotalAnswerCountByQuestion(questionId);
+		return contentService.countTotalAnswersByQuestionId(questionId);
 	}
 
 	@ApiOperation(value = "Get the amount of answers and abstention answers by a question, identified by the question ID",
@@ -569,8 +569,8 @@ public class ContentController extends PaginationController {
 	@RequestMapping(value = "/{questionId}/answerandabstentioncount", method = RequestMethod.GET)
 	public List<Integer> getAnswerAndAbstentionCount(@PathVariable final String questionId) {
 		return Arrays.asList(
-			contentService.getAnswerCount(questionId),
-			contentService.getAbstentionAnswerCount(questionId)
+			contentService.countAnswersByQuestionIdAndRound(questionId),
+			contentService.countTotalAbstentionsByQuestionId(questionId)
 		);
 	}
 
@@ -579,7 +579,7 @@ public class ContentController extends PaginationController {
 	@RequestMapping(value = "/{questionId}/freetextanswer/", method = RequestMethod.GET)
 	@Pagination
 	public List<Answer> getFreetextAnswers(@PathVariable final String questionId) {
-		return contentService.getFreetextAnswers(questionId, offset, limit);
+		return contentService.getFreetextAnswersByQuestionId(questionId, offset, limit);
 	}
 
 	@ApiOperation(value = "Get my answers of an session, identified by the sessionkey",
@@ -588,7 +588,7 @@ public class ContentController extends PaginationController {
 	@Deprecated
 	@RequestMapping(value = "/myanswers", method = RequestMethod.GET)
 	public List<Answer> getMyAnswers(@RequestParam final String sessionkey) {
-		return contentService.getMyAnswers(sessionkey);
+		return contentService.getMyAnswersBySessionKey(sessionkey);
 	}
 
 	@ApiOperation(value = "Get the total amount of answers of an session, identified by the sessionkey",
@@ -606,7 +606,7 @@ public class ContentController extends PaginationController {
 		} else if (preparationQuestionsOnly) {
 			return contentService.countPreparationQuestionAnswers(sessionkey);
 		} else {
-			return contentService.getTotalAnswerCount(sessionkey);
+			return contentService.countTotalAnswersBySessionKey(sessionkey);
 		}
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/controller/CourseController.java b/src/main/java/de/thm/arsnova/controller/CourseController.java
index e8919bc4328b9b8f96daba68863b5770b5eac614..74e6c51485c1599e32cefe6cab31ee4ff8044bce 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 2f3e3b16c7b509ee4336f6dda7805ef240245436..0d6c54d3d52bd82bb0c9ab18902d113498dcfefe 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,28 +37,28 @@ 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
 	@RequestMapping(value = "/session/{sessionkey}/feedback", method = RequestMethod.GET)
 	public Feedback getFeedback(@PathVariable final String sessionkey) {
-		return feedbackService.getFeedback(sessionkey);
+		return feedbackService.getBySessionKey(sessionkey);
 	}
 
 	@DeprecatedApi
 	@Deprecated
 	@RequestMapping(value = "/session/{sessionkey}/myfeedback", method = RequestMethod.GET)
 	public Integer getMyFeedback(@PathVariable final String sessionkey) {
-		Integer value = feedbackService.getMyFeedback(sessionkey, userService.getCurrentUser());
+		Integer value = feedbackService.getBySessionKeyAndUser(sessionkey, userService.getCurrentUser());
 		if (value != null && value >= Feedback.MIN_FEEDBACK_TYPE && value <= Feedback.MAX_FEEDBACK_TYPE) {
 			return value;
 		}
@@ -68,21 +69,21 @@ public class FeedbackController extends AbstractController {
 	@Deprecated
 	@RequestMapping(value = "/session/{sessionkey}/feedbackcount", method = RequestMethod.GET)
 	public int getFeedbackCount(@PathVariable final String sessionkey) {
-		return feedbackService.getFeedbackCount(sessionkey);
+		return feedbackService.countFeedbackBySessionKey(sessionkey);
 	}
 
 	@DeprecatedApi
 	@Deprecated
 	@RequestMapping(value = "/session/{sessionkey}/roundedaveragefeedback", method = RequestMethod.GET)
 	public long getAverageFeedbackRounded(@PathVariable final String sessionkey) {
-		return feedbackService.getAverageFeedbackRounded(sessionkey);
+		return feedbackService.calculateRoundedAverageFeedback(sessionkey);
 	}
 
 	@DeprecatedApi
 	@Deprecated
 	@RequestMapping(value = "/session/{sessionkey}/averagefeedback", method = RequestMethod.GET)
 	public double getAverageFeedback(@PathVariable final String sessionkey) {
-		return feedbackService.getAverageFeedback(sessionkey);
+		return feedbackService.calculateAverageFeedback(sessionkey);
 	}
 
 	@DeprecatedApi
@@ -94,8 +95,8 @@ public class FeedbackController extends AbstractController {
 			@RequestBody final int value
 			) {
 		User user = userService.getCurrentUser();
-		feedbackService.saveFeedback(sessionkey, value, user);
-		Feedback feedback = feedbackService.getFeedback(sessionkey);
+		feedbackService.save(sessionkey, value, user);
+		Feedback feedback = feedbackService.getBySessionKey(sessionkey);
 
 		return feedback;
 	}
diff --git a/src/main/java/de/thm/arsnova/controller/LegacyController.java b/src/main/java/de/thm/arsnova/controller/LegacyController.java
index a1d3a9de2a3ff25df4bba88b735f4e46853a8a94..384c02da1778e43b6bbf1ad5681de7bdef13f97c 100644
--- a/src/main/java/de/thm/arsnova/controller/LegacyController.java
+++ b/src/main/java/de/thm/arsnova/controller/LegacyController.java
@@ -17,7 +17,8 @@
  */
 package de.thm.arsnova.controller;
 
-import de.thm.arsnova.services.IContentService;
+import de.thm.arsnova.services.CommentService;
+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 +34,10 @@ import org.springframework.web.bind.annotation.ResponseBody;
 public class LegacyController extends AbstractController {
 
 	@Autowired
-	private IContentService contentService;
+	private ContentService contentService;
+
+	@Autowired
+	private CommentService commentService;
 
 	/* specific routes */
 
@@ -95,7 +99,7 @@ public class LegacyController extends AbstractController {
 	@RequestMapping(value = "/session/{sessionKey}/interposed", method = RequestMethod.DELETE)
 	@ResponseBody
 	public void deleteAllInterposedQuestions(@PathVariable final String sessionKey) {
-		contentService.deleteAllInterposedQuestions(sessionKey);
+		commentService.deleteBySessionKey(sessionKey);
 	}
 
 	@DeprecatedApi
diff --git a/src/main/java/de/thm/arsnova/controller/LoginController.java b/src/main/java/de/thm/arsnova/controller/LoginController.java
index 119e7594e516d997bf2f1f250717d6605337fac3..820bba4357a8166ff599d1c0ee574314aaba4679 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 9f1e5155476635107bb6a1cd114feeb03e8bac3a..e9a19f58eb6950e591218e0a318219f74d68e060 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)
@@ -63,18 +63,18 @@ public class MotdController extends AbstractController {
 		@ApiParam(value = "sessionkey", required = false) @RequestParam(value = "sessionkey", defaultValue = "null") final String sessionkey
 	) {
 		List<Motd> motds;
-		Date client = new Date(System.currentTimeMillis());
+		Date date = new Date(System.currentTimeMillis());
 		if (!clientdate.isEmpty()) {
-			client.setTime(Long.parseLong(clientdate));
+			date.setTime(Long.parseLong(clientdate));
 		}
 		if (adminview) {
-			if ("null".equals(sessionkey)) {
-				motds = motdService.getAdminMotds();
-			} else {
-				motds = motdService.getAllSessionMotds(sessionkey);
-			}
+			motds = "session".equals(audience) ?
+					motdService.getAllSessionMotds(sessionkey) :
+					motdService.getAdminMotds();
 		} else {
-			motds = motdService.getCurrentMotds(client, audience, sessionkey);
+			motds = "session".equals(audience) ?
+					motdService.getCurrentSessionMotds(date, sessionkey) :
+					motdService.getCurrentMotds(date, audience);
 		}
 		return motds;
 	}
@@ -93,9 +93,9 @@ public class MotdController extends AbstractController {
 		if (motd != null) {
 			Motd newMotd;
 			if ("session".equals(motd.getAudience()) && motd.getSessionkey() != null) {
-				newMotd = motdService.saveSessionMotd(motd.getSessionkey(), motd);
+				newMotd = motdService.save(motd.getSessionkey(), motd);
 			} else {
-				newMotd = motdService.saveMotd(motd);
+				newMotd = motdService.save(motd);
 			}
 			if (newMotd == null) {
 				response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
@@ -115,20 +115,20 @@ public class MotdController extends AbstractController {
 			@ApiParam(value = "current motd", required = true) @RequestBody final Motd motd
 			) {
 		if ("session".equals(motd.getAudience()) && motd.getSessionkey() != null) {
-			return motdService.updateSessionMotd(motd.getSessionkey(), motd);
+			return motdService.update(motd.getSessionkey(), motd);
 		} else {
-			return motdService.updateMotd(motd);
+			return motdService.update(motd);
 		}
 	}
 
 	@ApiOperation(value = "deletes a message of the day", nickname = "deleteMotd")
 	@RequestMapping(value = "/{motdkey}", method = RequestMethod.DELETE)
 	public void deleteMotd(@ApiParam(value = "Motd-key from the message that shall be deleted", required = true) @PathVariable final String motdkey) {
-		Motd motd = motdService.getMotd(motdkey);
+		Motd motd = motdService.getByKey(motdkey);
 		if ("session".equals(motd.getAudience())) {
-			motdService.deleteSessionMotd(motd.getSessionkey(), motd);
+			motdService.deleteBySessionKey(motd.getSessionkey(), motd);
 		} else {
-			motdService.deleteMotd(motd);
+			motdService.delete(motd);
 		}
 	}
 
@@ -136,7 +136,7 @@ public class MotdController extends AbstractController {
 	@RequestMapping(value = "/userlist", method = RequestMethod.GET)
 	public MotdList getUserMotdList(
 			@ApiParam(value = "users name", required = true) @RequestParam(value = "username", defaultValue = "null", required = true) final String username) {
-		return motdService.getMotdListForUser(username);
+		return motdService.getMotdListByUsername(username);
 	}
 
 	@ApiOperation(value = "create a list of the motdkeys the current user has confirmed to be read")
@@ -144,7 +144,7 @@ public class MotdController extends AbstractController {
 	public MotdList postUserMotdList(
 			@ApiParam(value = "current motdlist", required = true) @RequestBody final MotdList userMotdList
 			) {
-		return motdService.saveUserMotdList(userMotdList);
+		return motdService.saveMotdList(userMotdList);
 	}
 
 	@ApiOperation(value = "update a list of the motdkeys the current user has confirmed to be read")
@@ -152,6 +152,6 @@ public class MotdController extends AbstractController {
 	public MotdList updateUserMotdList(
 			@ApiParam(value = "current motdlist", required = true) @RequestBody final MotdList userMotdList
 			) {
-		return motdService.updateUserMotdList(userMotdList);
+		return motdService.updateMotdList(userMotdList);
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/controller/SessionController.java b/src/main/java/de/thm/arsnova/controller/SessionController.java
index 9b0dcd48a023fa8903103653c755eae633174dfc..325818ccc1d578cdef6f8ca0e4a97f5cdbe7e5db 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")
@@ -76,9 +76,9 @@ public class SessionController extends PaginationController {
 			@ApiParam(value = "Adminflag", required = false) @RequestParam(value = "admin", defaultValue = "false")	final boolean admin
 			) {
 		if (admin) {
-			return sessionService.getSessionForAdmin(sessionkey);
+			return sessionService.getForAdmin(sessionkey);
 		} else {
-			return sessionService.getSession(sessionkey);
+			return sessionService.getByKey(sessionkey);
 		}
 	}
 
@@ -86,7 +86,8 @@ public class SessionController extends PaginationController {
 			nickname = "deleteSession")
 	@RequestMapping(value = "/{sessionkey}", method = RequestMethod.DELETE)
 	public void deleteSession(@ApiParam(value = "Session-Key from current session", required = true) @PathVariable final String sessionkey) {
-		sessionService.deleteSession(sessionkey);
+		Session session = sessionService.getByKey(sessionkey);
+		sessionService.deleteCascading(session);
 	}
 
 	@ApiOperation(value = "count active users",
@@ -112,7 +113,7 @@ public class SessionController extends PaginationController {
 			final Course course = new Course();
 			course.setId(session.getCourseId());
 			courses.add(course);
-			final int sessionCount = sessionService.countSessions(courses);
+			final int sessionCount = sessionService.countSessionsByCourses(courses);
 			if (sessionCount > 0) {
 				final String appendix = " (" + (sessionCount + 1) + ")";
 				session.setName(session.getName() + appendix);
@@ -120,7 +121,7 @@ public class SessionController extends PaginationController {
 			}
 		}
 
-		final Session newSession = sessionService.saveSession(session);
+		final Session newSession = sessionService.save(session);
 
 		if (newSession == null) {
 			response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
@@ -137,7 +138,7 @@ public class SessionController extends PaginationController {
 			@ApiParam(value = "session-key from current session", required = true) @PathVariable final String sessionkey,
 			@ApiParam(value = "current session", required = true) @RequestBody final Session session
 			) {
-		return sessionService.updateSession(sessionkey, session);
+		return sessionService.update(sessionkey, session);
 	}
 
 	@ApiOperation(value = "change the session creator (owner)", nickname = "changeSessionCreator")
@@ -146,7 +147,7 @@ public class SessionController extends PaginationController {
 			@ApiParam(value = "session-key from current session", required = true) @PathVariable final String sessionkey,
 			@ApiParam(value = "new session creator", required = true) @RequestBody final String newCreator
 			) {
-		return sessionService.changeSessionCreator(sessionkey, newCreator);
+		return sessionService.updateCreator(sessionkey, newCreator);
 	}
 
 	@ApiOperation(value = "Retrieves a list of Sessions",
@@ -345,28 +346,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",
@@ -376,7 +377,7 @@ public class SessionController extends PaginationController {
 			@ApiParam(value = "session-key from current session", required = true) @PathVariable final String sessionkey,
 			final HttpServletResponse response
 			) {
-		return sessionService.getSessionFeatures(sessionkey);
+		return sessionService.getFeatures(sessionkey);
 	}
 
 	@RequestMapping(value = "/{sessionkey}/features", method = RequestMethod.PUT)
@@ -387,7 +388,7 @@ public class SessionController extends PaginationController {
 			@ApiParam(value = "session feature", required = true) @RequestBody final SessionFeature features,
 			final HttpServletResponse response
 			) {
-		return sessionService.changeSessionFeatures(sessionkey, features);
+		return sessionService.updateFeatures(sessionkey, features);
 	}
 
 	@RequestMapping(value = "/{sessionkey}/lockfeedbackinput", method = RequestMethod.POST)
diff --git a/src/main/java/de/thm/arsnova/controller/SocketController.java b/src/main/java/de/thm/arsnova/controller/SocketController.java
index 685d1ff7dccd6432aff1348e63c924ee76ef5576..0fa66e39a45f072029716738f7a39ecd64ff461f 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 a82e575357d426d07aa41c7509d207a4db02d212..baedcf0aed2f0e6e7fc36d3902f94156ff1d6d77 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 df9237599b782733210c06f11fb39edb4c9ec10c..c0fa9eade8ce87531aaaa28e67eefb0e7781df92 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;
@@ -50,7 +50,7 @@ public class UserController extends AbstractController {
 	public void register(@RequestParam final String username,
 			@RequestParam final String password,
 			final HttpServletRequest request, final HttpServletResponse response) {
-		if (null != userService.createDbUser(username, password)) {
+		if (null != userService.create(username, password)) {
 			return;
 		}
 
@@ -64,10 +64,10 @@ public class UserController extends AbstractController {
 			@PathVariable final String username,
 			@RequestParam final String key, final HttpServletRequest request,
 			final HttpServletResponse response) {
-		DbUser dbUser = userService.getDbUser(username);
+		DbUser dbUser = userService.getByUsername(username);
 		if (null != dbUser && key.equals(dbUser.getActivationKey())) {
 			dbUser.setActivationKey(null);
-			userService.updateDbUser(dbUser);
+			userService.update(dbUser);
 
 			return;
 		}
@@ -80,7 +80,7 @@ public class UserController extends AbstractController {
 			@PathVariable final String username,
 			final HttpServletRequest request,
 			final HttpServletResponse response) {
-		if (null == userService.deleteDbUser(username)) {
+		if (null == userService.deleteByUsername(username)) {
 			response.setStatus(HttpServletResponse.SC_NOT_FOUND);
 		}
 	}
@@ -92,7 +92,7 @@ public class UserController extends AbstractController {
 			@RequestParam(required = false) final String password,
 			final HttpServletRequest request,
 			final HttpServletResponse response) {
-		DbUser dbUser = userService.getDbUser(username);
+		DbUser dbUser = userService.getByUsername(username);
 		if (null == dbUser) {
 			response.setStatus(HttpServletResponse.SC_NOT_FOUND);
 
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 4995c742eaa8e7cb5f67adfe4ece28bf18ce9fcc..0000000000000000000000000000000000000000
--- 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/DbUser.java b/src/main/java/de/thm/arsnova/entities/DbUser.java
index 0bdc45121c35f9975ae0cb33fba49d53cdcf0fd7..ff2e99f15cedbbf2c28a769cdbcee97c106b3b4a 100644
--- a/src/main/java/de/thm/arsnova/entities/DbUser.java
+++ b/src/main/java/de/thm/arsnova/entities/DbUser.java
@@ -45,12 +45,12 @@ public class DbUser implements Entity {
 	}
 
 	@JsonView(View.Persistence.class)
-	public String getRev() {
+	public String getRevision() {
 		return rev;
 	}
 
 	@JsonView(View.Persistence.class)
-	public void setRev(String rev) {
+	public void setRevision(String rev) {
 		this.rev = rev;
 	}
 
diff --git a/src/main/java/de/thm/arsnova/entities/Entity.java b/src/main/java/de/thm/arsnova/entities/Entity.java
index 4de04ab4b7d1923cbfce3a50649e1af520db468f..ce0f2ee2e2541783a5cea38e3dc0a9755911f44e 100644
--- a/src/main/java/de/thm/arsnova/entities/Entity.java
+++ b/src/main/java/de/thm/arsnova/entities/Entity.java
@@ -24,6 +24,9 @@ public interface Entity {
 	String getId();
 	void setId(String id);
 
+	String getRevision();
+	void setRevision(String id);
+
 	@JsonView(View.Persistence.class)
 	default Class<? extends Entity> getType() {
 		return getClass();
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 342d2c29ae9fda8142356792dadd863211d1b9ee..1715f484e7dd2d2e868dd01199c46071d994e8b0 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 65bf6c6263075a06a9f40260e202ea676a88dea1..3c48460c1c3a3166fee982dc8378b9d71fe384af 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;
@@ -55,43 +55,6 @@ public class Session implements Entity {
 	private boolean feedbackLock;
 	private boolean flipFlashcards;
 
-	/**
-	 * Returns a copy of the given session without any information that identifies a person.
-	 * @param original The session to create a anonymized copy of
-	 * @return The anonymized copy of the session
-	 */
-	public static Session anonymizedCopy(final Session original) {
-		final Session copy = new Session();
-		copy.name = original.name;
-		copy.shortName = original.shortName;
-		copy.keyword = original.keyword;
-		copy.creator = ""; // anonymous
-		copy.active = original.active;
-		copy.lastOwnerActivity = original.lastOwnerActivity;
-		copy.courseType = original.courseType;
-		copy.courseId = original.courseId;
-		copy.creationTime = original.creationTime;
-		copy.learningProgressOptions = new LearningProgressOptions(original.learningProgressOptions);
-		copy.features = new SessionFeature(original.features);
-		// public pool
-		copy.ppAuthorName = original.ppAuthorName;
-		copy.ppAuthorMail = original.ppAuthorMail;
-		copy.ppUniversity = original.ppUniversity;
-		copy.ppLogo = original.ppLogo;
-		copy.ppSubject = original.ppSubject;
-		copy.ppLicense = original.ppLicense;
-		copy.ppDescription = original.ppDescription;
-		copy.ppFaculty = original.ppFaculty;
-		copy.ppLevel = original.ppLevel;
-		copy.sessionType = original.sessionType;
-		copy.feedbackLock = original.feedbackLock;
-		copy.flipFlashcards = original.flipFlashcards;
-
-		copy.id = original.id;
-		copy.rev = original.rev;
-		return copy;
-	}
-
 	@JsonView({View.Persistence.class, View.Public.class})
 	public String getId() {
 		return id;
@@ -220,14 +183,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 e04390add673beb3c89c54e8a85bde3cd7d1dc2e..618727cd6098d5130e7810aecc227c2071739d9f 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
deleted file mode 100644
index de074d3e1cc30029d1ee9a60c315aa69319931fc..0000000000000000000000000000000000000000
--- a/src/main/java/de/thm/arsnova/entities/transport/Answer.java
+++ /dev/null
@@ -1,165 +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.entities.transport;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonView;
-import de.thm.arsnova.entities.Content;
-import de.thm.arsnova.entities.User;
-import de.thm.arsnova.entities.serialization.View;
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-
-import java.io.Serializable;
-import java.util.Date;
-
-/**
- * A user's answer to a question.
- */
-@JsonInclude(JsonInclude.Include.NON_DEFAULT)
-@ApiModel(value = "session/answer", description = "the Answer API")
-public class Answer implements Serializable {
-
-	private String answerSubject;
-
-	private String answerSubjectRaw;
-
-	private String answerText;
-	private String answerTextRaw;
-	private double freeTextScore;
-	private boolean successfulFreeTextAnswer;
-
-	private String answerImage;
-
-	private boolean abstention;
-
-	public Answer() {
-
-	}
-
-	public Answer(de.thm.arsnova.entities.Answer a) {
-		answerSubject = a.getAnswerSubject();
-		answerText = a.getAnswerText();
-		answerImage = a.getAnswerImage();
-		abstention = a.isAbstention();
-		successfulFreeTextAnswer = a.isSuccessfulFreeTextAnswer();
-	}
-
-	@ApiModelProperty(required = true, value = "used to display text answer")
-	@JsonView(View.Public.class)
-	public String getAnswerText() {
-		return answerText;
-	}
-
-	public void setAnswerText(String answerText) {
-		this.answerText = answerText;
-	}
-
-	@ApiModelProperty(required = true, value = "used to display subject answer")
-	@JsonView(View.Public.class)
-	public String getAnswerSubject() {
-		return answerSubject;
-	}
-
-	public void setAnswerSubject(String answerSubject) {
-		this.answerSubject = answerSubject;
-	}
-
-	@JsonView(View.Public.class)
-	public final String getAnswerTextRaw() {
-		return this.answerTextRaw;
-	}
-
-	public final void setAnswerTextRaw(final String answerTextRaw) {
-		this.answerTextRaw = answerTextRaw;
-	}
-
-	@JsonView(View.Public.class)
-	public final String getAnswerSubjectRaw() {
-		return this.answerSubjectRaw;
-	}
-
-	public final void setAnswerSubjectRaw(final String answerSubjectRaw) {
-		this.answerSubjectRaw = answerSubjectRaw;
-	}
-
-	@JsonView(View.Public.class)
-	public final double getFreeTextScore() {
-		return this.freeTextScore;
-	}
-
-	public final void setFreeTextScore(final double freeTextScore) {
-		this.freeTextScore = freeTextScore;
-	}
-
-	@ApiModelProperty(required = true, value = "successfulFreeTextAnswer")
-	public final boolean isSuccessfulFreeTextAnswer() {
-		return this.successfulFreeTextAnswer;
-	}
-
-	public final void setSuccessfulFreeTextAnswer(final boolean successfulFreeTextAnswer) {
-		this.successfulFreeTextAnswer = successfulFreeTextAnswer;
-	}
-
-	@ApiModelProperty(required = true, value = "abstention")
-	@JsonView(View.Public.class)
-	public boolean isAbstention() {
-		return abstention;
-	}
-
-	public void setAbstention(boolean abstention) {
-		this.abstention = abstention;
-	}
-
-	public de.thm.arsnova.entities.Answer generateAnswerEntity(final User user, final Content content) {
-		// rewrite all fields so that no manipulated data gets written
-		// only answerText, answerSubject, and abstention are allowed
-		de.thm.arsnova.entities.Answer theAnswer = new de.thm.arsnova.entities.Answer();
-		theAnswer.setAnswerSubject(this.getAnswerSubject());
-		theAnswer.setAnswerText(this.getAnswerText());
-		theAnswer.setAnswerTextRaw(this.getAnswerTextRaw());
-		theAnswer.setSessionId(content.getSessionId());
-		theAnswer.setUser(user.getUsername());
-		theAnswer.setQuestionId(content.getId());
-		theAnswer.setTimestamp(new Date().getTime());
-		theAnswer.setQuestionVariant(content.getQuestionVariant());
-		theAnswer.setAbstention(this.isAbstention());
-		// calculate learning progress value after all properties are set
-		theAnswer.setQuestionValue(content.calculateValue(theAnswer));
-		theAnswer.setAnswerImage(this.getAnswerImage());
-		theAnswer.setSuccessfulFreeTextAnswer(this.isSuccessfulFreeTextAnswer());
-
-		if ("freetext".equals(content.getQuestionType())) {
-			theAnswer.setPiRound(0);
-		} else {
-			theAnswer.setPiRound(content.getPiRound());
-		}
-
-		return theAnswer;
-	}
-
-	@ApiModelProperty(required = true, value = "used to display image answer")
-	@JsonView(View.Public.class)
-	public String getAnswerImage() {
-		return answerImage;
-	}
-
-	public void setAnswerImage(String answerImage) {
-		this.answerImage = answerImage;
-	}
-}
diff --git a/src/main/java/de/thm/arsnova/entities/transport/Comment.java b/src/main/java/de/thm/arsnova/entities/transport/Comment.java
deleted file mode 100644
index e3006b9913bdb7aa80aa5faa074a303a61b64411..0000000000000000000000000000000000000000
--- a/src/main/java/de/thm/arsnova/entities/transport/Comment.java
+++ /dev/null
@@ -1,107 +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.entities.transport;
-
-import com.fasterxml.jackson.annotation.JsonView;
-import de.thm.arsnova.entities.serialization.View;
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A question a student is asking. Also known as comment, feedback or audience question.
- */
-@ApiModel(value = "audiencequestion/{questionId}", description = "the comment API")
-public class Comment {
-
-	private String id;
-	private String subject;
-	private String text;
-	private long timestamp;
-	private boolean read;
-
-	public static List<Comment> fromList(List<de.thm.arsnova.entities.Comment> comments) {
-		ArrayList<Comment> transportComments = new ArrayList<>();
-		for (de.thm.arsnova.entities.Comment comment : comments) {
-			transportComments.add(new Comment(comment));
-		}
-		return transportComments;
-	}
-
-	public Comment(de.thm.arsnova.entities.Comment comment) {
-		this.id = comment.getId();
-		this.subject = comment.getSubject();
-		this.text = comment.getText();
-		this.timestamp = comment.getTimestamp();
-		this.read = comment.isRead();
-	}
-
-	public Comment() { }
-
-	@ApiModelProperty(required = true, value = "used to display Id")
-	@JsonView(View.Public.class)
-	public String getId() {
-		return id;
-	}
-
-	public void setId(String id) {
-		this.id = id;
-	}
-
-	@ApiModelProperty(required = true, value = "used to display Subject")
-	@JsonView(View.Public.class)
-	public String getSubject() {
-		return subject;
-	}
-
-	public void setSubject(String subject) {
-		this.subject = subject;
-	}
-
-	@ApiModelProperty(required = true, value = "used to display Text")
-	@JsonView(View.Public.class)
-	public String getText() {
-		return text;
-	}
-
-	public void setText(String text) {
-		this.text = text;
-	}
-
-	@ApiModelProperty(required = true, value = "used to display Timetamp")
-	@JsonView(View.Public.class)
-	public long getTimestamp() {
-		return timestamp;
-	}
-
-	public void setTimestamp(long timestamp) {
-		this.timestamp = timestamp;
-	}
-
-	@ApiModelProperty(required = true, value = "is read")
-	@JsonView(View.Public.class)
-	public boolean isRead() {
-		return read;
-	}
-
-	public void setRead(boolean read) {
-		this.read = read;
-	}
-}
diff --git a/src/main/java/de/thm/arsnova/entities/transport/ImportExportSession.java b/src/main/java/de/thm/arsnova/entities/transport/ImportExportSession.java
index 1ef60232f0e80f12197be2720cdb27886dfa4191..ee56a2d13e666948e55bbc7f48c7a324316f9b2d 100644
--- a/src/main/java/de/thm/arsnova/entities/transport/ImportExportSession.java
+++ b/src/main/java/de/thm/arsnova/entities/transport/ImportExportSession.java
@@ -18,6 +18,8 @@
 package de.thm.arsnova.entities.transport;
 
 import com.fasterxml.jackson.annotation.JsonView;
+import de.thm.arsnova.entities.Answer;
+import de.thm.arsnova.entities.Comment;
 import de.thm.arsnova.entities.Content;
 import de.thm.arsnova.entities.Motd;
 import de.thm.arsnova.entities.Session;
diff --git a/src/main/java/de/thm/arsnova/entities/transport/LearningProgressOptions.java b/src/main/java/de/thm/arsnova/entities/transport/LearningProgressOptions.java
deleted file mode 100644
index 2bdc44886d518d97bed200f54246369148e1e257..0000000000000000000000000000000000000000
--- a/src/main/java/de/thm/arsnova/entities/transport/LearningProgressOptions.java
+++ /dev/null
@@ -1,61 +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.entities.transport;
-
-/**
- * A session's settings regarding the calculation of the learning progress.
- */
-public class LearningProgressOptions {
-
-	private String sessionKeyword;
-
-	private String type;
-
-	private String questionVariant;
-
-	public String getSessionKeyword() {
-		return sessionKeyword;
-	}
-
-	public void setSessionKeyword(String sessionKeyword) {
-		this.sessionKeyword = sessionKeyword;
-	}
-
-	public String getType() {
-		return type;
-	}
-
-	public void setType(String learningProgressType) {
-		this.type = learningProgressType;
-	}
-
-	public String getQuestionVariant() {
-		return questionVariant;
-	}
-
-	public void setQuestionVariant(String questionVariant) {
-		this.questionVariant = questionVariant;
-	}
-
-	public de.thm.arsnova.entities.LearningProgressOptions toEntity() {
-		de.thm.arsnova.entities.LearningProgressOptions entity = new de.thm.arsnova.entities.LearningProgressOptions();
-		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 686a733d09775ba257e97c8a11970bb105942ba4..1bb9af1a69c41ddc2bd6551611aa6b9d16808933 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 818113b284d112345af39eecefb27bb0ec241562..a647eeb35426da6341e84366958e98a7139bc13c 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 f02c32154ca8897d5f95251bb59a895387b72c76..6e09b1a9c9f4cf4bd10b2c90492c65b9a48ff076 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 60c104ab2cf1cab6f63e57e61aaec791ea3445cd..ef8e5f94446fd0d9d4c1f88b8e69d71b6fbee6df 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 14848eec737fb72b827e00a2845c8205877469bf..48825596351c5abc5ed24c3b0812dc6434c61280 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 1321bc33f9eca71b93e7506b43582e26a7afdcd2..df3b433d12dacc17cce90b1407982f0c41098529 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 7c772bc4676d8d8f990ce5839ef946bf1bebced2..0fbfe9639120099e229830965e8a65a48004b446 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 0ee77d87ca63cb691c06b723c0e83440de3d6ce5..8eb0c43cac4f6fc737ece93467bb1ce0208d7715 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 55970e207457824eff5d3ba23772ed3e24ad4e68..b47ae62d191ac285a0c8837cfdff9e29103e559b 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 a22b545aa4a81d7f0e80d9059531537943f3a3fd..54541263244339fa0482d997bb2769035ee452f9 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 a416b205d9eb714f2d55d183a4930a9a7c1e1332..f8f3f49944be61bfeb7695d5d354242d40991de2 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 0204ee5b7d5ca8bdd101a0cf41ddb7c005da0800..e2c1d7eb56d6d4d6ae42cc4ec5573ffee8fb7ed4 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 3bffe73bb4639cc8c8509a3fad8fabf74dada234..94f621e1aa063c2a5d0fac2c6180f6ae852303b4 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 40e08462519cc183a18b7ad108327312f3b387a8..f8b062fa4bd281ec62f54f2597e2a018ec736df0 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 78d7688fbf0fe22648fc3bb83fe00b89ee141be6..023378e3500b5bba827461fac69da2a8debe445f 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 c6330eb6e78b18e4d17f34c0a9df9a2b8dc6c4d4..fa19651f6999f68fede3f104a08b476d10731e33 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 8371c8b01c59573a2f7b6320421623a1dba4ff26..d9b3a11b3dd5d49d57b8431b436bf12fa2280512 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 2981f4e18f5610a5799167146c1036e0cb286be2..b5e792edeb26ae737a86648033e4764cdafdba3f 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 e3e9f000fd8fafa074572445ce493510a1a06d68..e18457bd036c8cfb418f138eef5f9e6a1053f78c 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 6f8043c18ae279962546d180e4191bde2d5e193e..5a08c87a3e8f1dc67cd49ac460ec1cf98acd7cf3 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 9172a5b5a5b307be3ed77a881ee0a868872674f7..afb0e6a703c47477a46968f0386c10a47f439a28 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 8330cbcd5b40c4d6ecf4b7cd2a1ad3c2e0dd0865..8b205b4017d84083c1bb63319c3aa64018163ba5 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 f4a88dbf8c8474a56386850738ed112ca3f6a494..10cd6fe9be5fc78e9364b97e1486e4b6f8cfa80a 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 1a9e1616ec35819e5825b14b6ad1fb5609e82f92..07e93e03e98764488becc8a91924a4cc968c9a88 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 ca8115e501c6df0f639f5fb11e628a2443a49311..5bb2893848e1717c5f3471528b9eea4e0d7618bc 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 64b51aea8780c1a4d5b68efd667b21f5eecc3e4d..6833d7a03f980e27db8f592d4b5ce7a8e5bf419f 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 dfaed2bb4fbf87b73fc27b9dae220de29b3bf29f..c9979950295be97c7d1887cdb896fcd77ea3a50d 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 335345185364f88e718c0f19d4b50c55520390f4..c6d6c240cc65af9924aee42ce3e4f3e633118a4b 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 96bd50df0d30637ea3c4fc3b87c65cba5b0bee70..0182fec221dbb085282169e0e1b533785898062d 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 00ea28c423c6ae48e12f19a58c486a4df970d6e9..66200d7de2b3cbfb9c6c824e84581792583ef61c 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 32c575dad0272c8c6da408f7b7e55737b6aa414f..751aebee8f037ebf959541c75626a19091911309 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 1d8e24c61ae3661a45b6d42b3f92d464df9f71e2..8401e08453f7f963d533713a99af2207d2625d10 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 153b702a6fbdd1177eb6fbb5fa920c8d99319215..72fbe8e98138bb4b224309de3856044fc4d821ea 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 00bf47a4d8781e8368cc33fcd14a0c1f122314b3..5ec26409244e2bf2c71d876506fe4edb17c99249 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 2dd3d4671849ac43cccf7a457ddc2e43a63feec7..1f834846fab6a58538b32061aa7d54cddb5e95d9 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/AnswerRepository.java b/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java
index 27693fa2b45a20be0526e70078d2d278f40422aa..dc2628a161e18f06b8b92bf31be78330695c4c8e 100644
--- a/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java
@@ -18,32 +18,23 @@
 package de.thm.arsnova.persistance;
 
 import de.thm.arsnova.entities.Answer;
-import de.thm.arsnova.entities.Content;
-import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.User;
+import org.springframework.data.repository.CrudRepository;
 
 import java.util.List;
 
-public interface AnswerRepository {
-	Answer get(String id);
-	Answer getMyAnswer(User me, String questionId, int piRound);
-	List<Answer> getAnswers(Content content, int piRound);
-	List<Answer> getAnswers(Content content);
-	List<Answer> getAllAnswers(Content content);
-	int getAnswerCount(Content content, int piRound);
-	int getTotalAnswerCountByQuestion(Content content);
-	int getAbstentionAnswerCount(String questionId);
-	List<Answer> getFreetextAnswers(String questionId, final int start, final int limit);
-	List<Answer> getMyAnswers(User me, Session session);
-	int getTotalAnswerCount(String sessionKey);
-	int deleteAnswers(Content content);
-	Answer saveAnswer(Answer answer, User user, Content content, Session session);
-	Answer updateAnswer(Answer answer);
-	void deleteAnswer(String answerId);
-	int countLectureQuestionAnswers(Session session);
-	int countPreparationQuestionAnswers(Session session);
-	int deleteAllQuestionsAnswers(Session session);
-	int deleteAllPreparationAnswers(Session session);
-	int deleteAllLectureAnswers(Session session);
-	int[] deleteAllAnswersWithQuestions(List<Content> contents);
+public interface AnswerRepository extends CrudRepository<Answer, String> {
+	Answer findByQuestionIdUserPiRound(String questionId, User user, int piRound);
+	List<Answer> findByContentIdPiRound(String contentId, int piRound);
+	List<Answer> findByContentId(String contentId);
+	int countByContentIdRound(String contentId, int round);
+	int countByContentId(String contentId);
+	List<Answer> findByContentId(String contentId, int start, int limit);
+	List<Answer> findByUserSessionId(User user, String sessionId);
+	int countBySessionKey(String sessionKey);
+	int deleteByContentId(String contentId);
+	int countBySessionIdLectureVariant(String sessionId);
+	int countBySessionIdPreparationVariant(String sessionId);
+	int deleteAllAnswersForQuestions(List<String> contentIds);
+	int deleteByContentIds(List<String> contentIds);
 }
diff --git a/src/main/java/de/thm/arsnova/persistance/CommentRepository.java b/src/main/java/de/thm/arsnova/persistance/CommentRepository.java
index c5f8bc350a907609195cda995c01401349a8f136..6e97fb98a70399eebca6150a28dad0ac21a5289c 100644
--- a/src/main/java/de/thm/arsnova/persistance/CommentRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/CommentRepository.java
@@ -2,21 +2,18 @@ package de.thm.arsnova.persistance;
 
 import de.thm.arsnova.entities.Comment;
 import de.thm.arsnova.entities.CommentReadingCount;
-import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.User;
+import org.springframework.data.repository.CrudRepository;
 
 import java.util.List;
 
-public interface CommentRepository {
-	int getInterposedCount(String sessionKey);
-	CommentReadingCount getInterposedReadingCount(Session session);
-	CommentReadingCount getInterposedReadingCount(Session session, User user);
-	List<Comment> getInterposedQuestions(Session session, final int start, final int limit);
-	List<Comment> getInterposedQuestions(Session session, User user, final int start, final int limit);
-	Comment getInterposedQuestion(String commentId);
-	Comment saveQuestion(Session session, Comment comment, User user);
-	void markInterposedQuestionAsRead(Comment comment);
-	void deleteInterposedQuestion(Comment comment);
-	int deleteAllInterposedQuestions(Session session);
-	int deleteAllInterposedQuestions(Session session, User user);
+public interface CommentRepository extends CrudRepository<Comment, String> {
+	int countBySessionId(String sessionKey);
+	CommentReadingCount countReadingBySessionId(String sessionId);
+	CommentReadingCount countReadingBySessionIdAndUser(String sessionId, User user);
+	List<Comment> findBySessionId(String sessionId, int start, int limit);
+	List<Comment> findBySessionIdAndUser(String sessionId, User user, int start, int limit);
+	Comment findOne(String commentId);
+	int deleteBySessionId(String sessionId);
+	int deleteBySessionIdAndUser(String sessionId, User user);
 }
diff --git a/src/main/java/de/thm/arsnova/persistance/ContentRepository.java b/src/main/java/de/thm/arsnova/persistance/ContentRepository.java
index 9c49aa7892f774b4eed4b6bb99b99bb6b70de7a2..9dbef6ce1605be5017c64818e5f2c8cec2771afe 100644
--- a/src/main/java/de/thm/arsnova/persistance/ContentRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/ContentRepository.java
@@ -1,44 +1,32 @@
 package de.thm.arsnova.persistance;
 
 import de.thm.arsnova.entities.Content;
-import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.User;
+import org.springframework.data.repository.CrudRepository;
 
 import java.util.List;
 
-public interface ContentRepository {
-	List<Content> getQuestions(Object... keys);
-	Content getQuestion(String id);
-	Content saveQuestion(Session session, Content content);
-	List<Content> getSkillQuestionsForUsers(Session session);
-	List<Content> getSkillQuestionsForTeachers(Session session);
-	int getSkillQuestionCount(Session session);
-	List<String> getQuestionIds(Session session, User user);
-	int deleteQuestionWithAnswers(Content content);
-	int[] deleteAllQuestionsWithAnswers(Session session);
-	List<String> getUnAnsweredQuestionIds(Session session, User user);
-	Content updateQuestion(Content content);
-	List<Content> getLectureQuestionsForUsers(Session session);
-	List<Content> getLectureQuestionsForTeachers(Session session);
-	List<Content> getFlashcardsForUsers(Session session);
-	List<Content> getFlashcardsForTeachers(Session session);
-	List<Content> getPreparationQuestionsForUsers(Session session);
-	List<Content> getPreparationQuestionsForTeachers(Session session);
-	List<Content> getAllSkillQuestions(Session session);
-	int getLectureQuestionCount(Session session);
-	int getFlashcardCount(Session session);
-	int getPreparationQuestionCount(Session session);
-	void publishQuestions(Session session, boolean publish, List<Content> contents);
-	List<Content> publishAllQuestions(Session session, boolean publish);
-	List<String> getQuestionIdsBySubject(Session session, String questionVariant, String subject);
-	List<Content> getQuestionsByIds(List<String> ids, Session session);
-	void resetQuestionsRoundState(Session session, List<Content> contents);
-	void setVotingAdmissions(Session session, boolean disableVoting, List<Content> contents);
-	List<Content> setVotingAdmissionForAllQuestions(Session session, boolean disableVoting);
-	int[] deleteAllLectureQuestionsWithAnswers(Session session);
-	int[] deleteAllFlashcardsWithAnswers(Session session);
-	int[] deleteAllPreparationQuestionsWithAnswers(Session session);
-	List<String> getSubjects(Session session, String questionVariant);
-	List<String> getUnAnsweredLectureQuestionIds(Session session, User user);
-	List<String> getUnAnsweredPreparationQuestionIds(Session session, User user);
+public interface ContentRepository extends CrudRepository<Content, String> {
+	List<Content> findBySessionIdAndVariantAndActive(Object... keys);
+	List<Content> findBySessionIdForUsers(String sessionId);
+	List<Content> findBySessionIdForSpeaker(String sessionId);
+	int countBySessionId(String sessionId);
+	List<String> findIdsBySessionId(String sessionId);
+	List<String> findIdsBySessionIdAndVariant(String sessionId, String variant);
+	int deleteBySessionId(String sessionId);
+	List<String> findUnansweredIdsBySessionIdAndUser(String sessionId, User user);
+	List<Content> findBySessionIdOnlyLectureVariantAndActive(String sessionId);
+	List<Content> findBySessionIdOnlyLectureVariant(String sessionId);
+	List<Content> findBySessionIdOnlyFlashcardVariantAndActive(String sessionId);
+	List<Content> findBySessionIdOnlyFlashcardVariant(String sessionId);
+	List<Content> findBySessionIdOnlyPreparationVariantAndActive(String sessionId);
+	List<Content> findBySessionIdOnlyPreparationVariant(String sessionId);
+	List<Content> findBySessionId(String sessionId);
+	int countLectureVariantBySessionId(String sessionId);
+	int countFlashcardVariantBySessionId(String sessionId);
+	int countPreparationVariantBySessionId(String sessionId);
+	List<String> findIdsBySessionIdAndVariantAndSubject(String sessionId, String questionVariant, String subject);
+	List<String> findSubjectsBySessionIdAndVariant(String sessionId, String questionVariant);
+	List<String> findUnansweredIdsBySessionIdAndUserOnlyLectureVariant(String sessionId, User user);
+	List<String> findUnansweredIdsBySessionIdAndUserOnlyPreparationVariant(String sessionId, User user);
 }
diff --git a/src/main/java/de/thm/arsnova/persistance/LogEntryRepository.java b/src/main/java/de/thm/arsnova/persistance/LogEntryRepository.java
index 7ec30eb51c5b8407680b629b72a2f99f4f76c54a..de88f3bd51c2a06cd514694b4bda48163ac4d106 100644
--- a/src/main/java/de/thm/arsnova/persistance/LogEntryRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/LogEntryRepository.java
@@ -41,7 +41,7 @@ public interface LogEntryRepository {
 	 * @param payload arbitrary logging data
 	 * @param level severity of the event
 	 */
-	default void log(String event, Map<String, Object> payload, LogEntry.LogLevel level) {
+	default void log(final String event, final Map<String, Object> payload, final LogEntry.LogLevel level) {
 		create(event, level, payload);
 	}
 
@@ -53,7 +53,7 @@ public interface LogEntryRepository {
 	 * @param event type of the event
 	 * @param payload arbitrary logging data
 	 */
-	default void log(String event, Map<String, Object> payload) {
+	default void log(final String event, final Map<String, Object> payload) {
 		create(event, LogEntry.LogLevel.INFO, payload);
 	}
 
@@ -65,7 +65,7 @@ public interface LogEntryRepository {
 	 * @param level severity of the event
 	 * @param rawPayload key/value pairs of arbitrary logging data
 	 */
-	default void log(String event, LogEntry.LogLevel level, Object... rawPayload) {
+	default void log(final String event, final LogEntry.LogLevel level, final Object... rawPayload) {
 		if (rawPayload.length % 2 != 0) {
 			throw new IllegalArgumentException("");
 		}
@@ -84,7 +84,7 @@ public interface LogEntryRepository {
 	 * @param event type of the event
 	 * @param rawPayload key/value pairs of arbitrary logging data
 	 */
-	default void log(String event, Object... rawPayload) {
+	default void log(final String event, final Object... rawPayload) {
 		log(event, LogEntry.LogLevel.INFO, rawPayload);
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/persistance/MotdListRepository.java b/src/main/java/de/thm/arsnova/persistance/MotdListRepository.java
index a044fe7ed6b637ea6c6bc0c487908f4082c42609..5b3a460f43216d8e8f70e9ac31160c59d41d3905 100644
--- a/src/main/java/de/thm/arsnova/persistance/MotdListRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/MotdListRepository.java
@@ -3,6 +3,6 @@ package de.thm.arsnova.persistance;
 import de.thm.arsnova.entities.MotdList;
 
 public interface MotdListRepository {
-	MotdList getMotdListForUser(final String username);
-	MotdList createOrUpdateMotdList(MotdList motdlist);
+	MotdList findByUsername(String username);
+	MotdList save(MotdList motdlist);
 }
diff --git a/src/main/java/de/thm/arsnova/persistance/MotdRepository.java b/src/main/java/de/thm/arsnova/persistance/MotdRepository.java
index 9c783165d8affbbfc057223ca76a87dbf17fb61f..deb9e9334f700f254e0de4d0f42258b28eb5b5e3 100644
--- a/src/main/java/de/thm/arsnova/persistance/MotdRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/MotdRepository.java
@@ -18,17 +18,16 @@
 package de.thm.arsnova.persistance;
 
 import de.thm.arsnova.entities.Motd;
+import org.springframework.data.repository.CrudRepository;
 
 import java.util.List;
 
-public interface MotdRepository {
-	List<Motd> getAdminMotds();
-	List<Motd> getMotdsForAll();
-	List<Motd> getMotdsForLoggedIn();
-	List<Motd> getMotdsForTutors();
-	List<Motd> getMotdsForStudents();
-	List<Motd> getMotdsForSession(final String sessionkey);
-	Motd getMotdByKey(String key);
-	Motd createOrUpdateMotd(Motd motd);
-	boolean deleteMotd(Motd motd);
+public interface MotdRepository extends CrudRepository<Motd, String> {
+	List<Motd> findGlobalForAdmin();
+	List<Motd> findGlobalForAll();
+	List<Motd> findGlobalForLoggedIn();
+	List<Motd> findGlobalForTutors();
+	List<Motd> findForStudents();
+	List<Motd> findBySessionKey(String sessionkey);
+	Motd findByKey(String key);
 }
diff --git a/src/main/java/de/thm/arsnova/persistance/SessionRepository.java b/src/main/java/de/thm/arsnova/persistance/SessionRepository.java
index 4c57f848416dd1cfb37f665758689e344839ecd1..43d88c292b696526a3daded0356765bf880ef6e5 100644
--- a/src/main/java/de/thm/arsnova/persistance/SessionRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/SessionRepository.java
@@ -23,37 +23,24 @@ import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.SessionInfo;
 import de.thm.arsnova.entities.User;
 import de.thm.arsnova.entities.transport.ImportExportSession;
+import org.springframework.data.repository.CrudRepository;
 
 import java.util.List;
 
-public interface SessionRepository {
-	Session getSessionFromId(String sessionId);
-	Session getSessionFromKeyword(String keyword);
-	Session saveSession(User user, Session session);
-	Session updateSession(Session session);
-
-	/**
-	 * Deletes a session and related data.
-	 *
-	 * @param session the session for deletion
-	 */
-	int[] deleteSession(Session session);
-
-	Session changeSessionCreator(Session session, String newCreator);
-	int[] deleteInactiveGuestSessions(long lastActivityBefore);
-	List<Session> getMySessions(User user, final int start, final int limit);
-	List<Session> getSessionsForUsername(String username, final int start, final int limit);
-	List<Session> getPublicPoolSessions();
-	List<Session> getMyPublicPoolSessions(User user);
-	boolean sessionKeyAvailable(String keyword);
-	Session updateSessionOwnerActivity(Session session);
-	List<Session> getVisitedSessionsForUsername(String username, final int start, final int limit);
-	List<SessionInfo> getMySessionsInfo(User user, final int start, final int limit);
-	List<SessionInfo> getPublicPoolSessionsInfo();
-	List<SessionInfo> getMyPublicPoolSessionsInfo(final User user);
-	List<SessionInfo> getMyVisitedSessionsInfo(User currentUser, final int start, final int limit);
-	List<Session> getCourseSessions(List<Course> courses);
+public interface SessionRepository extends CrudRepository<Session, String> {
+	Session findByKeyword(String keyword);
+	List<Session> findInactiveGuestSessionsMetadata(long lastActivityBefore);
+	List<Session> findByUser(User user, int start, int limit);
+	List<Session> findByUsername(String username, int start, int limit);
+	List<Session> findAllForPublicPool();
+	List<Session> findForPublicPoolByUser(User user);
+	List<Session> findVisitedByUsername(String username, int start, int limit);
+	List<SessionInfo> getMySessionsInfo(User user, int start, int limit);
+	List<SessionInfo> findInfosForPublicPool();
+	List<SessionInfo> findInfosForPublicPoolByUser(User user);
+	List<SessionInfo> findInfoForVisitedByUser(User currentUser, int start, int limit);
+	List<Session> findSessionsByCourses(List<Course> courses);
 	SessionInfo importSession(User user, ImportExportSession importSession);
 	ImportExportSession exportSession(String sessionkey, Boolean withAnswer, Boolean withFeedbackQuestions);
-	LoggedIn registerAsOnlineUser(final User user, final Session session);
+	LoggedIn registerAsOnlineUser(User user, Session session);
 }
diff --git a/src/main/java/de/thm/arsnova/persistance/SessionStatisticsRepository.java b/src/main/java/de/thm/arsnova/persistance/SessionStatisticsRepository.java
index 193da0333f4aaf143065f69a12eaef674b1a75c3..5791280415b3c7379780d03c7799eb5c2892b6ad 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/UserRepository.java b/src/main/java/de/thm/arsnova/persistance/UserRepository.java
index 7807f66aa3beb3583f2d7b6a22746ce158f4f327..c4480fac964d8276101f82f0befade165478fcb8 100644
--- a/src/main/java/de/thm/arsnova/persistance/UserRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/UserRepository.java
@@ -18,10 +18,9 @@
 package de.thm.arsnova.persistance;
 
 import de.thm.arsnova.entities.DbUser;
+import org.springframework.data.repository.CrudRepository;
 
-public interface UserRepository {
-	DbUser findUserByUsername(String username);
-	DbUser createOrUpdateUser(DbUser user);
-	boolean deleteUser(DbUser user);
+public interface UserRepository extends CrudRepository<DbUser, String> {
+	DbUser findByUsername(String username);
 	int deleteInactiveUsers(long lastActivityBefore);
 }
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java
index 244290982c7bc315631d0555874ce8e1c730c73f..96f8e43ed62a0c7fd7d60cf18e3b9355d7ea386f 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java
@@ -3,16 +3,9 @@ package de.thm.arsnova.persistance.couchdb;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.collect.Lists;
 import de.thm.arsnova.entities.Answer;
-import de.thm.arsnova.entities.Content;
-import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.User;
-import de.thm.arsnova.entities.transport.AnswerQueueElement;
-import de.thm.arsnova.events.NewAnswerEvent;
-import de.thm.arsnova.exceptions.NotFoundException;
 import de.thm.arsnova.persistance.AnswerRepository;
-import de.thm.arsnova.persistance.ContentRepository;
 import de.thm.arsnova.persistance.LogEntryRepository;
-import de.thm.arsnova.persistance.SessionRepository;
 import org.ektorp.BulkDeleteDocument;
 import org.ektorp.ComplexKey;
 import org.ektorp.CouchDbConnector;
@@ -20,90 +13,48 @@ import org.ektorp.DbAccessException;
 import org.ektorp.DocumentOperationResult;
 import org.ektorp.UpdateConflictException;
 import org.ektorp.ViewResult;
-import org.ektorp.support.CouchDbRepositorySupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.cache.annotation.Cacheable;
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.context.ApplicationEventPublisherAware;
-import org.springframework.scheduling.annotation.Scheduled;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
 
-public class CouchDbAnswerRepository extends CouchDbRepositorySupport<Answer> implements AnswerRepository, ApplicationEventPublisherAware {
+public class CouchDbAnswerRepository extends CouchDbCrudRepository<Answer> implements AnswerRepository, ApplicationEventPublisherAware {
 	private static final int BULK_PARTITION_SIZE = 500;
 	private static final Logger logger = LoggerFactory.getLogger(CouchDbAnswerRepository.class);
 
-	private final Queue<AnswerQueueElement> answerQueue = new ConcurrentLinkedQueue<>();
-
 	@Autowired
 	private LogEntryRepository dbLogger;
 
-	@Autowired
-	private SessionRepository sessionRepository;
-
-	@Autowired
-	private ContentRepository contentRepository;
-
 	private ApplicationEventPublisher publisher;
 
-	public CouchDbAnswerRepository(CouchDbConnector db, boolean createIfNotExists) {
-		super(Answer.class, db, createIfNotExists);
-	}
-
-	@Scheduled(fixedDelay = 5000)
-	public void flushAnswerQueue() {
-		if (answerQueue.isEmpty()) {
-			// no need to send an empty bulk request.
-			return;
-		}
-
-		final List<Answer> answerList = new ArrayList<>();
-		final List<AnswerQueueElement> elements = new ArrayList<>();
-		AnswerQueueElement entry;
-		while ((entry = this.answerQueue.poll()) != null) {
-			final Answer answer = entry.getAnswer();
-			answerList.add(answer);
-			elements.add(entry);
-		}
-		try {
-			db.executeBulk(answerList);
-
-			// Send NewAnswerEvents ...
-			for (AnswerQueueElement e : elements) {
-				this.publisher.publishEvent(new NewAnswerEvent(this, e.getSession(), e.getAnswer(), e.getUser(), e.getQuestion()));
-			}
-		} catch (DbAccessException e) {
-			logger.error("Could not bulk save answers from queue.", e);
-		}
+	public CouchDbAnswerRepository(final CouchDbConnector db, final boolean createIfNotExists) {
+		super(Answer.class, db, "by_sessionid", createIfNotExists);
 	}
 
 	@Override
-	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
+	public void setApplicationEventPublisher(final ApplicationEventPublisher publisher) {
 		this.publisher = publisher;
 	}
 
-	@CacheEvict("answers")
 	@Override
-	public int deleteAnswers(final Content content) {
+	public int deleteByContentId(final String contentId) {
 		try {
 			final ViewResult result = db.queryView(createQuery("by_questionid")
-					.key(content.getId()));
+					.key(contentId));
 			final List<List<ViewResult.Row>> partitions = Lists.partition(result.getRows(), BULK_PARTITION_SIZE);
 
 			int count = 0;
-			for (List<ViewResult.Row> partition: partitions) {
-				List<BulkDeleteDocument> answersToDelete = new ArrayList<>();
+			for (final List<ViewResult.Row> partition: partitions) {
+				final List<BulkDeleteDocument> answersToDelete = new ArrayList<>();
 				for (final ViewResult.Row a : partition) {
 					final BulkDeleteDocument d = new BulkDeleteDocument(a.getId(), a.getValueAsNode().get("_rev").asText());
 					answersToDelete.add(d);
 				}
-				List<DocumentOperationResult> errors = db.executeBulk(answersToDelete);
+				final List<DocumentOperationResult> errors = db.executeBulk(answersToDelete);
 				count += partition.size() - errors.size();
 				if (errors.size() > 0) {
 					logger.error("Could not bulk delete {} of {} answers.", errors.size(), partition.size());
@@ -113,29 +64,29 @@ public class CouchDbAnswerRepository extends CouchDbRepositorySupport<Answer> im
 
 			return count;
 		} catch (final DbAccessException e) {
-			logger.error("Could not delete answers for content {}.", content.getId(), e);
+			logger.error("Could not delete answers for content {}.", contentId, e);
 		}
 
 		return 0;
 	}
 
 	@Override
-	public Answer getMyAnswer(final User me, final String questionId, final int piRound) {
+	public Answer findByQuestionIdUserPiRound(final String contentId, final User user, final int piRound) {
 		final List<Answer> answerList = queryView("by_questionid_user_piround",
-				ComplexKey.of(questionId, me.getUsername(), piRound));
+				ComplexKey.of(contentId, user.getUsername(), piRound));
 		return answerList.isEmpty() ? null : answerList.get(0);
 	}
 
 	@Override
-	public List<Answer> getAnswers(final Content content, final int piRound) {
-		final String questionId = content.getId();
+	public List<Answer> findByContentIdPiRound(final String contentId, final int piRound) {
+		final String questionId = contentId;
 		final ViewResult result = db.queryView(createQuery("by_questionid_piround_text_subject")
 						.group(true)
 						.startKey(ComplexKey.of(questionId, piRound))
 						.endKey(ComplexKey.of(questionId, piRound, ComplexKey.emptyObject())));
-		final int abstentionCount = getAbstentionAnswerCount(questionId);
+		final int abstentionCount = countByContentId(questionId);
 
-		List<Answer> answers = new ArrayList<>();
+		final List<Answer> answers = new ArrayList<>();
 		for (final ViewResult.Row d : result) {
 			final Answer a = new Answer();
 			a.setAnswerCount(d.getValueAsInt());
@@ -151,13 +102,12 @@ public class CouchDbAnswerRepository extends CouchDbRepositorySupport<Answer> im
 	}
 
 	@Override
-	public List<Answer> getAllAnswers(final Content content) {
-		final String questionId = content.getId();
+	public List<Answer> findByContentId(final String contentId) {
 		final ViewResult result = db.queryView(createQuery("by_questionid_piround_text_subject")
 				.group(true)
-				.startKey(ComplexKey.of(questionId))
-				.endKey(ComplexKey.of(questionId, ComplexKey.emptyObject())));
-		final int abstentionCount = getAbstentionAnswerCount(questionId);
+				.startKey(ComplexKey.of(contentId))
+				.endKey(ComplexKey.of(contentId, ComplexKey.emptyObject())));
+		final int abstentionCount = countByContentId(contentId);
 
 		final List<Answer> answers = new ArrayList<>();
 		for (final ViewResult.Row d : result.getRows()) {
@@ -177,44 +127,28 @@ public class CouchDbAnswerRepository extends CouchDbRepositorySupport<Answer> im
 		return answers;
 	}
 
-	@Cacheable("answers")
-	@Override
-	public List<Answer> getAnswers(final Content content) {
-		return this.getAnswers(content, content.getPiRound());
-	}
-
-	@Override
-	public int getAbstentionAnswerCount(final String questionId) {
-		final ViewResult result = db.queryView(createQuery("by_questionid_piround_text_subject")
-				//.group(true)
-				.startKey(ComplexKey.of(questionId))
-				.endKey(ComplexKey.of(questionId, ComplexKey.emptyObject())));
-
-		return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt();
-	}
-
 	@Override
-	public int getAnswerCount(final Content content, final int piRound) {
+	public int countByContentId(final String contentId) {
 		final ViewResult result = db.queryView(createQuery("by_questionid_piround_text_subject")
-				//.group(true)
-				.startKey(ComplexKey.of(content.getId(), piRound))
-				.endKey(ComplexKey.of(content.getId(), piRound, ComplexKey.emptyObject())));
+				.reduce(true)
+				.startKey(ComplexKey.of(contentId))
+				.endKey(ComplexKey.of(contentId, ComplexKey.emptyObject())));
 
 		return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt();
 	}
 
 	@Override
-	public int getTotalAnswerCountByQuestion(final Content content) {
+	public int countByContentIdRound(final String contentId, final int round) {
 		final ViewResult result = db.queryView(createQuery("by_questionid_piround_text_subject")
-				//.group(true)
-				.startKey(ComplexKey.of(content.getId()))
-				.endKey(ComplexKey.of(content.getId(), ComplexKey.emptyObject())));
+				.reduce(true)
+				.startKey(ComplexKey.of(contentId, round))
+				.endKey(ComplexKey.of(contentId, round, ComplexKey.emptyObject())));
 
 		return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt();
 	}
 
 	@Override
-	public List<Answer> getFreetextAnswers(final String questionId, final int start, final int limit) {
+	public List<Answer> findByContentId(final String contentId, final int start, final int limit) {
 		final int qSkip = start > 0 ? start : -1;
 		final int qLimit = limit > 0 ? limit : -1;
 
@@ -222,8 +156,8 @@ public class CouchDbAnswerRepository extends CouchDbRepositorySupport<Answer> im
 						.skip(qSkip)
 						.limit(qLimit)
 						//.includeDocs(true)
-						.startKey(ComplexKey.of(questionId))
-						.endKey(ComplexKey.of(questionId, ComplexKey.emptyObject()))
+						.startKey(ComplexKey.of(contentId))
+						.endKey(ComplexKey.of(contentId, ComplexKey.emptyObject()))
 						.descending(true),
 				Answer.class);
 
@@ -231,155 +165,72 @@ public class CouchDbAnswerRepository extends CouchDbRepositorySupport<Answer> im
 	}
 
 	@Override
-	public List<Answer> getMyAnswers(final User me, final Session s) {
-		return queryView("by_user_sessionid", ComplexKey.of(me.getUsername(), s.getId()));
+	public List<Answer> findByUserSessionId(final User user, final String sessionId) {
+		return queryView("by_user_sessionid", ComplexKey.of(user.getUsername(), sessionId));
 	}
 
 	@Override
-	public int getTotalAnswerCount(final String sessionKey) {
-		final Session s = sessionRepository.getSessionFromKeyword(sessionKey);
-		if (s == null) {
-			throw new NotFoundException();
-		}
-		final ViewResult result = db.queryView(createQuery("by_sessionid_variant").key(s.getId()));
+	public int countBySessionKey(final String sessionKey) {
+		final ViewResult result = db.queryView(createQuery("by_sessionid_variant").key(sessionKey));
 
 		return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt();
 	}
 
-	@CacheEvict(value = "answers", key = "#content")
 	@Override
-	public Answer saveAnswer(final Answer answer, final User user, final Content content, final Session session) {
-		db.create(answer);
-		this.answerQueue.offer(new AnswerQueueElement(session, content, answer, user));
-
-		return answer;
+	public int countBySessionIdLectureVariant(final String sessionId) {
+		return countBySessionIdVariant(sessionId, "lecture");
 	}
 
-	/* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */
-	@CacheEvict(value = "answers", allEntries = true)
 	@Override
-	public Answer updateAnswer(final Answer answer) {
-		try {
-			update(answer);
-			return answer;
-		} catch (final UpdateConflictException e) {
-			logger.error("Could not update answer {}.", answer, e);
-		}
-
-		return null;
+	public int countBySessionIdPreparationVariant(final String sessionId) {
+		return countBySessionIdVariant(sessionId, "preparation");
 	}
 
-	/* TODO: Only evict cache entry for the answer's session. This requires some refactoring. */
-	@CacheEvict(value = "answers", allEntries = true)
-	@Override
-	public void deleteAnswer(final String answerId) {
-		try {
-			/* TODO: use id and rev instead of loading the answer */
-			db.delete(get(answerId));
-			dbLogger.log("delete", "type", "answer");
-		} catch (final DbAccessException e) {
-			logger.error("Could not delete answer {}.", answerId, e);
-		}
-	}
-
-	@Override
-	public int countLectureQuestionAnswers(final Session session) {
-		return countQuestionVariantAnswers(session, "lecture");
-	}
-
-	@Override
-	public int countPreparationQuestionAnswers(final Session session) {
-		return countQuestionVariantAnswers(session, "preparation");
-	}
-
-	private int countQuestionVariantAnswers(final Session session, final String variant) {
+	private int countBySessionIdVariant(final String sessionId, final String variant) {
 		final ViewResult result = db.queryView(createQuery("by_sessionid_variant")
-				.key(ComplexKey.of(session.getId(), variant)));
+				.key(ComplexKey.of(sessionId, variant)));
 
 		return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt();
 	}
 
-	/* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */
-	@CacheEvict(value = "answers", allEntries = true)
-	@Override
-	public int deleteAllQuestionsAnswers(final Session session) {
-		final List<Content> contents = contentRepository.getQuestions(session.getId());
-		contentRepository.resetQuestionsRoundState(session, contents);
-
-		return deleteAllAnswersForQuestions(contents);
-	}
-
-	/* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */
-	@CacheEvict(value = "answers", allEntries = true)
 	@Override
-	public int deleteAllPreparationAnswers(final Session session) {
-		final List<Content> contents = contentRepository.getQuestions(session.getId(), "preparation");
-		contentRepository.resetQuestionsRoundState(session, contents);
-
-		return deleteAllAnswersForQuestions(contents);
-	}
-
-	/* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */
-	@CacheEvict(value = "answers", allEntries = true)
-	@Override
-	public int deleteAllLectureAnswers(final Session session) {
-		final List<Content> contents = contentRepository.getQuestions(session.getId(), "lecture");
-		contentRepository.resetQuestionsRoundState(session, contents);
-
-		return deleteAllAnswersForQuestions(contents);
-	}
-
-	public int deleteAllAnswersForQuestions(List<Content> contents) {
-		List<String> questionIds = new ArrayList<>();
-		for (Content q : contents) {
-			questionIds.add(q.getId());
-		}
+	public int deleteAllAnswersForQuestions(final List<String> contentIds) {
 		final ViewResult result = db.queryView(createQuery("by_questionid")
-				.keys(questionIds));
+				.keys(contentIds));
 		final List<BulkDeleteDocument> allAnswers = new ArrayList<>();
-		for (ViewResult.Row a : result.getRows()) {
+		for (final ViewResult.Row a : result.getRows()) {
 			final BulkDeleteDocument d = new BulkDeleteDocument(a.getId(), a.getValueAsNode().get("_rev").asText());
 			allAnswers.add(d);
 		}
 		try {
-			List<DocumentOperationResult> errors = db.executeBulk(allAnswers);
+			final List<DocumentOperationResult> errors = db.executeBulk(allAnswers);
 
 			return allAnswers.size() - errors.size();
-		} catch (DbAccessException e) {
+		} catch (final DbAccessException e) {
 			logger.error("Could not bulk delete answers.", e);
 		}
 
 		return 0;
 	}
 
-	public int[] deleteAllAnswersWithQuestions(List<Content> contents) {
-		List<String> questionIds = new ArrayList<>();
-		final List<BulkDeleteDocument> allQuestions = new ArrayList<>();
-		for (Content q : contents) {
-			final BulkDeleteDocument d = new BulkDeleteDocument(q.getId(), q.getRevision());
-			questionIds.add(q.getId());
-			allQuestions.add(d);
-		}
-
+	@Override
+	public int deleteByContentIds(final List<String> contentIds) {
 		final ViewResult result = db.queryView(createQuery("by_questionid")
-				.key(questionIds));
-		final List<BulkDeleteDocument> allAnswers = new ArrayList<>();
-		for (ViewResult.Row a : result.getRows()) {
+				.keys(contentIds));
+		final List<BulkDeleteDocument> deleteDocs = new ArrayList<>();
+		for (final ViewResult.Row a : result.getRows()) {
 			final BulkDeleteDocument d = new BulkDeleteDocument(a.getId(), a.getValueAsNode().get("_rev").asText());
-			allAnswers.add(d);
+			deleteDocs.add(d);
 		}
 
 		try {
-			List<BulkDeleteDocument> deleteList = new ArrayList<>(allAnswers);
-			deleteList.addAll(allQuestions);
-			List<DocumentOperationResult> errors = db.executeBulk(deleteList);
-
-			/* TODO: subtract errors from count */
-			return new int[] {allQuestions.size(), allAnswers.size()};
-		} catch (DbAccessException e) {
-			logger.error("Could not bulk delete contents and answers.", e);
+			final List<DocumentOperationResult> errors = db.executeBulk(deleteDocs);
+
+			return deleteDocs.size() - errors.size();
+		} catch (final DbAccessException e) {
+			logger.error("Could not bulk delete answers.", e);
 		}
 
-		return new int[] {0, 0};
+		return 0;
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java
index fbd64dd2ad83c42601fc51775ef1fa02ae147ecb..6276c44fbed535d262a3080bc229c45a4be69444 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java
@@ -3,45 +3,35 @@ package de.thm.arsnova.persistance.couchdb;
 import com.fasterxml.jackson.databind.JsonNode;
 import de.thm.arsnova.entities.Comment;
 import de.thm.arsnova.entities.CommentReadingCount;
-import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.User;
-import de.thm.arsnova.exceptions.NotFoundException;
 import de.thm.arsnova.persistance.CommentRepository;
 import de.thm.arsnova.persistance.LogEntryRepository;
-import de.thm.arsnova.persistance.SessionRepository;
 import org.ektorp.ComplexKey;
 import org.ektorp.CouchDbConnector;
-import org.ektorp.DocumentNotFoundException;
 import org.ektorp.UpdateConflictException;
 import org.ektorp.ViewResult;
-import org.ektorp.support.CouchDbRepositorySupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import java.util.List;
 
-public class CouchDbCommentRepository extends CouchDbRepositorySupport<Comment> implements CommentRepository {
+public class CouchDbCommentRepository extends CouchDbCrudRepository<Comment> implements CommentRepository {
 	private static final Logger logger = LoggerFactory.getLogger(CouchDbCommentRepository.class);
 
 	@Autowired
 	private LogEntryRepository dbLogger;
 
-	@Autowired
-	private SessionRepository sessionRepository;
-
-	public CouchDbCommentRepository(CouchDbConnector db, boolean createIfNotExists) {
-		super(Comment.class, db, createIfNotExists);
+	public CouchDbCommentRepository(final CouchDbConnector db, final boolean createIfNotExists) {
+		super(Comment.class, db, "by_sessionid", createIfNotExists);
 	}
 
 	@Override
-	public int getInterposedCount(final String sessionKey) {
-		final Session s = sessionRepository.getSessionFromKeyword(sessionKey);
-		if (s == null) {
-			throw new NotFoundException();
-		}
-
-		final ViewResult result = db.queryView(createQuery("by_sessionid").key(s.getId()).group(true));
+	public int countBySessionId(final String sessionId) {
+		final ViewResult result = db.queryView(createQuery("by_sessionid")
+				.key(sessionId)
+				.reduce(true)
+				.group(true));
 		if (result.isEmpty()) {
 			return 0;
 		}
@@ -50,24 +40,26 @@ public class CouchDbCommentRepository extends CouchDbRepositorySupport<Comment>
 	}
 
 	@Override
-	public CommentReadingCount getInterposedReadingCount(final Session session) {
+	public CommentReadingCount countReadingBySessionId(final String sessionId) {
 		final ViewResult result = db.queryView(createQuery("by_sessionid_read")
-				.startKey(ComplexKey.of(session.getId()))
-				.endKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject()))
+				.startKey(ComplexKey.of(sessionId))
+				.endKey(ComplexKey.of(sessionId, ComplexKey.emptyObject()))
+				.reduce(true)
 				.group(true));
-		return getInterposedReadingCount(result);
+		return calculateReadingCount(result);
 	}
 
 	@Override
-	public CommentReadingCount getInterposedReadingCount(final Session session, final User user) {
+	public CommentReadingCount countReadingBySessionIdAndUser(final String sessionId, final User user) {
 		final ViewResult result = db.queryView(createQuery("by_sessionid_creator_read")
-				.startKey(ComplexKey.of(session.getId(), user.getUsername()))
-				.endKey(ComplexKey.of(session.getId(), user.getUsername(), ComplexKey.emptyObject()))
+				.startKey(ComplexKey.of(sessionId, user.getUsername()))
+				.endKey(ComplexKey.of(sessionId, user.getUsername(), ComplexKey.emptyObject()))
+				.reduce(true)
 				.group(true));
-		return getInterposedReadingCount(result);
+		return calculateReadingCount(result);
 	}
 
-	private CommentReadingCount getInterposedReadingCount(final ViewResult viewResult) {
+	private CommentReadingCount calculateReadingCount(final ViewResult viewResult) {
 		if (viewResult.isEmpty()) {
 			return new CommentReadingCount();
 		}
@@ -111,7 +103,7 @@ public class CouchDbCommentRepository extends CouchDbRepositorySupport<Comment>
 	}
 
 	@Override
-	public List<Comment> getInterposedQuestions(final Session session, final int start, final int limit) {
+	public List<Comment> findBySessionId(final String sessionId, final int start, final int limit) {
 		final int qSkip = start > 0 ? start : -1;
 		final int qLimit = limit > 0 ? limit : -1;
 
@@ -119,8 +111,8 @@ public class CouchDbCommentRepository extends CouchDbRepositorySupport<Comment>
 						.skip(qSkip)
 						.limit(qLimit)
 						.descending(true)
-						.startKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject()))
-						.endKey(ComplexKey.of(session.getId()))
+						.startKey(ComplexKey.of(sessionId, ComplexKey.emptyObject()))
+						.endKey(ComplexKey.of(sessionId))
 						.includeDocs(true),
 				Comment.class);
 //		for (Comment comment : comments) {
@@ -131,7 +123,7 @@ public class CouchDbCommentRepository extends CouchDbRepositorySupport<Comment>
 	}
 
 	@Override
-	public List<Comment> getInterposedQuestions(final Session session, final User user, final int start, final int limit) {
+	public List<Comment> findBySessionIdAndUser(final String sessionId, final User user, final int start, final int limit) {
 		final int qSkip = start > 0 ? start : -1;
 		final int qLimit = limit > 0 ? limit : -1;
 
@@ -139,8 +131,8 @@ public class CouchDbCommentRepository extends CouchDbRepositorySupport<Comment>
 						.skip(qSkip)
 						.limit(qLimit)
 						.descending(true)
-						.startKey(ComplexKey.of(session.getId(), user.getUsername(), ComplexKey.emptyObject()))
-						.endKey(ComplexKey.of(session.getId(), user.getUsername()))
+						.startKey(ComplexKey.of(sessionId, user.getUsername(), ComplexKey.emptyObject()))
+						.endKey(ComplexKey.of(sessionId, user.getUsername()))
 						.includeDocs(true),
 				Comment.class);
 //		for (Comment comment : comments) {
@@ -151,74 +143,22 @@ public class CouchDbCommentRepository extends CouchDbRepositorySupport<Comment>
 	}
 
 	@Override
-	public Comment getInterposedQuestion(final String commentId) {
-		try {
-			final Comment comment = get(commentId);
-			/* TODO: Refactor code so the next line can be removed */
-			//comment.setSessionId(sessionRepository.getSessionFromKeyword(comment.getSessionId()).getId());
-			return comment;
-		} catch (final DocumentNotFoundException e) {
-			logger.error("Could not load comment {}.", commentId, e);
-		}
-		return null;
-	}
-
-	@Override
-	public Comment saveQuestion(final Session session, final Comment comment, User user) {
-		comment.setSessionId(session.getId());
-		comment.setCreator(user.getUsername());
-		comment.setRead(false);
-		if (comment.getTimestamp() == 0) {
-			comment.setTimestamp(System.currentTimeMillis());
-		}
-		try {
-			db.create(comment);
-
-			return comment;
-		} catch (final IllegalArgumentException e) {
-			logger.error("Could not save comment {}.", comment, e);
-		}
-
-		return null;
-	}
-
-	@Override
-	public void markInterposedQuestionAsRead(final Comment comment) {
-		try {
-			comment.setRead(true);
-			db.update(comment);
-		} catch (final UpdateConflictException e) {
-			logger.error("Could not mark comment as read {}.", comment.getId(), e);
-		}
-	}
-
-	@Override
-	public void deleteInterposedQuestion(final Comment comment) {
-		try {
-			db.delete(comment.getId(), comment.getRevision());
-			dbLogger.log("delete", "type", "comment");
-		} catch (final UpdateConflictException e) {
-			logger.error("Could not delete comment {}.", comment.getId(), e);
-		}
-	}
-
-	@Override
-	public int deleteAllInterposedQuestions(final Session session) {
-		final ViewResult result = db.queryView(createQuery("by_sessionid").key(session.getId()));
+	public int deleteBySessionId(final String sessionId) {
+		final ViewResult result = db.queryView(createQuery("by_sessionid").key(sessionId));
 
-		return deleteAllInterposedQuestions(session, result);
+		return delete(result);
 	}
 
 	@Override
-	public int deleteAllInterposedQuestions(final Session session, final User user) {
+	public int deleteBySessionIdAndUser(final String sessionId, final User user) {
 		final ViewResult result = db.queryView(createQuery("by_sessionid_creator_read")
-				.startKey(ComplexKey.of(session.getId(), user.getUsername()))
-				.endKey(ComplexKey.of(session.getId(), user.getUsername(), ComplexKey.emptyObject())));
+				.startKey(ComplexKey.of(sessionId, user.getUsername()))
+				.endKey(ComplexKey.of(sessionId, user.getUsername(), ComplexKey.emptyObject())));
 
-		return deleteAllInterposedQuestions(session, result);
+		return delete(result);
 	}
 
-	private int deleteAllInterposedQuestions(final Session session, final ViewResult comments) {
+	private int delete(final ViewResult comments) {
 		if (comments.isEmpty()) {
 			return 0;
 		}
@@ -227,7 +167,7 @@ public class CouchDbCommentRepository extends CouchDbRepositorySupport<Comment>
 			try {
 				db.delete(row.getId(), row.getValueAsNode().get("rev").asText());
 			} catch (final UpdateConflictException e) {
-				logger.error("Could not delete all comments {}.", session, e);
+				logger.error("Could not delete comments.", e);
 			}
 		}
 
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java
index fca13b13e57bd4cdffa646db7480d328b0c1e21f..ab9934fd1574e49daaf5b5c389eb3692c05c66ff 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java
@@ -1,26 +1,17 @@
 package de.thm.arsnova.persistance.couchdb;
 
 import de.thm.arsnova.entities.Content;
-import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.User;
-import de.thm.arsnova.persistance.AnswerRepository;
 import de.thm.arsnova.persistance.ContentRepository;
 import de.thm.arsnova.persistance.LogEntryRepository;
+import org.ektorp.BulkDeleteDocument;
 import org.ektorp.ComplexKey;
 import org.ektorp.CouchDbConnector;
-import org.ektorp.DbAccessException;
-import org.ektorp.DocumentNotFoundException;
-import org.ektorp.UpdateConflictException;
-import org.ektorp.ViewQuery;
+import org.ektorp.DocumentOperationResult;
 import org.ektorp.ViewResult;
-import org.ektorp.support.CouchDbRepositorySupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.cache.annotation.CachePut;
-import org.springframework.cache.annotation.Cacheable;
-import org.springframework.cache.annotation.Caching;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -30,26 +21,22 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-public class CouchDbContentRepository extends CouchDbRepositorySupport<Content> implements ContentRepository {
+public class CouchDbContentRepository extends CouchDbCrudRepository<Content> implements ContentRepository {
 	private static final Logger logger = LoggerFactory.getLogger(CouchDbContentRepository.class);
 
 	@Autowired
 	private LogEntryRepository dbLogger;
 
-	@Autowired
-	private AnswerRepository answerRepository;
-
-	public CouchDbContentRepository(CouchDbConnector db, boolean createIfNotExists) {
-		super(Content.class, db, createIfNotExists);
+	public CouchDbContentRepository(final CouchDbConnector db, final boolean createIfNotExists) {
+		super(Content.class, db, "by_sessionid", createIfNotExists);
 	}
 
-	@Cacheable("skillquestions")
 	@Override
-	public List<Content> getSkillQuestionsForUsers(final Session session) {
+	public List<Content> findBySessionIdForUsers(final String sessionId) {
 		final List<Content> contents = new ArrayList<>();
-		final List<Content> questions1 = getQuestions(session.getId(), "lecture", true);
-		final List<Content> questions2 = getQuestions(session.getId(), "preparation", true);
-		final List<Content> questions3 = getQuestions(session.getId(), "flashcard", true);
+		final List<Content> questions1 = findBySessionIdAndVariantAndActive(sessionId, "lecture", true);
+		final List<Content> questions2 = findBySessionIdAndVariantAndActive(sessionId, "preparation", true);
+		final List<Content> questions3 = findBySessionIdAndVariantAndActive(sessionId, "flashcard", true);
 		contents.addAll(questions1);
 		contents.addAll(questions2);
 		contents.addAll(questions3);
@@ -57,217 +44,128 @@ public class CouchDbContentRepository extends CouchDbRepositorySupport<Content>
 		return contents;
 	}
 
-	@Cacheable("skillquestions")
 	@Override
-	public List<Content> getSkillQuestionsForTeachers(final Session session) {
-		return getQuestions(new Object[] {session.getId()}, session);
+	public List<Content> findBySessionIdForSpeaker(final String sessionId) {
+		return findBySessionIdAndVariantAndActive(new Object[] {sessionId}, sessionId);
 	}
 
 	@Override
-	public int getSkillQuestionCount(final Session session) {
+	public int countBySessionId(final String sessionId) {
 		final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active")
-				.startKey(ComplexKey.of(session.getId()))
-				.endKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject())));
+				.startKey(ComplexKey.of(sessionId))
+				.endKey(ComplexKey.of(sessionId, ComplexKey.emptyObject())));
 
 		return result.getSize();
 	}
 
-	@Caching(evict = {@CacheEvict(value = "skillquestions", key = "#session"),
-			@CacheEvict(value = "lecturequestions", key = "#session", condition = "#content.getQuestionVariant().equals('lecture')"),
-			@CacheEvict(value = "preparationquestions", key = "#session", condition = "#content.getQuestionVariant().equals('preparation')"),
-			@CacheEvict(value = "flashcardquestions", key = "#session", condition = "#content.getQuestionVariant().equals('flashcard')") },
-			put = {@CachePut(value = "questions", key = "#content.id")})
-	@Override
-	public Content saveQuestion(final Session session, final Content content) {
-		content.setSessionId(session.getId());
-		try {
-			db.create(content);
-
-			return content;
-		} catch (final IllegalArgumentException e) {
-			logger.error("Could not save content {}.", content, e);
-		}
-
-		return null;
-	}
-
-	/* TODO: Only evict cache entry for the content's session. This requires some refactoring. */
-	@Caching(evict = {@CacheEvict(value = "skillquestions", allEntries = true),
-			@CacheEvict(value = "lecturequestions", allEntries = true, condition = "#content.getQuestionVariant().equals('lecture')"),
-			@CacheEvict(value = "preparationquestions", allEntries = true, condition = "#content.getQuestionVariant().equals('preparation')"),
-			@CacheEvict(value = "flashcardquestions", allEntries = true, condition = "#content.getQuestionVariant().equals('flashcard')") },
-			put = {@CachePut(value = "questions", key = "#content.id")})
-	@Override
-	public Content updateQuestion(final Content content) {
-		try {
-			/* TODO: Make sure that sessionId is valid before so the content does not need to be retrieved. */
-			final Content oldContent = get(content.getId());
-			content.setId(oldContent.getId());
-			content.setRevision(oldContent.getRevision());
-			content.updateRoundManagementState();
-			update(content);
-
-			return content;
-		} catch (final UpdateConflictException e) {
-			logger.error("Could not update content {}.", content, e);
-		}
-
-		return null;
-	}
-
-	@Cacheable("questions")
-	@Override
-	public Content getQuestion(final String id) {
-		try {
-			final Content content = get(id);
-			content.updateRoundManagementState();
-			//content.setSessionKeyword(sessionRepository.getSessionFromId(content.getSessionId()).getKeyword());
-
-			return content;
-		} catch (final DocumentNotFoundException e) {
-			logger.error("Could not get question {}.", id, e);
-		}
-
-		return null;
-	}
-
 	@Override
-	public List<String> getQuestionIds(final Session session, final User user) {
-		return collectQuestionIds(db.queryView(createQuery("by_sessionid_variant_active").key(session.getId())));
+	public List<String> findIdsBySessionId(final String sessionId) {
+		return collectQuestionIds(db.queryView(createQuery("by_sessionid_variant_active")
+				.startKey(ComplexKey.of(sessionId))
+				.endKey(ComplexKey.of(sessionId, ComplexKey.emptyObject()))));
 	}
 
-	/* TODO: Only evict cache entry for the content's session. This requires some refactoring. */
-	@Caching(evict = { @CacheEvict(value = "questions", key = "#content.id"),
-			@CacheEvict(value = "skillquestions", allEntries = true),
-			@CacheEvict(value = "lecturequestions", allEntries = true, condition = "#content.getQuestionVariant().equals('lecture')"),
-			@CacheEvict(value = "preparationquestions", allEntries = true, condition = "#content.getQuestionVariant().equals('preparation')"),
-			@CacheEvict(value = "flashcardquestions", allEntries = true, condition = "#content.getQuestionVariant().equals('flashcard')") })
 	@Override
-	public int deleteQuestionWithAnswers(final Content content) {
-		try {
-			int count = answerRepository.deleteAnswers(content);
-			db.delete(content);
-			dbLogger.log("delete", "type", "content", "answerCount", count);
-
-			return count;
-		} catch (final IllegalArgumentException e) {
-			logger.error("Could not delete content {}.", content.getId(), e);
-		}
-
-		return 0;
+	public List<String> findIdsBySessionIdAndVariant(final String sessionId, final String variant) {
+		return collectQuestionIds(db.queryView(createQuery("by_sessionid_variant_active")
+				.startKey(ComplexKey.of(sessionId, variant))
+				.endKey(ComplexKey.of(sessionId, variant, ComplexKey.emptyObject()))));
 	}
 
-	@Caching(evict = { @CacheEvict(value = "questions", allEntries = true),
-			@CacheEvict(value = "skillquestions", key = "#session"),
-			@CacheEvict(value = "lecturequestions", key = "#session"),
-			@CacheEvict(value = "preparationquestions", key = "#session"),
-			@CacheEvict(value = "flashcardquestions", key = "#session") })
 	@Override
-	public int[] deleteAllQuestionsWithAnswers(final Session session) {
+	public int deleteBySessionId(final String sessionId) {
 		final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active")
-				.startKey(ComplexKey.of(session.getId()))
-				.endKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject()))
+				.startKey(ComplexKey.of(sessionId))
+				.endKey(ComplexKey.of(sessionId, ComplexKey.emptyObject()))
 				.reduce(false));
 
-		return deleteAllQuestionDocumentsWithAnswers(result);
-	}
-
-	private int[] deleteAllQuestionDocumentsWithAnswers(final ViewResult viewResult) {
-		List<Content> contents = new ArrayList<>();
-		for (final ViewResult.Row row : viewResult.getRows()) {
-			final Content q = new Content();
-			q.setId(row.getId());
-			q.setRevision(row.getValueAsNode().get("_rev").asText());
-			contents.add(q);
+		final List<BulkDeleteDocument> deleteDocs = new ArrayList<>();
+		for (final ViewResult.Row a : result.getRows()) {
+			final BulkDeleteDocument d = new BulkDeleteDocument(a.getId(), a.getValueAsNode().get("_rev").asText());
+			deleteDocs.add(d);
 		}
+		List<DocumentOperationResult> errors = db.executeBulk(deleteDocs);
 
-		int[] count = answerRepository.deleteAllAnswersWithQuestions(contents);
-		dbLogger.log("delete", "type", "question", "questionCount", count[0]);
-		dbLogger.log("delete", "type", "answer", "answerCount", count[1]);
-
-		return count;
+		return deleteDocs.size() - errors.size();
 	}
 
 	@Override
-	public List<String> getUnAnsweredQuestionIds(final Session session, final User user) {
+	public List<String> findUnansweredIdsBySessionIdAndUser(final String sessionId, final User user) {
 		final ViewResult result = db.queryView(createQuery("questionid_by_user_sessionid_variant")
 				.designDocId("_design/Answer")
-				.startKey(ComplexKey.of(user.getUsername(), session.getId()))
-				.endKey(ComplexKey.of(user.getUsername(), session.getId(), ComplexKey.emptyObject())));
-		List<String> answeredIds = new ArrayList<>();
-		for (ViewResult.Row row : result.getRows()) {
+				.startKey(ComplexKey.of(user.getUsername(), sessionId))
+				.endKey(ComplexKey.of(user.getUsername(), sessionId, ComplexKey.emptyObject())));
+		final List<String> answeredIds = new ArrayList<>();
+		for (final ViewResult.Row row : result.getRows()) {
 			answeredIds.add(row.getId());
 		}
-		return collectUnansweredQuestionIds(getQuestionIds(session, user), answeredIds);
+		return collectUnansweredQuestionIds(findIdsBySessionId(sessionId), answeredIds);
 	}
 
 	@Override
-	public List<String> getUnAnsweredLectureQuestionIds(final Session session, final User user) {
+	public List<String> findUnansweredIdsBySessionIdAndUserOnlyLectureVariant(final String sessionId, final User user) {
 		final ViewResult result = db.queryView(createQuery("questionid_piround_by_user_sessionid_variant")
 				.designDocId("_design/Answer")
-				.key(ComplexKey.of(user.getUsername(), session.getId(), "lecture")));
-		Map<String, Integer> answeredQuestions = new HashMap<>();
-		for (ViewResult.Row row : result.getRows()) {
+				.key(ComplexKey.of(user.getUsername(), sessionId, "lecture")));
+		final Map<String, Integer> answeredQuestions = new HashMap<>();
+		for (final ViewResult.Row row : result.getRows()) {
 			answeredQuestions.put(row.getId(), row.getKeyAsNode().get(2).asInt());
 		}
 
-		return collectUnansweredQuestionIdsByPiRound(getLectureQuestionsForUsers(session), answeredQuestions);
+		return collectUnansweredQuestionIdsByPiRound(findBySessionIdOnlyLectureVariantAndActive(sessionId), answeredQuestions);
 	}
 
 	@Override
-	public List<String> getUnAnsweredPreparationQuestionIds(final Session session, final User user) {
+	public List<String> findUnansweredIdsBySessionIdAndUserOnlyPreparationVariant(final String sessionId, final User user) {
 		final ViewResult result = db.queryView(createQuery("questionid_piround_by_user_sessionid_variant")
 				.designDocId("_design/Answer")
-				.key(ComplexKey.of(user.getUsername(), session.getId(), "preparation")));
-		Map<String, Integer> answeredQuestions = new HashMap<>();
-		for (ViewResult.Row row : result.getRows()) {
+				.key(ComplexKey.of(user.getUsername(), sessionId, "preparation")));
+		final Map<String, Integer> answeredQuestions = new HashMap<>();
+		for (final ViewResult.Row row : result.getRows()) {
 			answeredQuestions.put(row.getId(), row.getKeyAsNode().get(2).asInt());
 		}
 
-		return collectUnansweredQuestionIdsByPiRound(getPreparationQuestionsForUsers(session), answeredQuestions);
+		return collectUnansweredQuestionIdsByPiRound(findBySessionIdOnlyPreparationVariantAndActive(sessionId), answeredQuestions);
 	}
 
-	@Cacheable("lecturequestions")
 	@Override
-	public List<Content> getLectureQuestionsForUsers(final Session session) {
-		return getQuestions(session.getId(), "lecture", true);
+	public List<Content> findBySessionIdOnlyLectureVariantAndActive(final String sessionId) {
+		return findBySessionIdAndVariantAndActive(sessionId, "lecture", true);
 	}
 
 	@Override
-	public List<Content> getLectureQuestionsForTeachers(final Session session) {
-		return getQuestions(session.getId(), "lecture");
+	public List<Content> findBySessionIdOnlyLectureVariant(final String sessionId) {
+		return findBySessionIdAndVariantAndActive(sessionId, "lecture");
 	}
 
-	@Cacheable("flashcardquestions")
 	@Override
-	public List<Content> getFlashcardsForUsers(final Session session) {
-		return getQuestions(session.getId(), "flashcard", true);
+	public List<Content> findBySessionIdOnlyFlashcardVariantAndActive(final String sessionId) {
+		return findBySessionIdAndVariantAndActive(sessionId, "flashcard", true);
 	}
 
 	@Override
-	public List<Content> getFlashcardsForTeachers(final Session session) {
-		return getQuestions(session.getId(), "flashcard");
+	public List<Content> findBySessionIdOnlyFlashcardVariant(final String sessionId) {
+		return findBySessionIdAndVariantAndActive(sessionId, "flashcard");
 	}
 
-	@Cacheable("preparationquestions")
 	@Override
-	public List<Content> getPreparationQuestionsForUsers(final Session session) {
-		return getQuestions(session.getId(), "preparation", true);
+	public List<Content> findBySessionIdOnlyPreparationVariantAndActive(final String sessionId) {
+		return findBySessionIdAndVariantAndActive(sessionId, "preparation", true);
 	}
 
 	@Override
-	public List<Content> getPreparationQuestionsForTeachers(final Session session) {
-		return getQuestions(session.getId(), "preparation");
+	public List<Content> findBySessionIdOnlyPreparationVariant(final String sessionId) {
+		return findBySessionIdAndVariantAndActive(sessionId, "preparation");
 	}
 
 	@Override
-	public List<Content> getAllSkillQuestions(final Session session) {
-		return getQuestions(session.getId());
+	public List<Content> findBySessionId(final String sessionId) {
+		return findBySessionIdAndVariantAndActive(sessionId);
 	}
 
 	@Override
-	public List<Content> getQuestions(final Object... keys) {
-		Object[] endKeys = Arrays.copyOf(keys, keys.length + 1);
+	public List<Content> findBySessionIdAndVariantAndActive(final Object... keys) {
+		final Object[] endKeys = Arrays.copyOf(keys, keys.length + 1);
 		endKeys[keys.length] = ComplexKey.emptyObject();
 		final List<Content> contents = db.queryView(createQuery("by_sessionid_variant_active")
 						.includeDocs(true)
@@ -275,7 +173,7 @@ public class CouchDbContentRepository extends CouchDbRepositorySupport<Content>
 						.startKey(ComplexKey.of(keys))
 						.endKey(ComplexKey.of(endKeys)),
 				Content.class);
-		for (Content content : contents) {
+		for (final Content content : contents) {
 			content.updateRoundManagementState();
 			//content.setSessionKeyword(session.getKeyword());
 		}
@@ -284,88 +182,43 @@ public class CouchDbContentRepository extends CouchDbRepositorySupport<Content>
 	}
 
 	@Override
-	public int getLectureQuestionCount(final Session session) {
+	public int countLectureVariantBySessionId(final String sessionId) {
 		/* TODO: reduce code duplication */
 		final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active")
-				.startKey(ComplexKey.of(session.getId(), "lecture"))
-				.endKey(ComplexKey.of(session.getId(), "lecture", ComplexKey.emptyObject())));
+				.startKey(ComplexKey.of(sessionId, "lecture"))
+				.endKey(ComplexKey.of(sessionId, "lecture", ComplexKey.emptyObject())));
 
 		return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt();
 	}
 
 	@Override
-	public int getFlashcardCount(final Session session) {
+	public int countFlashcardVariantBySessionId(final String sessionId) {
 		/* TODO: reduce code duplication */
 		final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active")
-				.startKey(ComplexKey.of(session.getId(), "flashcard"))
-				.endKey(ComplexKey.of(session.getId(), "flashcard", ComplexKey.emptyObject())));
+				.startKey(ComplexKey.of(sessionId, "flashcard"))
+				.endKey(ComplexKey.of(sessionId, "flashcard", ComplexKey.emptyObject())));
 
 		return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt();
 	}
 
 	@Override
-	public int getPreparationQuestionCount(final Session session) {
+	public int countPreparationVariantBySessionId(final String sessionId) {
 		/* TODO: reduce code duplication */
 		final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active")
-				.startKey(ComplexKey.of(session.getId(), "preparation"))
-				.endKey(ComplexKey.of(session.getId(), "preparation", ComplexKey.emptyObject())));
+				.startKey(ComplexKey.of(sessionId, "preparation"))
+				.endKey(ComplexKey.of(sessionId, "preparation", ComplexKey.emptyObject())));
 
 		return result.isEmpty() ? 0 : result.getRows().get(0).getValueAsInt();
 	}
 
-	/* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */
-	@Caching(evict = { @CacheEvict(value = "questions", allEntries = true),
-			@CacheEvict("skillquestions"),
-			@CacheEvict("lecturequestions"),
-			@CacheEvict(value = "answers", allEntries = true)})
-	@Override
-	public int[] deleteAllLectureQuestionsWithAnswers(final Session session) {
-		final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active")
-				.startKey(ComplexKey.of(session.getId(), "lecture"))
-				.endKey(ComplexKey.of(session.getId(), "lecture", ComplexKey.emptyObject()))
-				.reduce(false));
-
-		return deleteAllQuestionDocumentsWithAnswers(result);
-	}
-
-	/* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */
-	@Caching(evict = { @CacheEvict(value = "questions", allEntries = true),
-			@CacheEvict("skillquestions"),
-			@CacheEvict("flashcardquestions"),
-			@CacheEvict(value = "answers", allEntries = true)})
-	@Override
-	public int[] deleteAllFlashcardsWithAnswers(final Session session) {
-		final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active")
-				.startKey(ComplexKey.of(session.getId(), "flashcard"))
-				.endKey(ComplexKey.of(session.getId(), "flashcard", ComplexKey.emptyObject()))
-				.reduce(false));
-
-		return deleteAllQuestionDocumentsWithAnswers(result);
-	}
-
-	/* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */
-	@Caching(evict = { @CacheEvict(value = "questions", allEntries = true),
-			@CacheEvict("skillquestions"),
-			@CacheEvict("preparationquestions"),
-			@CacheEvict(value = "answers", allEntries = true)})
-	@Override
-	public int[] deleteAllPreparationQuestionsWithAnswers(final Session session) {
-		final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active")
-				.startKey(ComplexKey.of(session.getId(), "preparation"))
-				.endKey(ComplexKey.of(session.getId(), "preparation", ComplexKey.emptyObject()))
-				.reduce(false));
-
-		return deleteAllQuestionDocumentsWithAnswers(result);
-	}
-
 	private List<String> collectUnansweredQuestionIds(
-			final List<String> questions,
-			final List<String> answeredQuestions
+			final List<String> contentIds,
+			final List<String> answeredContentIds
 	) {
 		final List<String> unanswered = new ArrayList<>();
-		for (final String questionId : questions) {
-			if (!answeredQuestions.contains(questionId)) {
-				unanswered.add(questionId);
+		for (final String contentId : contentIds) {
+			if (!answeredContentIds.contains(contentId)) {
+				unanswered.add(contentId);
 			}
 		}
 		return unanswered;
@@ -395,76 +248,14 @@ public class CouchDbContentRepository extends CouchDbRepositorySupport<Content>
 		return ids;
 	}
 
-	@Override
-	public List<Content> publishAllQuestions(final Session session, final boolean publish) {
-		final List<Content> contents = db.queryView(createQuery("by_sessionid_variant_active")
-						.startKey(ComplexKey.of(session.getId()))
-						.endKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject())),
-				Content.class);
-		/* FIXME: caching */
-		publishQuestions(session, publish, contents);
-
-		return contents;
-	}
-
-	@Caching(evict = { @CacheEvict(value = "contents", allEntries = true),
-			@CacheEvict(value = "skillquestions", key = "#session"),
-			@CacheEvict(value = "lecturequestions", key = "#session"),
-			@CacheEvict(value = "preparationquestions", key = "#session"),
-			@CacheEvict(value = "flashcardquestions", key = "#session") })
-	@Override
-	public void publishQuestions(final Session session, final boolean publish, List<Content> contents) {
-		for (final Content content : contents) {
-			content.setActive(publish);
-		}
-		try {
-			db.executeBulk(contents);
-		} catch (final DbAccessException e) {
-			logger.error("Could not bulk publish all contents.", e);
-		}
-	}
-
-	@Override
-	public List<Content> setVotingAdmissionForAllQuestions(final Session session, final boolean disableVoting) {
-		final List<Content> contents = db.queryView(createQuery("by_sessionid_variant_active")
-						.startKey(ComplexKey.of(session.getId()))
-						.endKey(ComplexKey.of(session.getId(), ComplexKey.emptyObject()))
-						.includeDocs(true),
-				Content.class);
-		/* FIXME: caching */
-		setVotingAdmissions(session, disableVoting, contents);
-
-		return contents;
-	}
-
-	@Caching(evict = { @CacheEvict(value = "contents", allEntries = true),
-			@CacheEvict(value = "skillquestions", key = "#session"),
-			@CacheEvict(value = "lecturequestions", key = "#session"),
-			@CacheEvict(value = "preparationquestions", key = "#session"),
-			@CacheEvict(value = "flashcardquestions", key = "#session") })
-	@Override
-	public void setVotingAdmissions(final Session session, final boolean disableVoting, List<Content> contents) {
-		for (final Content q : contents) {
-			if (!"flashcard".equals(q.getQuestionType())) {
-				q.setVotingDisabled(disableVoting);
-			}
-		}
-
-		try {
-			db.executeBulk(contents);
-		} catch (final DbAccessException e) {
-			logger.error("Could not bulk set voting admission for all contents.", e);
-		}
-	}
-
 	/* TODO: remove if this method is no longer used */
 	@Override
-	public List<String> getQuestionIdsBySubject(Session session, String questionVariant, String subject) {
+	public List<String> findIdsBySessionIdAndVariantAndSubject(final String sessionId, final String questionVariant, final String subject) {
 		final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active")
-				.startKey(ComplexKey.of(session.getId(), questionVariant, 1, subject))
-				.endKey(ComplexKey.of(session.getId(), questionVariant, 1, subject, ComplexKey.emptyObject())));
+				.startKey(ComplexKey.of(sessionId, questionVariant, 1, subject))
+				.endKey(ComplexKey.of(sessionId, questionVariant, 1, subject, ComplexKey.emptyObject())));
 
-		List<String> qids = new ArrayList<>();
+		final List<String> qids = new ArrayList<>();
 
 		for (final ViewResult.Row row : result.getRows()) {
 			final String s = row.getId();
@@ -475,17 +266,12 @@ public class CouchDbContentRepository extends CouchDbRepositorySupport<Content>
 	}
 
 	@Override
-	public List<Content> getQuestionsByIds(List<String> ids, final Session session) {
-		return db.queryView(new ViewQuery().allDocs().keys(ids).includeDocs(true), Content.class);
-	}
-
-	@Override
-	public List<String> getSubjects(Session session, String questionVariant) {
+	public List<String> findSubjectsBySessionIdAndVariant(final String sessionId, final String questionVariant) {
 		final ViewResult result = db.queryView(createQuery("by_sessionid_variant_active")
-				.startKey(ComplexKey.of(session.getId(), questionVariant))
-				.endKey(ComplexKey.of(session.getId(), questionVariant, ComplexKey.emptyObject())));
+				.startKey(ComplexKey.of(sessionId, questionVariant))
+				.endKey(ComplexKey.of(sessionId, questionVariant, ComplexKey.emptyObject())));
 
-		Set<String> uniqueSubjects = new HashSet<>();
+		final Set<String> uniqueSubjects = new HashSet<>();
 
 		for (final ViewResult.Row row : result.getRows()) {
 			uniqueSubjects.add(row.getKeyAsNode().get(3).asText());
@@ -493,22 +279,4 @@ public class CouchDbContentRepository extends CouchDbRepositorySupport<Content>
 
 		return new ArrayList<>(uniqueSubjects);
 	}
-
-	@Caching(evict = { @CacheEvict(value = "contents", allEntries = true),
-			@CacheEvict(value = "skillquestions", key = "#session"),
-			@CacheEvict(value = "lecturequestions", key = "#session"),
-			@CacheEvict(value = "preparationquestions", key = "#session"),
-			@CacheEvict(value = "flashcardquestions", key = "#session") })
-	@Override
-	public void resetQuestionsRoundState(final Session session, List<Content> contents) {
-		for (final Content q : contents) {
-			q.setSessionId(session.getId());
-			q.resetQuestionState();
-		}
-		try {
-			db.executeBulk(contents);
-		} catch (final DbAccessException e) {
-			logger.error("Could not bulk reset all contents round state.", e);
-		}
-	}
 }
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCrudRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCrudRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..7166c93da28b4a023539a8866ce4ba787725e67b
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCrudRepository.java
@@ -0,0 +1,122 @@
+package de.thm.arsnova.persistance.couchdb;
+
+import de.thm.arsnova.entities.Entity;
+import org.ektorp.BulkDeleteDocument;
+import org.ektorp.CouchDbConnector;
+import org.ektorp.support.CouchDbRepositorySupport;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.NoRepositoryBean;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@NoRepositoryBean
+abstract class CouchDbCrudRepository<T extends Entity> extends CouchDbRepositorySupport<T> implements CrudRepository<T, String> {
+	private String countableAllViewName;
+
+	protected CouchDbCrudRepository(
+			final Class<T> type,
+			final CouchDbConnector db,
+			final String designDocName,
+			final String countableAllViewName,
+			final boolean createIfNotExists) {
+		super(type, db, designDocName, createIfNotExists);
+		this.countableAllViewName = countableAllViewName;
+	}
+
+	protected CouchDbCrudRepository(
+			final Class<T> type,
+			final CouchDbConnector db,
+			final String countableAllViewName,
+			final boolean createIfNotExists) {
+		super(type, db, createIfNotExists);
+		this.countableAllViewName = countableAllViewName;
+	}
+
+	protected String getCountableAllViewName() {
+		return countableAllViewName;
+	}
+
+	@Override
+	public <S extends T> S save(final S entity) {
+		final String id = entity.getId();
+		if (id != null) {
+			db.update(entity);
+		} else {
+			db.create(entity);
+		}
+
+		return entity;
+	}
+
+	@Override
+	public <S extends T> Iterable<S> save(final Iterable<S> entities) {
+		if (!(entities instanceof Collection)) {
+			throw new IllegalArgumentException("Implementation only supports Collections.");
+		}
+		db.executeBulk((Collection<S>) entities);
+
+		return entities;
+	}
+
+	@Override
+	public T findOne(final String id) {
+		return get(id);
+	}
+
+	@Override
+	public boolean exists(final String id) {
+		return contains(id);
+	}
+
+	@Override
+	public Iterable<T> findAll() {
+		return db.queryView(createQuery(countableAllViewName).includeDocs(true), type);
+	}
+
+	@Override
+	public Iterable<T> findAll(final Iterable<String> strings) {
+		if (!(strings instanceof Collection)) {
+			throw new IllegalArgumentException("Implementation only supports Collections.");
+		}
+
+		return db.queryView(createQuery(countableAllViewName)
+						.keys((Collection<String>) strings)
+						.includeDocs(true),
+				type);
+	}
+
+	@Override
+	public long count() {
+		return db.queryView(createQuery(countableAllViewName).reduce(true)).getRows().get(0).getValueAsInt();
+	}
+
+	@Override
+	public void delete(final String id) {
+		T entity = get(id);
+		db.delete(id, entity.getRevision());
+	}
+
+	@Override
+	public void delete(final T entity) {
+		db.delete(entity);
+	}
+
+	@Override
+	public void delete(final Iterable<? extends T> entities) {
+		if (!(entities instanceof Collection)) {
+			throw new IllegalArgumentException("Implementation only supports Collections.");
+		}
+
+		final List<BulkDeleteDocument> docs = ((Collection<? extends T>) entities).stream()
+				.map(entity -> new BulkDeleteDocument(entity.getId(), entity.getRevision()))
+				.collect(Collectors.toList());
+		db.executeBulk(docs);
+	}
+
+	@Override
+	public void deleteAll() {
+		throw new UnsupportedOperationException("Deletion of all entities is not supported for security reasons.");
+	}
+}
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbLogEntryRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbLogEntryRepository.java
index 0ceaa3bf08780e6133eebd0c58ad2e94e1896b97..ff7fc21a5cf1a05d1c2c898f19b135bd0664720a 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbLogEntryRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbLogEntryRepository.java
@@ -29,13 +29,13 @@ import java.util.Map;
 public class CouchDbLogEntryRepository extends CouchDbRepositorySupport<LogEntry> implements LogEntryRepository {
 	private static final Logger logger = LoggerFactory.getLogger(CouchDbLogEntryRepository.class);
 
-	public CouchDbLogEntryRepository(CouchDbConnector db, boolean createIfNotExists) {
+	public CouchDbLogEntryRepository(final CouchDbConnector db, final boolean createIfNotExists) {
 		super(LogEntry.class, db, createIfNotExists);
 	}
 
 	@Override
-	public void create(String event, LogEntry.LogLevel level, Map<String, Object> payload) {
-		LogEntry log = new LogEntry(event, level.ordinal(), payload);
+	public void create(final String event, final LogEntry.LogLevel level, final Map<String, Object> payload) {
+		final LogEntry log = new LogEntry(event, level.ordinal(), payload);
 		try {
 			db.create(log);
 		} catch (final IllegalArgumentException e) {
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdListRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdListRepository.java
index 4260a6668e161303400aefac9728b87aef0cb5aa..499013a874871e16905241e0a9a296f3be4145c0 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdListRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdListRepository.java
@@ -7,28 +7,25 @@ import org.ektorp.DbAccessException;
 import org.ektorp.support.CouchDbRepositorySupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.cache.annotation.CachePut;
-import org.springframework.cache.annotation.Cacheable;
 
 import java.util.List;
 
 public class CouchDbMotdListRepository extends CouchDbRepositorySupport<MotdList> implements MotdListRepository {
 	private static final Logger logger = LoggerFactory.getLogger(CouchDbMotdListRepository.class);
 
-	public CouchDbMotdListRepository(CouchDbConnector db, boolean createIfNotExists) {
+	public CouchDbMotdListRepository(final CouchDbConnector db, final boolean createIfNotExists) {
 		super(MotdList.class, db, createIfNotExists);
 	}
 
 	@Override
-	@Cacheable(cacheNames = "motdlist", key = "#p0")
-	public MotdList getMotdListForUser(final String username) {
-		List<MotdList> motdListList = queryView("by_username", username);
+	public MotdList findByUsername(final String username) {
+		final List<MotdList> motdListList = queryView("by_username", username);
 		return motdListList.isEmpty() ? new MotdList() : motdListList.get(0);
 	}
 
+	/* TODO: Move to service layer. */
 	@Override
-	@CachePut(cacheNames = "motdlist", key = "#p0.username")
-	public MotdList createOrUpdateMotdList(MotdList motdlist) {
+	public MotdList save(final MotdList motdlist) {
 		try {
 			if (motdlist.getId() != null) {
 				update(motdlist);
@@ -37,7 +34,7 @@ public class CouchDbMotdListRepository extends CouchDbRepositorySupport<MotdList
 			}
 
 			return motdlist;
-		} catch (DbAccessException e) {
+		} catch (final DbAccessException e) {
 			logger.error("Could not save MotD list {}.", motdlist, e);
 		}
 
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 b0ee9bf42706d0e06edc596c771f3d885d85ba76..7a00e2886c13b355485976f198c330d89acea80c 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java
@@ -19,103 +19,66 @@ package de.thm.arsnova.persistance.couchdb;
 
 import de.thm.arsnova.entities.Motd;
 import de.thm.arsnova.persistance.MotdRepository;
-import de.thm.arsnova.services.ISessionService;
 import org.ektorp.CouchDbConnector;
-import org.ektorp.support.CouchDbRepositorySupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.cache.annotation.Cacheable;
 
 import java.util.ArrayList;
 import java.util.List;
 
-public class CouchDbMotdRepository extends CouchDbRepositorySupport<Motd> implements MotdRepository {
+public class CouchDbMotdRepository extends CouchDbCrudRepository<Motd> implements MotdRepository {
 	private static final Logger logger = LoggerFactory.getLogger(CouchDbMotdRepository.class);
 
-	@Autowired
-	private ISessionService sessionService;
-
-	public CouchDbMotdRepository(CouchDbConnector db, boolean createIfNotExists) {
-		super(Motd.class, db, createIfNotExists);
+	public CouchDbMotdRepository(final CouchDbConnector db, final boolean createIfNotExists) {
+		super(Motd.class, db, "by_sessionkey", createIfNotExists);
 	}
 
 	@Override
-	public List<Motd> getAdminMotds() {
-		return getMotds("by_audience_for_global", null);
+	public List<Motd> findGlobalForAdmin() {
+		return find("by_audience_for_global", null);
 	}
 
 	@Override
-	@Cacheable(cacheNames = "motds", key = "'all'")
-	public List<Motd> getMotdsForAll() {
-		return getMotds("by_audience_for_global", "all");
+	public List<Motd> findGlobalForAll() {
+		return find("by_audience_for_global", "all");
 	}
 
 	@Override
-	@Cacheable(cacheNames = "motds", key = "'loggedIn'")
-	public List<Motd> getMotdsForLoggedIn() {
-		return getMotds("by_audience_for_global", "loggedIn");
+	public List<Motd> findGlobalForLoggedIn() {
+		return find("by_audience_for_global", "loggedIn");
 	}
 
 	@Override
-	@Cacheable(cacheNames = "motds", key = "'tutors'")
-	public List<Motd> getMotdsForTutors() {
+	public List<Motd> findGlobalForTutors() {
 		final List<Motd> union = new ArrayList<>();
-		union.addAll(getMotds("by_audience_for_global", "loggedIn"));
-		union.addAll(getMotds("by_audience_for_global", "tutors"));
+		union.addAll(find("by_audience_for_global", "loggedIn"));
+		union.addAll(find("by_audience_for_global", "tutors"));
 
 		return union;
 	}
 
 	@Override
-	@Cacheable(cacheNames = "motds", key = "'students'")
-	public List<Motd> getMotdsForStudents() {
+	public List<Motd> findForStudents() {
 		final List<Motd> union = new ArrayList<>();
-		union.addAll(getMotds("by_audience_for_global", "loggedIn"));
-		union.addAll(getMotds("by_audience_for_global", "students"));
+		union.addAll(find("by_audience_for_global", "loggedIn"));
+		union.addAll(find("by_audience_for_global", "students"));
 
 		return union;
 	}
 
 	@Override
-	@Cacheable(cacheNames = "motds", key = "('session').concat(#p0)")
-	public List<Motd> getMotdsForSession(final String sessionkey) {
-		return getMotds("by_sessionkey", sessionkey);
+	public List<Motd> findBySessionKey(final String sessionkey) {
+		return find("by_sessionkey", sessionkey);
 	}
 
-	private List<Motd> getMotds(String viewName, String key) {
+	private List<Motd> find(final String viewName, final String key) {
 		return queryView(viewName, key);
 	}
 
 	@Override
-	public Motd getMotdByKey(String key) {
-		List<Motd> motd = queryView("by_motdkey", key);
+	public Motd findByKey(final String key) {
+		final List<Motd> motd = queryView("by_motdkey", key);
 
 		return motd.get(0);
 	}
-
-	@Override
-	@CacheEvict(cacheNames = "motds", key = "#p0.audience.concat(#p0.sessionkey)")
-	public Motd createOrUpdateMotd(Motd motd) {
-		String id = motd.getId();
-		String rev = motd.getRevision();
-
-		if (null != id) {
-			Motd oldMotd = get(id);
-			motd.setMotdkey(oldMotd.getMotdkey());
-			update(motd);
-		} else {
-			motd.setMotdkey(sessionService.generateKeyword());
-			add(motd);
-		}
-
-		return motd;
-	}
-
-	@Override
-	@CacheEvict(cacheNames = "motds", key = "#p0.audience.concat(#p0.sessionkey)")
-	public boolean deleteMotd(Motd motd) {
-		return db.delete(motd) != null;
-	}
 }
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java
index 2731d70a0f7b9fc2b88a02cac1cc0b9a691a82af..724755064b1cf9f90af963ce8559949956fc6994 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java
@@ -18,32 +18,27 @@
 package de.thm.arsnova.persistance.couchdb;
 
 import de.thm.arsnova.connector.model.Course;
+import de.thm.arsnova.entities.Comment;
 import de.thm.arsnova.entities.LoggedIn;
 import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.SessionInfo;
 import de.thm.arsnova.entities.User;
 import de.thm.arsnova.entities.VisitedSession;
-import de.thm.arsnova.entities.transport.Comment;
 import de.thm.arsnova.entities.transport.ImportExportSession;
 import de.thm.arsnova.exceptions.NotFoundException;
 import de.thm.arsnova.persistance.LogEntryRepository;
 import de.thm.arsnova.persistance.MotdRepository;
 import de.thm.arsnova.persistance.SessionRepository;
-import de.thm.arsnova.services.ISessionService;
 import org.ektorp.ComplexKey;
 import org.ektorp.CouchDbConnector;
 import org.ektorp.DocumentNotFoundException;
 import org.ektorp.UpdateConflictException;
 import org.ektorp.ViewQuery;
 import org.ektorp.ViewResult;
-import org.ektorp.support.CouchDbRepositorySupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.cache.annotation.CachePut;
 import org.springframework.cache.annotation.Cacheable;
-import org.springframework.cache.annotation.Caching;
 
 import java.io.IOException;
 import java.util.AbstractMap;
@@ -55,58 +50,28 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session> implements SessionRepository {
+public class CouchDbSessionRepository extends CouchDbCrudRepository<Session> implements SessionRepository {
 	private static final Logger logger = LoggerFactory.getLogger(CouchDbSessionRepository.class);
 
-	@Autowired
-	private ISessionService sessionService;
-
 	@Autowired
 	private LogEntryRepository dbLogger;
 
 	@Autowired
 	private MotdRepository motdRepository;
 
-	public CouchDbSessionRepository(CouchDbConnector db, boolean createIfNotExists) {
-		super(Session.class, db, createIfNotExists);
+	public CouchDbSessionRepository(final CouchDbConnector db, final boolean createIfNotExists) {
+		super(Session.class, db, "by_keyword", createIfNotExists);
 	}
 
 	@Override
 	@Cacheable("sessions")
-	public Session getSessionFromKeyword(final String keyword) {
+	public Session findByKeyword(final String keyword) {
 		final List<Session> session = queryView("by_keyword", keyword);
 
 		return !session.isEmpty() ? session.get(0) : null;
 	}
 
-	@Override
-	@Cacheable("sessions")
-	public Session getSessionFromId(final String sessionId) {
-		return get(sessionId);
-	}
-
-	@Override
-	@Caching(evict = @CacheEvict(cacheNames = "sessions", key = "#result.keyword"))
-	public Session saveSession(final User user, final Session session) {
-		session.setKeyword(sessionService.generateKeyword());
-		session.setCreator(user.getUsername());
-		session.setActive(true);
-		session.setFeedbackLock(false);
-
-		try {
-			db.create(session);
-		} catch (final IllegalArgumentException e) {
-			logger.error("Could not save session to database.", e);
-		}
-
-		return session.getId() != null ? session : null;
-	}
-
-	@Override
-	public boolean sessionKeyAvailable(final String keyword) {
-		return getSessionFromKeyword(keyword) == null;
-	}
-
+	/* TODO: Move to service layer. */
 	private String getSessionKeyword(final String internalSessionId) throws IOException {
 		final Session session = get(internalSessionId);
 		if (session == null) {
@@ -119,33 +84,14 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 	}
 
 	@Override
-	@CachePut(value = "sessions")
-	public Session updateSessionOwnerActivity(final Session session) {
-		try {
-			/* Do not clutter CouchDB. Only update once every 3 hours. */
-			if (session.getLastOwnerActivity() > System.currentTimeMillis() - 3 * 3600000) {
-				return session;
-			}
-
-			session.setLastOwnerActivity(System.currentTimeMillis());
-			update(session);
-
-			return session;
-		} catch (final UpdateConflictException e) {
-			logger.error("Failed to update lastOwnerActivity for session {}.", session, e);
-			return session;
-		}
-	}
-
-	@Override
-	public List<Session> getVisitedSessionsForUsername(String username, final int start, final int limit) {
+	public List<Session> findVisitedByUsername(final String username, final int start, final int limit) {
 		final int qSkip = start > 0 ? start : -1;
 		final int qLimit = limit > 0 ? limit : -1;
 
 		try {
-			ViewResult visitedSessionResult = db.queryView(createQuery("visited_sessions_by_user")
+			final ViewResult visitedSessionResult = db.queryView(createQuery("visited_sessions_by_user")
 					.designDocId("_design/LoggedIn").key(username));
-			List<Session> visitedSessions = visitedSessionResult.getRows().stream().map(vs -> {
+			final List<Session> visitedSessions = visitedSessionResult.getRows().stream().map(vs -> {
 				final Session s = new Session();
 				s.setId(vs.getValueAsNode().get("_id").asText());
 				s.setKeyword(vs.getValueAsNode().get("keyword").asText());
@@ -164,7 +110,7 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 			for (final Session s : visitedSessions) {
 				try {
 					/* FIXME: caching (getSessionFromKeyword) */
-					final Session session = getSessionFromKeyword(s.getKeyword());
+					final Session session = findByKeyword(s.getKeyword());
 					if (session != null && !(session.getCreator().equals(username))) {
 						result.add(session);
 					} else {
@@ -178,10 +124,10 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 				return result;
 			}
 			// Update document to remove sessions that don't exist anymore
-				List<VisitedSession> newVisitedSessions = new ArrayList<>();
-				for (final Session s : result) {
-					newVisitedSessions.add(new VisitedSession(s));
-				}
+			final List<VisitedSession> newVisitedSessions = new ArrayList<>();
+			for (final Session s : result) {
+				newVisitedSessions.add(new VisitedSession(s));
+			}
 
 			try {
 				final LoggedIn loggedIn = db.get(LoggedIn.class, visitedSessionResult.getRows().get(0).getId());
@@ -192,14 +138,14 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 			}
 
 			return result;
-		} catch (DocumentNotFoundException e) {
+		} catch (final DocumentNotFoundException e) {
 			return new ArrayList<>();
 		}
 	}
 
 	@Override
-	public List<SessionInfo> getMyVisitedSessionsInfo(final User user, final int start, final int limit) {
-		List<Session> sessions = getVisitedSessionsForUsername(user.getUsername(), start, limit);
+	public List<SessionInfo> findInfoForVisitedByUser(final User user, final int start, final int limit) {
+		final List<Session> sessions = findVisitedByUsername(user.getUsername(), start, limit);
 		if (sessions.isEmpty()) {
 			return new ArrayList<>();
 		}
@@ -207,92 +153,39 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 	}
 
 	@Override
-	public List<Session> getCourseSessions(final List<Course> courses) {
+	public List<Session> findSessionsByCourses(final List<Course> courses) {
 		return queryView("by_courseid",
 				ComplexKey.of(courses.stream().map(Course::getId).collect(Collectors.toList())));
 	}
 
 	@Override
-	@CachePut(value = "sessions")
-	public Session updateSession(final Session session) {
-		try {
-			update(session);
-
-			return session;
-		} catch (final UpdateConflictException e) {
-			logger.error("Could not update session {}.", session, e);
-		}
-
-		return null;
-	}
-
-	@Override
-	@Caching(evict = { @CacheEvict("sessions"), @CacheEvict(cacheNames = "sessions", key = "#p0.keyword") })
-	public Session changeSessionCreator(final Session session, final String newCreator) {
-		Session s = get(session.getId());
-		s.setCreator(newCreator);
-		try {
-			update(s);
-		} catch (final UpdateConflictException e) {
-			logger.error("Could not update creator for session {}.", session, e);
-		}
-
-		return s;
-	}
-
-	@Override
-	@Caching(evict = { @CacheEvict("sessions"), @CacheEvict(cacheNames = "sessions", key = "#p0.keyword") })
-	public int[] deleteSession(final Session session) {
-		/* FIXME: not yet migrated - move to service layer */
-		throw new UnsupportedOperationException();
-//		int[] count = new int[] {0, 0};
-//		try {
-//			count = deleteAllQuestionsWithAnswers(session);
-//			remove(session);
-//			logger.debug("Deleted session document {} and related data.", session.getId());
-//			dbLogger.log("delete", "type", "session", "id", session.getId());
-//		} catch (final Exception e) {
-//			/* TODO: improve error handling */
-//			logger.error("Could not delete session {}.", session, e);
-//		}
-//
-//		return count;
-	}
-
-	@Override
-	public int[] deleteInactiveGuestSessions(long lastActivityBefore) {
-		ViewResult result = db.queryView(
+	public List<Session> findInactiveGuestSessionsMetadata(final long lastActivityBefore) {
+		final ViewResult result = db.queryView(
 				createQuery("by_lastactivity_for_guests").endKey(lastActivityBefore));
-		int[] count = new int[3];
+		final int[] count = new int[3];
 
-		for (ViewResult.Row row : result.getRows()) {
-			Session s = new Session();
+		List<Session> sessions = new ArrayList<>();
+		for (final ViewResult.Row row : result.getRows()) {
+			final Session s = new Session();
 			s.setId(row.getId());
 			s.setRevision(row.getValueAsNode().get("_rev").asText());
-			int[] qaCount = deleteSession(s);
-			count[1] += qaCount[0];
-			count[2] += qaCount[1];
-		}
-
-		if (!result.isEmpty()) {
-			logger.info("Deleted {} inactive guest sessions.", result.getSize());
-			dbLogger.log("cleanup", "type", "session", "sessionCount", result.getSize(), "questionCount", count[1], "answerCount", count[2]);
+			sessions.add(s);
 		}
-		count[0] = result.getSize();
 
-		return count;
+		return sessions;
 	}
 
+	/* TODO: Move to service layer. */
 	@Override
-	public SessionInfo importSession(User user, ImportExportSession importSession) {
+	public SessionInfo importSession(final User user, final ImportExportSession importSession) {
 		/* FIXME: not yet migrated - move to service layer */
 		throw new UnsupportedOperationException();
 //		final Session session = this.saveSession(user, importSession.generateSessionEntity(user));
-//		List<Document> questions = new ArrayList<>();
+//		final List<Document> questions = new ArrayList<>();
 //		// We need to remember which answers belong to which question.
 //		// The answers need a questionId, so we first store the questions to get the IDs.
 //		// Then we update the answer objects and store them as well.
-//		Map<Document, ImportExportSession.ImportExportContent> mapping = new HashMap<>();
+//		final Map<Document, ImportExportSession.ImportExportContent> mapping = new HashMap<>();
 //		// Later, generate all answer documents
 //		List<Document> answers = new ArrayList<>();
 //		// We can then push answers together with comments in one large bulk request
@@ -301,8 +194,8 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 //		List<Document> motds = new ArrayList<>();
 //		try {
 //			// add session id to all questions and generate documents
-//			for (ImportExportSession.ImportExportContent question : importSession.getQuestions()) {
-//				Document doc = toQuestionDocument(session, question);
+//			for (final ImportExportSession.ImportExportContent question : importSession.getQuestions()) {
+//				final Document doc = toQuestionDocument(session, question);
 //				question.setSessionId(session.getId());
 //				questions.add(doc);
 //				mapping.put(doc, question);
@@ -315,7 +208,7 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 //				final ImportExportSession.ImportExportContent question = entry.getValue();
 //				question.setId(doc.getId());
 //				question.setRevision(doc.getRev());
-//				for (de.thm.arsnova.entities.transport.Answer answer : question.getAnswers()) {
+//				for (final de.thm.arsnova.entities.transport.Answer answer : question.getAnswers()) {
 //					final Answer a = answer.generateAnswerEntity(user, question);
 //					final Document answerDoc = new Document();
 //					answerDoc.put("type", "skill_question_answer");
@@ -335,7 +228,7 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 //					answers.add(answerDoc);
 //				}
 //			}
-//			for (de.thm.arsnova.entities.transport.Comment i : importSession.getFeedbackQuestions()) {
+//			for (final de.thm.arsnova.entities.transport.Comment i : importSession.getFeedbackQuestions()) {
 //				final Document q = new Document();
 //				q.put("type", "interposed_question");
 //				q.put("sessionId", session.getId());
@@ -347,7 +240,7 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 //				q.put("creator", "");
 //				interposedQuestions.add(q);
 //			}
-//			for (Motd m : importSession.getMotds()) {
+//			for (final Motd m : importSession.getMotds()) {
 //				final Document d = new Document();
 //				d.put("type", "motd");
 //				d.put("motdkey", m.getMotdkey());
@@ -359,11 +252,11 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 //				d.put("enddate", String.valueOf(m.getEnddate().getTime()));
 //				motds.add(d);
 //			}
-//			List<Document> documents = new ArrayList<>(answers);
+//			final List<Document> documents = new ArrayList<>(answers);
 //			database.bulkSaveDocuments(interposedQuestions.toArray(new Document[interposedQuestions.size()]));
 //			database.bulkSaveDocuments(motds.toArray(new Document[motds.size()]));
 //			database.bulkSaveDocuments(documents.toArray(new Document[documents.size()]));
-//		} catch (IOException e) {
+//		} catch (final IOException e) {
 //			logger.error("Could not import session.", e);
 //			// Something went wrong, delete this session since we do not want a partial import
 //			this.deleteSession(session);
@@ -372,25 +265,29 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 //		return this.calculateSessionInfo(importSession, session);
 	}
 
+	/* TODO: Move to service layer. */
 	@Override
-	public ImportExportSession exportSession(String sessionkey, Boolean withAnswers, Boolean withFeedbackQuestions) {
+	public ImportExportSession exportSession(
+			final String sessionkey,
+			final Boolean withAnswers,
+			final Boolean withFeedbackQuestions) {
 		/* FIXME: not yet migrated - move to service layer */
 		throw new UnsupportedOperationException();
-//		ImportExportSession importExportSession = new ImportExportSession();
-//		Session session = getDatabaseDao().getSessionFromKeyword(sessionkey);
+//		final ImportExportSession importExportSession = new ImportExportSession();
+//		final Session session = getDatabaseDao().getSessionFromKeyword(sessionkey);
 //		importExportSession.setSessionFromSessionObject(session);
-//		List<Content> questionList = getDatabaseDao().getAllSkillQuestions(session);
-//		for (Content question : questionList) {
-//			List<de.thm.arsnova.entities.transport.Answer> answerList = new ArrayList<>();
+//		final List<Content> questionList = getDatabaseDao().getAllSkillQuestions(session);
+//		for (final Content question : questionList) {
+//			final List<de.thm.arsnova.entities.transport.Answer> answerList = new ArrayList<>();
 //			if (withAnswers) {
-//				for (Answer a : this.getDatabaseDao().getAllAnswers(question)) {
-//					de.thm.arsnova.entities.transport.Answer transportAnswer = new de.thm.arsnova.entities.transport.Answer(a);
+//				for (final Answer a : this.getDatabaseDao().getAllAnswers(question)) {
+//					final de.thm.arsnova.entities.transport.Answer transportAnswer = new de.thm.arsnova.entities.transport.Answer(a);
 //					answerList.add(transportAnswer);
 //				}
 //				// getAllAnswers does not grep for whole answer object so i need to add empty entries for abstentions
 //				int i = this.getDatabaseDao().getAbstentionAnswerCount(question.getId());
 //				for (int b = 0; b < i; b++) {
-//					de.thm.arsnova.entities.transport.Answer ans = new de.thm.arsnova.entities.transport.Answer();
+//					final de.thm.arsnova.entities.transport.Answer ans = new de.thm.arsnova.entities.transport.Answer();
 //					ans.setAnswerSubject("");
 //					ans.setAnswerImage("");
 //					ans.setAnswerText("");
@@ -401,8 +298,8 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 //			importExportSession.addQuestionWithAnswers(question, answerList);
 //		}
 //		if (withFeedbackQuestions) {
-//			List<de.thm.arsnova.entities.transport.Comment> interposedQuestionList = new ArrayList<>();
-//			for (Comment i : getDatabaseDao().getInterposedQuestions(session, 0, 0)) {
+//			final List<de.thm.arsnova.entities.transport.Comment> interposedQuestionList = new ArrayList<>();
+//			for (final Comment i : getDatabaseDao().getInterposedQuestions(session, 0, 0)) {
 //				de.thm.arsnova.entities.transport.Comment transportInterposedQuestion = new de.thm.arsnova.entities.transport.Comment(i);
 //				interposedQuestionList.add(transportInterposedQuestion);
 //			}
@@ -415,7 +312,8 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 //		return importExportSession;
 	}
 
-	private SessionInfo calculateSessionInfo(ImportExportSession importExportSession, Session session) {
+	/* TODO: Move to service layer. */
+	private SessionInfo calculateSessionInfo(final ImportExportSession importExportSession, final Session session) {
 		int unreadComments = 0;
 		int numUnanswered = 0;
 		int numAnswers = 0;
@@ -440,17 +338,17 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 	}
 
 	@Override
-	public List<Session> getMySessions(final User user, final int start, final int limit) {
-		return getSessionsForUsername(user.getUsername(), start, limit);
+	public List<Session> findByUser(final User user, final int start, final int limit) {
+		return findByUsername(user.getUsername(), start, limit);
 	}
 
 	@Override
-	public List<Session> getSessionsForUsername(String username, final int start, final int limit) {
+	public List<Session> findByUsername(final String username, final int start, final int limit) {
 		final int qSkip = start > 0 ? start : -1;
 		final int qLimit = limit > 0 ? limit : -1;
 
 		/* TODO: Only load IDs and check against cache for data. */
-		List<Session> sessions = db.queryView(
+		return db.queryView(
 				createQuery("partial_by_sessiontype_creator_name")
 						.skip(qSkip)
 						.limit(qLimit)
@@ -458,24 +356,22 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 						.endKey(ComplexKey.of(null, username, ComplexKey.emptyObject()))
 						.includeDocs(true),
 				Session.class);
-
-		return sessions;
 	}
 
 	@Override
-	public List<Session> getPublicPoolSessions() {
+	public List<Session> findAllForPublicPool() {
 		// TODO replace with new view
 		return queryView("partial_by_ppsubject_name_for_publicpool");
 	}
 
 	@Override
-	public List<SessionInfo> getPublicPoolSessionsInfo() {
-		final List<Session> sessions = this.getPublicPoolSessions();
+	public List<SessionInfo> findInfosForPublicPool() {
+		final List<Session> sessions = this.findAllForPublicPool();
 		return getInfosForSessions(sessions);
 	}
 
 	@Override
-	public List<Session> getMyPublicPoolSessions(final User user) {
+	public List<Session> findForPublicPoolByUser(final User user) {
 		/* TODO: Only load IDs and check against cache for data. */
 		return db.queryView(
 				createQuery("partial_by_sessiontype_creator_name")
@@ -485,26 +381,29 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 				Session.class);
 	}
 
+	/* TODO: Move to service layer. */
 	@Override
-	public List<SessionInfo> getMyPublicPoolSessionsInfo(final User user) {
-		final List<Session> sessions = this.getMyPublicPoolSessions(user);
+	public List<SessionInfo> findInfosForPublicPoolByUser(final User user) {
+		final List<Session> sessions = this.findForPublicPoolByUser(user);
 		if (sessions.isEmpty()) {
 			return new ArrayList<>();
 		}
 		return getInfosForSessions(sessions);
 	}
 
+	/* TODO: Move to service layer. */
 	@Override
 	public List<SessionInfo> getMySessionsInfo(final User user, final int start, final int limit) {
-		final List<Session> sessions = this.getMySessions(user, start, limit);
+		final List<Session> sessions = this.findByUser(user, start, limit);
 		if (sessions.isEmpty()) {
 			return new ArrayList<>();
 		}
 		return getInfosForSessions(sessions);
 	}
 
+	/* TODO: Move to service layer. */
 	private List<SessionInfo> getInfosForSessions(final List<Session> sessions) {
-		List<String> sessionIds = sessions.stream().map(Session::getId).collect(Collectors.toList());
+		final List<String> sessionIds = sessions.stream().map(Session::getId).collect(Collectors.toList());
 		final ViewQuery questionCountView = createQuery("by_sessionid").designDocId("_design/Content")
 				.group(true).keys(sessionIds);
 		final ViewQuery answerCountView = createQuery("by_sessionid").designDocId("_design/Answer")
@@ -517,94 +416,100 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 		return getSessionInfoData(sessions, questionCountView, answerCountView, commentCountView, unreadCommentCountView);
 	}
 
+	/* TODO: Move to service layer. */
 	private List<SessionInfo> getInfosForVisitedSessions(final List<Session> sessions, final User user) {
 		final ViewQuery answeredQuestionsView = createQuery("by_user_sessionid").designDocId("_design/Answer")
 				.keys(sessions.stream().map(session -> ComplexKey.of(user.getUsername(), session.getId())).collect(Collectors.toList()));
-		final ViewQuery questionIdsView = createQuery("by_sessionid").designDocId("_design/Content")
+		final ViewQuery contentIdsView = createQuery("by_sessionid").designDocId("_design/Content")
 				.keys(sessions.stream().map(Session::getId).collect(Collectors.toList()));
 
-		return getVisitedSessionInfoData(sessions, answeredQuestionsView, questionIdsView);
+		return getVisitedSessionInfoData(sessions, answeredQuestionsView, contentIdsView);
 	}
 
-	private List<SessionInfo> getVisitedSessionInfoData(List<Session> sessions,
-														ViewQuery answeredQuestionsView, ViewQuery questionIdsView) {
+	/* TODO: Move to service layer. */
+	private List<SessionInfo> getVisitedSessionInfoData(
+			final List<Session> sessions,
+			final ViewQuery answeredQuestionsView,
+			final ViewQuery contentIdsView) {
 		final Map<String, Set<String>> answeredQuestionsMap = new HashMap<>();
-		final Map<String, Set<String>> questionIdMap = new HashMap<>();
+		final Map<String, Set<String>> contentIdMap = new HashMap<>();
 
 		// Maps a session ID to a set of question IDs of answered questions of that session
 		for (final ViewResult.Row row : db.queryView(answeredQuestionsView).getRows()) {
 			final String sessionId = row.getKey();
-			final String questionId = row.getValue();
-			Set<String> questionIdsInSession = answeredQuestionsMap.get(sessionId);
-			if (questionIdsInSession == null) {
-				questionIdsInSession = new HashSet<>();
+			final String contentId = row.getValue();
+			Set<String> contentIdsInSession = answeredQuestionsMap.get(sessionId);
+			if (contentIdsInSession == null) {
+				contentIdsInSession = new HashSet<>();
 			}
-			questionIdsInSession.add(questionId);
-			answeredQuestionsMap.put(sessionId, questionIdsInSession);
+			contentIdsInSession.add(contentId);
+			answeredQuestionsMap.put(sessionId, contentIdsInSession);
 		}
 
 		// Maps a session ID to a set of question IDs of that session
-		for (final ViewResult.Row row : db.queryView(questionIdsView).getRows()) {
+		for (final ViewResult.Row row : db.queryView(contentIdsView).getRows()) {
 			final String sessionId = row.getKey();
-			final String questionId = row.getId();
-			Set<String> questionIdsInSession = questionIdMap.get(sessionId);
-			if (questionIdsInSession == null) {
-				questionIdsInSession = new HashSet<>();
+			final String contentId = row.getId();
+			Set<String> contentIdsInSession = contentIdMap.get(sessionId);
+			if (contentIdsInSession == null) {
+				contentIdsInSession = new HashSet<>();
 			}
-			questionIdsInSession.add(questionId);
-			questionIdMap.put(sessionId, questionIdsInSession);
+			contentIdsInSession.add(contentId);
+			contentIdMap.put(sessionId, contentIdsInSession);
 		}
 
 		// For each session, count the question IDs that are not yet answered
-		Map<String, Integer> unansweredQuestionsCountMap = new HashMap<>();
+		final Map<String, Integer> unansweredQuestionsCountMap = new HashMap<>();
 		for (final Session s : sessions) {
-			if (!questionIdMap.containsKey(s.getId())) {
+			if (!contentIdMap.containsKey(s.getId())) {
 				continue;
 			}
 			// Note: create a copy of the first set so that we don't modify the contents in the original set
-			Set<String> questionIdsInSession = new HashSet<>(questionIdMap.get(s.getId()));
-			Set<String> answeredQuestionIdsInSession = answeredQuestionsMap.get(s.getId());
-			if (answeredQuestionIdsInSession == null) {
-				answeredQuestionIdsInSession = new HashSet<>();
+			final Set<String> contentIdsInSession = new HashSet<>(contentIdMap.get(s.getId()));
+			Set<String> answeredContentIdsInSession = answeredQuestionsMap.get(s.getId());
+			if (answeredContentIdsInSession == null) {
+				answeredContentIdsInSession = new HashSet<>();
 			}
-			questionIdsInSession.removeAll(answeredQuestionIdsInSession);
-			unansweredQuestionsCountMap.put(s.getId(), questionIdsInSession.size());
+			contentIdsInSession.removeAll(answeredContentIdsInSession);
+			unansweredQuestionsCountMap.put(s.getId(), contentIdsInSession.size());
 		}
 
-		List<SessionInfo> sessionInfos = new ArrayList<>();
-		for (Session session : sessions) {
+		final List<SessionInfo> sessionInfos = new ArrayList<>();
+		for (final Session session : sessions) {
 			int numUnanswered = 0;
 
 			if (unansweredQuestionsCountMap.containsKey(session.getId())) {
 				numUnanswered = unansweredQuestionsCountMap.get(session.getId());
 			}
-			SessionInfo info = new SessionInfo(session);
+			final SessionInfo info = new SessionInfo(session);
 			info.setNumUnanswered(numUnanswered);
 			sessionInfos.add(info);
 		}
 		return sessionInfos;
 	}
 
-	private List<SessionInfo> getSessionInfoData(final List<Session> sessions,
-												 final ViewQuery questionCountView,
-												 final ViewQuery answerCountView,
-												 final ViewQuery commentCountView,
-												 final ViewQuery unreadCommentCountView) {
-		Map<String, Integer> questionCountMap = db.queryView(questionCountView).getRows()
+	/* TODO: Move to service layer. */
+	private List<SessionInfo> getSessionInfoData(
+			final List<Session> sessions,
+			final ViewQuery questionCountView,
+			final ViewQuery answerCountView,
+			final ViewQuery commentCountView,
+			final ViewQuery unreadCommentCountView) {
+		final Map<String, Integer> questionCountMap = db.queryView(questionCountView).getRows()
 				.stream().map(row -> new AbstractMap.SimpleImmutableEntry<>(row.getKey(), row.getValueAsInt()))
 				.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
-		Map<String, Integer> answerCountMap = db.queryView(answerCountView).getRows()
+		final Map<String, Integer> answerCountMap = db.queryView(answerCountView).getRows()
 				.stream().map(row -> new AbstractMap.SimpleImmutableEntry<>(row.getKey(), row.getValueAsInt()))
 				.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
-		Map<String, Integer> commentCountMap = db.queryView(commentCountView).getRows()
+		final Map<String, Integer> commentCountMap = db.queryView(commentCountView).getRows()
 				.stream().map(row -> new AbstractMap.SimpleImmutableEntry<>(row.getKey(), row.getValueAsInt()))
 				.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
-		Map<String, Integer> unreadCommentCountMap = db.queryView(unreadCommentCountView).getRows()
+		final Map<String, Integer> unreadCommentCountMap = db.queryView(unreadCommentCountView).getRows()
 				.stream().map(row -> new AbstractMap.SimpleImmutableEntry<>(row.getKey(), row.getValueAsInt()))
 				.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
 
-		List<SessionInfo> sessionInfos = new ArrayList<>();
-		for (Session session : sessions) {
+		final List<SessionInfo> sessionInfos = new ArrayList<>();
+		for (final Session session : sessions) {
 			int numQuestions = 0;
 			int numAnswers = 0;
 			int numComments = 0;
@@ -622,7 +527,7 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 				numUnreadComments = unreadCommentCountMap.get(session.getId());
 			}
 
-			SessionInfo info = new SessionInfo(session);
+			final SessionInfo info = new SessionInfo(session);
 			info.setNumQuestions(numQuestions);
 			info.setNumAnswers(numAnswers);
 			info.setNumInterposed(numComments);
@@ -632,11 +537,12 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session>
 		return sessionInfos;
 	}
 
+	/* TODO: Move to service layer. */
 	@Override
 	public LoggedIn registerAsOnlineUser(final User user, final Session session) {
 		LoggedIn loggedIn = new LoggedIn();
 		try {
-			List<LoggedIn> loggedInList = db.queryView(createQuery("all").designDocId("_design/LoggedIn").key(user.getUsername()), LoggedIn.class);
+			final List<LoggedIn> loggedInList = db.queryView(createQuery("all").designDocId("_design/LoggedIn").key(user.getUsername()), LoggedIn.class);
 
 			if (!loggedInList.isEmpty()) {
 				loggedIn = loggedInList.get(0);
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 0d7bbcb1dc115ee8a02f3ba484d074d9c0e1fefe..e0c5f4927da1ad8b9ca33d3aaf6646411935698d 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionStatisticsRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionStatisticsRepository.java
@@ -1,30 +1,28 @@
 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;
 import org.ektorp.CouchDbConnector;
 import org.ektorp.ViewResult;
 import org.ektorp.support.CouchDbRepositorySupport;
-import org.springframework.cache.annotation.Cacheable;
 
 public class CouchDbSessionStatisticsRepository extends CouchDbRepositorySupport implements SessionStatisticsRepository {
-	public CouchDbSessionStatisticsRepository(CouchDbConnector db, boolean createIfNotExists) {
+	public CouchDbSessionStatisticsRepository(final CouchDbConnector db, final boolean createIfNotExists) {
 		super(Object.class, db, "learning_progress", createIfNotExists);
 	}
 
-	@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()) {
@@ -33,21 +31,21 @@ public class CouchDbSessionStatisticsRepository extends CouchDbRepositorySupport
 
 		// collect mapping (questionId -> max value)
 		for (ViewResult.Row row : maximumValueResult) {
-			final String questionId = row.getKeyAsNode().get(1).asText();
+			final String contentId = row.getKeyAsNode().get(1).asText();
 			final JsonNode value = row.getValueAsNode();
 			final int questionScore = value.get("value").asInt();
 			final String questionVariant = value.get("questionVariant").asText();
 			final int piRound = value.get("piRound").asInt();
-			courseScore.addQuestion(questionId, questionVariant, piRound, questionScore);
+			courseScore.addQuestion(contentId, questionVariant, piRound, questionScore);
 		}
 		// collect mapping (questionId -> (user -> value))
 		for (ViewResult.Row row : answerSumResult) {
 			final String username = row.getKeyAsNode().get(1).asText();
 			final JsonNode value = row.getValueAsNode();
-			final String questionId = value.get("questionId").asText();
+			final String contentId = value.get("questionId").asText();
 			final int userscore = value.get("score").asInt();
 			final int piRound = value.get("piRound").asInt();
-			courseScore.addAnswer(questionId, piRound, username, userscore);
+			courseScore.addAnswer(contentId, piRound, username, userscore);
 		}
 		return courseScore;
 	}
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbStatisticsRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbStatisticsRepository.java
index 19c6838a2e4eefaf5792ef515301cb964070696d..3506f7da41bc4033de687df6536a96da4fb0187a 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbStatisticsRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbStatisticsRepository.java
@@ -8,7 +8,6 @@ import org.ektorp.ViewResult;
 import org.ektorp.support.CouchDbRepositorySupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.cache.annotation.Cacheable;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -16,11 +15,10 @@ import java.util.Set;
 public class CouchDbStatisticsRepository extends CouchDbRepositorySupport implements StatisticsRepository {
 	private static final Logger logger = LoggerFactory.getLogger(CouchDbStatisticsRepository.class);
 
-	public CouchDbStatisticsRepository(CouchDbConnector db, boolean createIfNotExists) {
+	public CouchDbStatisticsRepository(final CouchDbConnector db, final boolean createIfNotExists) {
 		super(Object.class, db, "statistics", createIfNotExists);
 	}
 
-	@Cacheable("statistics")
 	@Override
 	public Statistics getStatistics() {
 		final Statistics stats = new Statistics();
@@ -30,7 +28,7 @@ public class CouchDbStatisticsRepository extends CouchDbRepositorySupport implem
 			final ViewResult studentUserResult = db.queryView(createQuery("active_student_users").group(true));
 
 			if (!statsResult.isEmpty()) {
-				for (ViewResult.Row row: statsResult.getRows()) {
+				for (final ViewResult.Row row: statsResult.getRows()) {
 					final int value = row.getValueAsInt();
 					switch (row.getKey()) {
 						case "openSessions":
@@ -65,14 +63,14 @@ public class CouchDbStatisticsRepository extends CouchDbRepositorySupport implem
 				}
 			}
 			if (!creatorResult.isEmpty()) {
-				Set<String> creators = new HashSet<>();
+				final Set<String> creators = new HashSet<>();
 				for (ViewResult.Row row: statsResult.getRows()) {
 					creators.add(row.getKey());
 				}
 				stats.setCreators(creators.size());
 			}
 			if (!studentUserResult.isEmpty()) {
-				Set<String> students = new HashSet<>();
+				final Set<String> students = new HashSet<>();
 				for (ViewResult.Row row: statsResult.getRows()) {
 					students.add(row.getKey());
 				}
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbUserRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbUserRepository.java
index 5c88ab22a79d47c22801a537194c3317ae11c6a9..e2e5e91345222adcdb83537bed659ba799485d3f 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbUserRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbUserRepository.java
@@ -22,23 +22,23 @@ import de.thm.arsnova.entities.DbUser;
 import de.thm.arsnova.persistance.UserRepository;
 import org.ektorp.BulkDeleteDocument;
 import org.ektorp.CouchDbConnector;
+import org.ektorp.DbAccessException;
 import org.ektorp.DocumentOperationResult;
 import org.ektorp.ViewQuery;
 import org.ektorp.ViewResult;
-import org.ektorp.support.CouchDbRepositorySupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.List;
 
-public class CouchDbUserRepository extends CouchDbRepositorySupport<DbUser> implements UserRepository {
+public class CouchDbUserRepository extends CouchDbCrudRepository<DbUser> implements UserRepository {
 	private static final int BULK_PARTITION_SIZE = 500;
 
 	private static final Logger logger = LoggerFactory.getLogger(CouchDbUserRepository.class);
 
-	public CouchDbUserRepository(CouchDbConnector db, boolean createIfNotExists) {
-		super(DbUser.class, db, createIfNotExists);
+	public CouchDbUserRepository(final CouchDbConnector db, final boolean createIfNotExists) {
+		super(DbUser.class, db, "by_username", createIfNotExists);
 	}
 
 	private void log(Object... strings) {
@@ -46,8 +46,8 @@ public class CouchDbUserRepository extends CouchDbRepositorySupport<DbUser> impl
 	}
 
 	@Override
-	public DbUser createOrUpdateUser(final DbUser user) {
-		String id = user.getId();
+	public DbUser save(final DbUser user) {
+		final String id = user.getId();
 
 		if (null != id) {
 			db.update(user);
@@ -59,40 +59,39 @@ public class CouchDbUserRepository extends CouchDbRepositorySupport<DbUser> impl
 	}
 
 	@Override
-	public DbUser findUserByUsername(String username) {
-		List<DbUser> users = queryView("by_username", username);
+	public DbUser findByUsername(final String username) {
+		final List<DbUser> users = queryView("by_username", username);
 
 		return !users.isEmpty() ? users.get(0) : null;
 	}
 
 	@Override
-	public boolean deleteUser(final DbUser user) {
+	public void delete(final DbUser user) {
 		if (db.delete(user) != null) {
 			log("delete", "type", "user", "id", user.getId());
-			return true;
 		} else {
 			logger.error("Could not delete user {}", user.getId());
-			return false;
+			throw new DbAccessException("Could not delete document.");
 		}
 	}
 
 	@Override
-	public int deleteInactiveUsers(long lastActivityBefore) {
-		ViewQuery q = createQuery("by_creation_for_inactive").endKey(lastActivityBefore);
-		List<ViewResult.Row> rows = db.queryView(q).getRows();
+	public int deleteInactiveUsers(final long lastActivityBefore) {
+		final ViewQuery q = createQuery("by_creation_for_inactive").endKey(lastActivityBefore);
+		final List<ViewResult.Row> rows = db.queryView(q).getRows();
 
 		int count = 0;
 		final List<List<ViewResult.Row>> partitions = Lists.partition(rows, BULK_PARTITION_SIZE);
-		for (List<ViewResult.Row> partition: partitions) {
+		for (final List<ViewResult.Row> partition: partitions) {
 			final List<BulkDeleteDocument> newDocs = new ArrayList<>();
-			for (ViewResult.Row oldDoc : partition) {
+			for (final ViewResult.Row oldDoc : partition) {
 				final BulkDeleteDocument newDoc = new BulkDeleteDocument(oldDoc.getId(), oldDoc.getValue());
 				newDocs.add(newDoc);
 				logger.debug("Marked user document {} for deletion.", oldDoc.getId());
 			}
 
 			if (newDocs.size() > 0) {
-				List<DocumentOperationResult> results = db.executeBulk(newDocs);
+				final List<DocumentOperationResult> results = db.executeBulk(newDocs);
 				if (!results.isEmpty()) {
 					/* TODO: This condition should be improved so that it checks the operation results. */
 					count += newDocs.size();
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbVisitedSessionRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbVisitedSessionRepository.java
index 03b2840873cd01102be8c848f4c81964efe0ff0b..a9bacfe27eb4f366cfdb5567981c5d841af7a195 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbVisitedSessionRepository.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbVisitedSessionRepository.java
@@ -25,18 +25,18 @@ public class CouchDbVisitedSessionRepository extends CouchDbRepositorySupport<Vi
 	@Autowired
 	private LogEntryRepository dbLogger;
 
-	public CouchDbVisitedSessionRepository(CouchDbConnector db, boolean createIfNotExists) {
+	public CouchDbVisitedSessionRepository(final CouchDbConnector db, final boolean createIfNotExists) {
 		super(VisitedSession.class, db, createIfNotExists);
 	}
 
 	@Override
-	public int deleteInactiveGuestVisitedSessionLists(long lastActivityBefore) {
+	public int deleteInactiveGuestVisitedSessionLists(final long lastActivityBefore) {
 		try {
-			ViewResult result = db.queryView(createQuery("by_last_activity_for_guests").endKey(lastActivityBefore));
+			final ViewResult result = db.queryView(createQuery("by_last_activity_for_guests").endKey(lastActivityBefore));
 
 			int count = 0;
-			List<List<ViewResult.Row>> partitions = Lists.partition(result.getRows(), BULK_PARTITION_SIZE);
-			for (List<ViewResult.Row> partition: partitions) {
+			final List<List<ViewResult.Row>> partitions = Lists.partition(result.getRows(), BULK_PARTITION_SIZE);
+			for (final List<ViewResult.Row> partition: partitions) {
 				final List<BulkDeleteDocument> newDocs = new ArrayList<>();
 				for (final ViewResult.Row oldDoc : partition) {
 					final BulkDeleteDocument newDoc = new BulkDeleteDocument(oldDoc.getId(), oldDoc.getValueAsNode().get("_rev").asText());
@@ -47,7 +47,7 @@ public class CouchDbVisitedSessionRepository extends CouchDbRepositorySupport<Vi
 				}
 
 				if (!newDocs.isEmpty()) {
-					List<DocumentOperationResult> results = db.executeBulk(newDocs);
+					final List<DocumentOperationResult> results = db.executeBulk(newDocs);
 					count += newDocs.size() - results.size();
 					if (!results.isEmpty()) {
 						logger.error("Could not bulk delete some visited session lists.");
@@ -61,7 +61,7 @@ public class CouchDbVisitedSessionRepository extends CouchDbRepositorySupport<Vi
 			}
 
 			return count;
-		} catch (DbAccessException e) {
+		} catch (final DbAccessException e) {
 			logger.error("Could not delete visited session lists of inactive users.", e);
 		}
 
diff --git a/src/main/java/de/thm/arsnova/persistance/couchdb/InitializingCouchDbConnector.java b/src/main/java/de/thm/arsnova/persistance/couchdb/InitializingCouchDbConnector.java
index d968a7ae030dd6e207d29287efeac3a2bf59f929..126504089a0f6252e5b0c24b22bb4c24734a718b 100644
--- a/src/main/java/de/thm/arsnova/persistance/couchdb/InitializingCouchDbConnector.java
+++ b/src/main/java/de/thm/arsnova/persistance/couchdb/InitializingCouchDbConnector.java
@@ -30,11 +30,11 @@ public class InitializingCouchDbConnector extends StdCouchDbConnector implements
 
 	private ResourceLoader resourceLoader;
 
-	public InitializingCouchDbConnector(String databaseName, CouchDbInstance dbInstance) {
+	public InitializingCouchDbConnector(final String databaseName, final CouchDbInstance dbInstance) {
 		super(databaseName, dbInstance);
 	}
 
-	public InitializingCouchDbConnector(String databaseName, CouchDbInstance dbi, ObjectMapperFactory om) {
+	public InitializingCouchDbConnector(final String databaseName, final CouchDbInstance dbi, final ObjectMapperFactory om) {
 		super(databaseName, dbi, om);
 	}
 
@@ -42,14 +42,14 @@ public class InitializingCouchDbConnector extends StdCouchDbConnector implements
 		final ScriptEngine engine = new ScriptEngineManager().getEngineByMimeType("application/javascript");
 		engine.eval(new InputStreamReader(new ClassPathResource("couchdb/jsToJson.js").getInputStream()));
 
-		PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
-		Resource[] resources = resolver.getResources("classpath:couchdb/*.design.js");
+		final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+		final Resource[] resources = resolver.getResources("classpath:couchdb/*.design.js");
 		for (Resource resource : resources) {
 			logger.debug("Loading CouchDB design doc: {}", resource.getFilename());
-			String js = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
+			final String js = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
 			/* Reset designDoc before parsing a new one. */
 			engine.eval("var designDoc = null;" + js);
-			Bindings jsonObject = (Bindings) engine.eval("jsToJson(designDoc)");
+			final Bindings jsonObject = (Bindings) engine.eval("jsToJson(designDoc)");
 			docs.add(jsonObject);
 		}
 	}
@@ -64,10 +64,10 @@ public class InitializingCouchDbConnector extends StdCouchDbConnector implements
 				}
 			}
 			try {
-				String rev = getCurrentRevision((String) doc.get("_id"));
+				final String rev = getCurrentRevision((String) doc.get("_id"));
 				doc.put("_rev", rev);
 				update(doc);
-			} catch (DocumentNotFoundException e) {
+			} catch (final DocumentNotFoundException e) {
 				create(doc);
 			}
 		});
@@ -80,7 +80,7 @@ public class InitializingCouchDbConnector extends StdCouchDbConnector implements
 	}
 
 	@Override
-	public void setResourceLoader(ResourceLoader resourceLoader) {
+	public void setResourceLoader(final ResourceLoader resourceLoader) {
 		this.resourceLoader = resourceLoader;
 	}
 }
diff --git a/src/main/java/de/thm/arsnova/security/ApplicationPermissionEvaluator.java b/src/main/java/de/thm/arsnova/security/ApplicationPermissionEvaluator.java
index fd72edb9c147432e3fa754f924cea85caac97b32..1bb5faf5c8ca74e699d30c0611e24214dc7b0b65 100644
--- a/src/main/java/de/thm/arsnova/security/ApplicationPermissionEvaluator.java
+++ b/src/main/java/de/thm/arsnova/security/ApplicationPermissionEvaluator.java
@@ -21,7 +21,6 @@ import de.thm.arsnova.entities.Comment;
 import de.thm.arsnova.entities.Content;
 import de.thm.arsnova.entities.Session;
 import de.thm.arsnova.entities.User;
-import de.thm.arsnova.exceptions.UnauthorizedException;
 import de.thm.arsnova.persistance.CommentRepository;
 import de.thm.arsnova.persistance.ContentRepository;
 import de.thm.arsnova.persistance.SessionRepository;
@@ -59,18 +58,20 @@ public class ApplicationPermissionEvaluator implements PermissionEvaluator {
 	public boolean hasPermission(
 			final Authentication authentication,
 			final Object targetDomainObject,
-			final Object permission
-			) {
-		final String username = getUsername(authentication);
-		if (checkAdminPermission(username)) {
-			return true;
-		} else if (
-				targetDomainObject instanceof Session
-				&& checkSessionPermission(username, ((Session) targetDomainObject).getKeyword(), permission)
-				) {
-			return true;
+			final Object permission) {
+		if (authentication == null || targetDomainObject == null || !(permission instanceof String)) {
+			return false;
 		}
-		return false;
+
+		final String username = getUsername(authentication);
+
+		return hasAdminRole(username)
+				|| (targetDomainObject instanceof Session
+						&& hasSessionPermission(username, ((Session) targetDomainObject), permission.toString()))
+				|| (targetDomainObject instanceof Content
+						&& hasContentPermission(username, ((Content) targetDomainObject), permission.toString()))
+				|| (targetDomainObject instanceof Comment
+						&& hasCommentPermission(username, ((Comment) targetDomainObject), permission.toString()));
 	}
 
 	@Override
@@ -78,87 +79,100 @@ public class ApplicationPermissionEvaluator implements PermissionEvaluator {
 			final Authentication authentication,
 			final Serializable targetId,
 			final String targetType,
-			final Object permission
-			) {
+			final Object permission) {
+		if (authentication == null || targetId == null || targetType == null || !(permission instanceof String)) {
+			return false;
+		}
+
 		final String username = getUsername(authentication);
-		if (checkAdminPermission(username)) {
-			return true;
-		} else if (
-				"session".equals(targetType)
-				&& checkSessionPermission(username, targetId, permission)) {
-			return true;
-		} else if (
-				"content".equals(targetType)
-				&& checkQuestionPermission(username, targetId, permission)
-				) {
-			return true;
-		} else if (
-				"comment".equals(targetType)
-				&& checkInterposedQuestionPermission(username, targetId, permission)
-				) {
+		if (hasAdminRole(username)) {
 			return true;
 		}
-		return false;
-	}
 
-	private boolean checkAdminPermission(final String username) {
-		/* TODO: only allow accounts from arsnova db */
-		return Arrays.asList(adminAccounts).contains(username);
+		switch (targetType) {
+			case "session":
+				final Session targetSession = sessionRepository.findByKeyword(targetId.toString());
+				return targetSession != null && hasSessionPermission(username, targetSession, permission.toString());
+			case "content":
+				final Content targetContent = contentRepository.findOne(targetId.toString());
+				return targetContent != null && hasContentPermission(username, targetContent, permission.toString());
+			case "comment":
+				final Comment targetComment = commentRepository.findOne(targetId.toString());
+				return targetComment != null && hasCommentPermission(username, targetComment, permission.toString());
+			default:
+				return false;
+		}
 	}
 
-	private boolean checkSessionPermission(
+	private boolean hasSessionPermission(
 			final String username,
-			final Serializable targetId,
-			final Object permission
-			) {
-		if (permission instanceof String && ("owner".equals(permission) || "write".equals(permission))) {
-			return sessionRepository.getSessionFromKeyword(targetId.toString()).getCreator().equals(username);
-		} else if (permission instanceof String && "read".equals(permission)) {
-			return sessionRepository.getSessionFromKeyword(targetId.toString()).isActive();
+			final Session targetSession,
+			final String permission) {
+		switch (permission) {
+			case "read":
+				return targetSession.isActive();
+			case "create":
+				return !username.isEmpty();
+			case "owner":
+			case "update":
+			case "delete":
+				return targetSession.getCreator().equals(username);
+			default:
+				return false;
 		}
-		return false;
 	}
 
-	private boolean checkQuestionPermission(
+	private boolean hasContentPermission(
 			final String username,
-			final Serializable targetId,
-			final Object permission
-			) {
-		if (permission instanceof String && "owner".equals(permission)) {
-			final Content content = contentRepository.getQuestion(targetId.toString());
-			if (content != null) {
-				final Session session = sessionRepository.getSessionFromId(content.getSessionId());
-
+			final Content targetContent,
+			final String permission) {
+		switch (permission) {
+			case "read":
+				return sessionRepository.findOne(targetContent.getSessionId()).isActive();
+			case "create":
+			case "owner":
+			case "update":
+			case "delete":
+				final Session session = sessionRepository.findOne(targetContent.getSessionId());
 				return session != null && session.getCreator().equals(username);
-			}
+			default:
+				return false;
 		}
-		return false;
 	}
 
-	private boolean checkInterposedQuestionPermission(
+	private boolean hasCommentPermission(
 			final String username,
-			final Serializable targetId,
-			final Object permission
-			) {
-		if (permission instanceof String && "owner".equals(permission)) {
-			final Comment comment = commentRepository.getInterposedQuestion(targetId.toString());
-			if (comment != null) {
-				// Does the creator want to delete his own comment?
-				if (comment.getCreator() != null && comment.getCreator().equals(username)) {
+			final Comment targetComment,
+			final String permission) {
+		switch (permission) {
+			case "create":
+				return !username.isEmpty() && sessionRepository.findOne(targetComment.getSessionId()).isActive();
+			case "owner":
+			case "update":
+				return targetComment.getCreator() != null && targetComment.getCreator().equals(username);
+			case "read":
+			case "delete":
+				if (targetComment.getCreator() != null && targetComment.getCreator().equals(username)) {
 					return true;
 				}
-				// Allow deletion if requested by session owner
-				final Session session = sessionRepository.getSessionFromKeyword(comment.getSessionId());
+
+				/* Allow reading & deletion by session owner */
+				final Session session = sessionRepository.findOne(targetComment.getSessionId());
 
 				return session != null && session.getCreator().equals(username);
-			}
+			default:
+				return false;
 		}
-		return false;
+	}
+
+	private boolean hasAdminRole(final String username) {
+		/* TODO: only allow accounts from arsnova db */
+		return Arrays.asList(adminAccounts).contains(username);
 	}
 
 	private String getUsername(final Authentication authentication) {
 		if (authentication == null || authentication instanceof AnonymousAuthenticationToken) {
-			throw new UnauthorizedException();
+			return "";
 		}
 
 		if (authentication instanceof Pac4jAuthenticationToken) {
@@ -183,4 +197,8 @@ public class ApplicationPermissionEvaluator implements PermissionEvaluator {
 
 		return authentication.getName();
 	}
+
+	private boolean isWebsocketAccess(Authentication auth) {
+		return auth instanceof AnonymousAuthenticationToken && auth.getAuthorities().contains("ROLE_WEBSOCKET_ACCESS");
+	}
 }
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 13eb9343443ed2cd00203d11f3d3c2d0562761b8..dbb451a6f651b28ff36f1346ff230797a59a1ddc 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 9261648fb99860ff9a37c1509dbf4e427dd2e50c..05bc988df36b482fad37dea5616a52561e81a23b 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/security/DbUserDetailsService.java b/src/main/java/de/thm/arsnova/security/DbUserDetailsService.java
index 8e3b308cb42e8dd9ff1cdd5e7fbc1ea593b6566d..5351d98c560baf58c5e99773c5f803d34d0b03ca 100644
--- a/src/main/java/de/thm/arsnova/security/DbUserDetailsService.java
+++ b/src/main/java/de/thm/arsnova/security/DbUserDetailsService.java
@@ -48,7 +48,7 @@ public class DbUserDetailsService implements UserDetailsService {
 	public UserDetails loadUserByUsername(String username) {
 		String uid = username.toLowerCase();
 		logger.debug("Load user: " + uid);
-		DbUser dbUser = userRepository.findUserByUsername(uid);
+		DbUser dbUser = userRepository.findByUsername(uid);
 		if (null == dbUser) {
 			throw new UsernameNotFoundException("User does not exist.");
 		}
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 f3f4298751c83d7fa410272ff28bb3a729216453..3a4e1c04d0879c2a2da2f5b6f3ffd4a5427c079e 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 714578ee6ef33665a588ae9443b495da91a8c2d3..b78b7462748120230c25d7d252413d7faa2561f6 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/CommentService.java b/src/main/java/de/thm/arsnova/services/CommentService.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ffc38c91f4690ab6ceba281f464c492fcd78f71
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/CommentService.java
@@ -0,0 +1,25 @@
+package de.thm.arsnova.services;
+
+import de.thm.arsnova.entities.Comment;
+import de.thm.arsnova.entities.CommentReadingCount;
+import de.thm.arsnova.entities.User;
+
+import java.util.List;
+
+public interface CommentService {
+	boolean save(Comment comment);
+
+	int count(String sessionKey);
+
+	CommentReadingCount countRead(String sessionKey, String username);
+
+	List<Comment> getBySessionKey(String sessionKey, int offset, int limit);
+
+	Comment getAndMarkRead(String commentId);
+
+	Comment getAndMarkReadInternal(String commentId, User user);
+
+	void delete(String commentId);
+
+	void deleteBySessionKey(String sessionKeyword);
+}
diff --git a/src/main/java/de/thm/arsnova/services/CommentServiceImpl.java b/src/main/java/de/thm/arsnova/services/CommentServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..82eaca02d32e40acd28b2c0cab50d0d518c55f0c
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/CommentServiceImpl.java
@@ -0,0 +1,182 @@
+package de.thm.arsnova.services;
+
+import de.thm.arsnova.entities.Comment;
+import de.thm.arsnova.entities.CommentReadingCount;
+import de.thm.arsnova.entities.Session;
+import de.thm.arsnova.entities.User;
+import de.thm.arsnova.events.DeleteCommentEvent;
+import de.thm.arsnova.events.NewCommentEvent;
+import de.thm.arsnova.exceptions.ForbiddenException;
+import de.thm.arsnova.exceptions.NotFoundException;
+import de.thm.arsnova.exceptions.UnauthorizedException;
+import de.thm.arsnova.persistance.CommentRepository;
+import de.thm.arsnova.persistance.SessionRepository;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * Performs all comment related operations.
+ */
+@Service
+public class CommentServiceImpl extends EntityService<Comment> implements CommentService, ApplicationEventPublisherAware {
+	private UserService userService;
+
+	private CommentRepository commentRepository;
+
+	private SessionRepository sessionRepository;
+
+	private ApplicationEventPublisher publisher;
+
+	public CommentServiceImpl(
+			CommentRepository repository,
+			SessionRepository sessionRepository,
+			UserService userService,
+			@Qualifier("defaultJsonMessageConverter") MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) {
+		super(Comment.class, repository, jackson2HttpMessageConverter.getObjectMapper());
+		this.commentRepository = repository;
+		this.sessionRepository = sessionRepository;
+		this.userService = userService;
+	}
+
+	@Override
+	public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
+		this.publisher = applicationEventPublisher;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public boolean save(final Comment comment) {
+		final Session session = sessionRepository.findByKeyword(comment.getSessionId());
+		final User user = userService.getCurrentUser();
+		comment.setSessionId(session.getId());
+		comment.setCreator(user.getUsername());
+		comment.setRead(false);
+		if (comment.getTimestamp() == 0) {
+			comment.setTimestamp(System.currentTimeMillis());
+		}
+		final Comment result = super.create(comment);
+
+		if (null != result) {
+			final NewCommentEvent event = new NewCommentEvent(this, session, result);
+			this.publisher.publishEvent(event);
+			return true;
+		}
+		return false;
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#commentId, 'comment', 'owner')")
+	public void delete(final String commentId) {
+		final Comment comment = commentRepository.findOne(commentId);
+		if (comment == null) {
+			throw new NotFoundException();
+		}
+		commentRepository.delete(comment);
+
+		final Session session = sessionRepository.findByKeyword(comment.getSessionId());
+		final DeleteCommentEvent event = new DeleteCommentEvent(this, session, comment);
+		this.publisher.publishEvent(event);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public void deleteBySessionKey(final String sessionKeyword) {
+		final Session session = sessionRepository.findByKeyword(sessionKeyword);
+		if (session == null) {
+			throw new UnauthorizedException();
+		}
+		final User user = getCurrentUser();
+		if (session.isCreator(user)) {
+			commentRepository.deleteBySessionId(session.getId());
+		} else {
+			commentRepository.deleteBySessionIdAndUser(session.getId(), user);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int count(final String sessionKey) {
+		return commentRepository.countBySessionId(getSession(sessionKey).getId());
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public CommentReadingCount countRead(final String sessionKey, String username) {
+		final Session session = sessionRepository.findByKeyword(sessionKey);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		if (username == null) {
+			return commentRepository.countReadingBySessionId(session.getId());
+		} else {
+			User currentUser = userService.getCurrentUser();
+			if (!currentUser.getUsername().equals(username)) {
+				throw new ForbiddenException();
+			}
+
+			return commentRepository.countReadingBySessionIdAndUser(session.getId(), currentUser);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Comment> getBySessionKey(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.findBySessionId(session.getId(), offset, limit);
+		} else {
+			return commentRepository.findBySessionIdAndUser(session.getId(), user, offset, limit);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public Comment getAndMarkRead(final String commentId) {
+		final User user = userService.getCurrentUser();
+		return this.getAndMarkReadInternal(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 getAndMarkReadInternal(final String commentId, User user) {
+		final Comment comment = commentRepository.findOne(commentId);
+		if (comment == null) {
+			throw new NotFoundException();
+		}
+		final Session session = sessionRepository.findOne(comment.getSessionId());
+		if (!comment.isCreator(user) && !session.isCreator(user)) {
+			throw new UnauthorizedException();
+		}
+		if (session.isCreator(user)) {
+			comment.setRead(true);
+			save(comment);
+		}
+		return comment;
+	}
+
+	private User getCurrentUser() {
+		final User user = userService.getCurrentUser();
+		if (user == null) {
+			throw new UnauthorizedException();
+		}
+		return user;
+	}
+
+	private Session getSession(final String sessionkey) {
+		final Session session = sessionRepository.findByKeyword(sessionkey);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		return session;
+	}
+}
diff --git a/src/main/java/de/thm/arsnova/services/ContentService.java b/src/main/java/de/thm/arsnova/services/ContentService.java
index 2191d8cf7d1216bd621fbe8bb1ad194f8a0bffb9..6a66cc99935a056d3942c56b854d0ba38aea3402 100644
--- a/src/main/java/de/thm/arsnova/services/ContentService.java
+++ b/src/main/java/de/thm/arsnova/services/ContentService.java
@@ -17,1025 +17,136 @@
  */
 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;
+public interface ContentService {
+	Content save(Content content);
 
-	@Autowired
-	private SessionRepository sessionRepository;
+	Content get(String id);
 
-	@Autowired
-	private CommentRepository commentRepository;
+	List<Content> getBySessionKey(String sessionkey);
 
-	@Autowired
-	private ContentRepository contentRepository;
+	int countBySessionKey(String sessionkey);
 
-	@Autowired
-	private AnswerRepository answerRepository;
+	void delete(String questionId);
 
-	@Autowired
-	private ImageUtils imageUtils;
+	void startNewPiRound(String questionId, User user);
 
-	@Value("${upload.filesize_b}")
-	private int uploadFileSizeByte;
+	void startNewPiRoundDelayed(String questionId, int time);
 
-	private ApplicationEventPublisher publisher;
+	void cancelPiRoundChange(String questionId);
 
-	private static final Logger logger = LoggerFactory.getLogger(ContentService.class);
+	void cancelDelayedPiRoundChange(String questionId);
 
-	private HashMap<String, Timer> timerList = new HashMap<>();
+	void resetPiRoundState(String questionId);
 
-	@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);
-		}
-	}
+	List<String> getUnAnsweredQuestionIds(String sessionKey);
 
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public int getSkillQuestionCount(final String sessionkey) {
-		final Session session = sessionRepository.getSessionFromKeyword(sessionkey);
-		return contentRepository.getSkillQuestionCount(session);
-	}
+	Answer getMyAnswer(String questionId);
 
-	/* 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);
+	void getFreetextAnswerAndMarkRead(String answerId, User user);
 
-		if ("freetext".equals(content.getQuestionType())) {
-			content.setPiRound(0);
-		} else if (content.getPiRound() < 1 || content.getPiRound() > 2) {
-			content.setPiRound(1);
-		}
+	List<Answer> getAnswers(String questionId, int piRound, int offset, int limit);
 
-		// 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();
-			}
-		}
+	List<Answer> getAnswers(String questionId, int offset, int limit);
 
-		final Content result = contentRepository.saveQuestion(session, content);
+	List<Answer> getAllAnswers(String questionId, int offset, int limit);
 
-		final NewQuestionEvent event = new NewQuestionEvent(this, session, result);
-		this.publisher.publishEvent(event);
+	int countAnswersByQuestionIdAndRound(String questionId);
 
-		return result;
-	}
+	int countAnswersByQuestionIdAndRound(String questionId, int piRound);
 
-	@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());
+	List<Answer> getFreetextAnswersByQuestionId(String questionId, int offset, int limit);
 
-		if (null != result) {
-			final NewCommentEvent event = new NewCommentEvent(this, session, result);
-			this.publisher.publishEvent(event);
-			return true;
-		}
-		return false;
-	}
+	List<Answer> getMyAnswersBySessionKey(String sessionKey);
 
-	@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);
-		}
+	int countTotalAnswersBySessionKey(String sessionKey);
 
-		return result;
-	}
+	int countTotalAnswersByQuestionId(String questionId);
 
-	@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();
-		}
+	Content save(final String sessionId, final Content content);
 
-		final Session session = sessionRepository.getSessionFromId(content.getSessionId());
-		if (session == null) {
-			throw new UnauthorizedException();
-		}
-		contentRepository.deleteQuestionWithAnswers(content);
+	Content update(Content content);
 
-		final DeleteQuestionEvent event = new DeleteQuestionEvent(this, session, content);
-		this.publisher.publishEvent(event);
-	}
+	void deleteAnswers(String questionId);
 
-	@Override
-	@PreAuthorize("isAuthenticated() and hasPermission(#sessionKeyword, 'session', 'owner')")
-	public void deleteAllQuestions(final String sessionKeyword) {
-		final Session session = getSessionWithAuthCheck(sessionKeyword);
-		contentRepository.deleteAllQuestionsWithAnswers(session);
+	Answer saveAnswer(String questionId, Answer answer);
 
-		final DeleteAllQuestionsEvent event = new DeleteAllQuestionsEvent(this, session);
-		this.publisher.publishEvent(event);
-	}
+	Answer updateAnswer(Answer answer);
 
-	@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());
+	void deleteAnswer(String questionId, String answerId);
 
-		if (null == user) {
-			user = userService.getCurrentUser();
-		}
+	List<Content> getLectureQuestions(String sessionkey);
 
-		cancelDelayedPiRoundChange(questionId);
+	List<Content> getFlashcards(String sessionkey);
 
-		content.setPiRoundEndTime(0);
-		content.setVotingDisabled(true);
-		content.updateRoundManagementState();
-		update(content, user);
+	List<Content> getPreparationQuestions(String sessionkey);
 
-		this.publisher.publishEvent(new PiRoundEndEvent(this, session, content));
-	}
+	int countLectureQuestions(String sessionkey);
 
-	@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());
+	int countFlashcards(String sessionkey);
 
-		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);
+	int countPreparationQuestions(String sessionkey);
 
-		this.publisher.publishEvent(new PiRoundDelayedStartEvent(this, session, content));
-		timerList.put(questionId, timer);
+	Map<String, Object> countAnswersAndAbstentionsInternal(String questionid);
 
-		timer.schedule(new TimerTask() {
-			@Override
-			public void run() {
-				contentService.startNewPiRound(questionId, user);
-			}
-		}, endDate);
-	}
+	int countLectureQuestionAnswers(String sessionkey);
 
-	@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());
+	int countLectureQuestionAnswersInternal(String sessionkey);
 
-		cancelDelayedPiRoundChange(questionId);
-		content.resetRoundManagementState();
+	int countPreparationQuestionAnswers(String sessionkey);
 
-		if (0 == content.getPiRound() || 1 == content.getPiRound()) {
-			content.setPiRoundFinished(false);
-		} else {
-			content.setPiRound(1);
-			content.setPiRoundFinished(true);
-		}
+	int countPreparationQuestionAnswersInternal(String sessionkey);
 
-		update(content);
-		this.publisher.publishEvent(new PiRoundCancelEvent(this, session, content));
-	}
+	int countFlashcardsForUserInternal(String sessionkey);
 
-	@Override
-	public void cancelDelayedPiRoundChange(final String questionId) {
-		Timer timer = timerList.get(questionId);
+	void deleteAllContent(String sessionkey);
 
-		if (null != timer) {
-			timer.cancel();
-			timerList.remove(questionId);
-			timer.purge();
-		}
-	}
+	void deleteLectureQuestions(String sessionkey);
 
-	@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);
+	void deletePreparationQuestions(String sessionkey);
 
-		if ("freetext".equals(content.getQuestionType())) {
-			content.setPiRound(0);
-		} else {
-			content.setPiRound(1);
-		}
+	void deleteFlashcards(String sessionkey);
 
-		content.resetRoundManagementState();
-		answerRepository.deleteAnswers(content);
-		update(content);
-		this.publisher.publishEvent(new PiRoundResetEvent(this, session, content));
-	}
+	List<String> getUnAnsweredLectureQuestionIds(String sessionkey);
 
-	@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);
+	List<String> getUnAnsweredLectureQuestionIds(String sessionKey, User user);
 
-		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);
-	}
+	List<String> getUnAnsweredPreparationQuestionIds(String sessionkey);
 
-	@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);
-	}
+	List<String> getUnAnsweredPreparationQuestionIds(String sessionKey, User user);
 
-	@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);
-	}
+	void publishAll(String sessionkey, boolean publish);
 
-	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;
-	}
+	void publishQuestions(String sessionkey, boolean publish, List<Content> contents);
 
-	@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);
+	void deleteAllQuestionsAnswers(String sessionkey);
 
-		final Session session = sessionRepository.getSessionFromKeyword(comment.getSessionId());
-		final DeleteCommentEvent event = new DeleteCommentEvent(this, session, comment);
-		this.publisher.publishEvent(event);
-	}
+	void deleteAllPreparationAnswers(String sessionkey);
 
-	@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);
-		}
-	}
+	void deleteAllLectureAnswers(String sessionkey);
 
-	@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);
-	}
+	int countTotalAbstentionsByQuestionId(String questionId);
 
-	@Override
-	@PreAuthorize("isAuthenticated()")
-	public List<String> getUnAnsweredQuestionIds(final String sessionKey) {
-		final User user = getCurrentUser();
-		final Session session = getSession(sessionKey);
-		return contentRepository.getUnAnsweredQuestionIds(session, user);
-	}
+	String getImage(String questionId, String answerId);
 
-	private User getCurrentUser() {
-		final User user = userService.getCurrentUser();
-		if (user == null) {
-			throw new UnauthorizedException();
-		}
-		return user;
-	}
+	void setVotingAdmission(String questionId, boolean disableVoting);
 
-	@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());
-	}
+	void setVotingAdmissions(String sessionkey, boolean disableVoting, List<Content> contents);
 
-	@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);
-		}
-	}
+	void setVotingAdmissionForAllQuestions(String sessionkey, boolean disableVoting);
 
-	@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);
-	}
+	String getQuestionImage(String questionId);
 
-	@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);
-		}
-	}
+	String getQuestionFcImage(String questionId);
 
-	@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;
-	}
+	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 0000000000000000000000000000000000000000..167b318638db02c1ffcbe50eac7fe4e8d0b9a413
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/ContentServiceImpl.java
@@ -0,0 +1,1104 @@
+/*
+ * 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.transport.AnswerQueueElement;
+import de.thm.arsnova.persistance.LogEntryRepository;
+import de.thm.arsnova.util.ImageUtils;
+import de.thm.arsnova.entities.Answer;
+import de.thm.arsnova.entities.Content;
+import de.thm.arsnova.entities.Session;
+import de.thm.arsnova.entities.User;
+import de.thm.arsnova.events.*;
+import de.thm.arsnova.exceptions.BadRequestException;
+import de.thm.arsnova.exceptions.NotFoundException;
+import de.thm.arsnova.exceptions.UnauthorizedException;
+import de.thm.arsnova.persistance.AnswerRepository;
+import de.thm.arsnova.persistance.ContentRepository;
+import de.thm.arsnova.persistance.SessionRepository;
+import org.ektorp.DbAccessException;
+import org.ektorp.DocumentNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.cache.annotation.Caching;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.scheduling.annotation.Scheduled;
+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.Queue;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.stream.Collectors;
+
+/**
+ * Performs all content and answer related operations.
+ */
+@Service
+public class ContentServiceImpl extends EntityService<Content> implements ContentService, ApplicationEventPublisherAware {
+	private UserService userService;
+
+	private LogEntryRepository dbLogger;
+
+	private SessionRepository sessionRepository;
+
+	private ContentRepository contentRepository;
+
+	private AnswerRepository answerRepository;
+
+	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<>();
+
+	private final Queue<AnswerQueueElement> answerQueue = new ConcurrentLinkedQueue<>();
+
+	public ContentServiceImpl(
+			ContentRepository repository,
+			AnswerRepository answerRepository,
+			SessionRepository sessionRepository,
+			LogEntryRepository dbLogger,
+			UserService userService,
+			ImageUtils imageUtils,
+			@Qualifier("defaultJsonMessageConverter") MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) {
+		super(Content.class, repository, jackson2HttpMessageConverter.getObjectMapper());
+		this.contentRepository = repository;
+		this.answerRepository = answerRepository;
+		this.sessionRepository = sessionRepository;
+		this.dbLogger = dbLogger;
+		this.userService = userService;
+		this.imageUtils = imageUtils;
+	}
+
+	@Scheduled(fixedDelay = 5000)
+	public void flushAnswerQueue() {
+		if (answerQueue.isEmpty()) {
+			// no need to send an empty bulk request.
+			return;
+		}
+
+		final List<Answer> answerList = new ArrayList<>();
+		final List<AnswerQueueElement> elements = new ArrayList<>();
+		AnswerQueueElement entry;
+		while ((entry = this.answerQueue.poll()) != null) {
+			final Answer answer = entry.getAnswer();
+			answerList.add(answer);
+			elements.add(entry);
+		}
+		try {
+			answerRepository.save(answerList);
+
+			// Send NewAnswerEvents ...
+			for (AnswerQueueElement e : elements) {
+				this.publisher.publishEvent(new NewAnswerEvent(this, e.getSession(), e.getAnswer(), e.getUser(), e.getQuestion()));
+			}
+		} catch (final DbAccessException e) {
+			logger.error("Could not bulk save answers from queue.", e);
+		}
+	}
+
+	@Cacheable("contents")
+	@Override
+	public Content get(final String id) {
+		try {
+			final Content content = super.get(id);
+			if (!"freetext".equals(content.getQuestionType()) && 0 == content.getPiRound()) {
+			/* needed for legacy questions whose piRound property has not been set */
+				content.setPiRound(1);
+			}
+			content.updateRoundManagementState();
+			//content.setSessionKeyword(sessionRepository.getSessionFromId(content.getSessionId()).getKeyword());
+
+			return content;
+		} catch (final DocumentNotFoundException e) {
+			logger.error("Could not get question {}.", id, e);
+		}
+
+		return null;
+	}
+
+	@Override
+	@Caching(evict = {@CacheEvict(value = "contentlists", key = "#sessionId"),
+			@CacheEvict(value = "lecturecontentlists", key = "#sessionId", condition = "#content.getQuestionVariant().equals('lecture')"),
+			@CacheEvict(value = "preparationcontentlists", key = "#sessionId", condition = "#content.getQuestionVariant().equals('preparation')"),
+			@CacheEvict(value = "flashcardcontentlists", key = "#sessionId", condition = "#content.getQuestionVariant().equals('flashcard')") },
+			put = {@CachePut(value = "contents", key = "#content.id")})
+	public Content save(final String sessionId, final Content content) {
+		content.setSessionId(sessionId);
+		try {
+			contentRepository.save(content);
+
+			return content;
+		} catch (final IllegalArgumentException e) {
+			logger.error("Could not save content {}.", content, e);
+		}
+
+		return null;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	@Caching(evict = {
+			@CacheEvict(value = "contentlists", allEntries = true),
+			@CacheEvict(value = "lecturecontentlists", allEntries = true, condition = "#content.getQuestionVariant().equals('lecture')"),
+			@CacheEvict(value = "preparationcontentlists", allEntries = true, condition = "#content.getQuestionVariant().equals('preparation')"),
+			@CacheEvict(value = "flashcardcontentlists", allEntries = true, condition = "#content.getQuestionVariant().equals('flashcard')") },
+			put = {@CachePut(value = "contents", key = "#content.id")})
+	public Content update(final Content content) {
+		final User user = userService.getCurrentUser();
+		final Content oldContent = contentRepository.findOne(content.getId());
+		if (null == oldContent) {
+			throw new NotFoundException();
+		}
+
+		final Session session = sessionRepository.findOne(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);
+		}
+
+		content.setId(oldContent.getId());
+		content.setRevision(oldContent.getRevision());
+		content.updateRoundManagementState();
+		contentRepository.save(content);
+
+		if (!oldContent.isActive() && content.isActive()) {
+			final UnlockQuestionEvent event = new UnlockQuestionEvent(this, session, content);
+			this.publisher.publishEvent(event);
+		} else if (oldContent.isActive() && !content.isActive()) {
+			final LockQuestionEvent event = new LockQuestionEvent(this, session, content);
+			this.publisher.publishEvent(event);
+		}
+		return content;
+	}
+
+	/* FIXME: caching */
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	//@Cacheable("contentlists")
+	public List<Content> getBySessionKey(final String sessionkey) {
+		final Session session = getSession(sessionkey);
+		final User user = userService.getCurrentUser();
+		if (session.isCreator(user)) {
+			return contentRepository.findBySessionIdForSpeaker(session.getId());
+		} else {
+			return contentRepository.findBySessionIdForUsers(session.getId());
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int countBySessionKey(final String sessionkey) {
+		final Session session = sessionRepository.findByKeyword(sessionkey);
+		return contentRepository.countBySessionId(session.getId());
+	}
+
+	/* FIXME: #content.getSessionKeyword() cannot be checked since keyword is no longer set for content. */
+	@Override
+	@PreAuthorize("hasPermission(#content.getSessionKeyword(), 'session', 'owner')")
+	public Content save(final Content content) {
+		final Session session = sessionRepository.findByKeyword(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 = save(session.getId(), content);
+
+		final NewQuestionEvent event = new NewQuestionEvent(this, session, result);
+		this.publisher.publishEvent(event);
+
+		return result;
+	}
+
+	/* TODO: Only evict cache entry for the content's session. This requires some refactoring. */
+	@Override
+	@PreAuthorize("hasPermission(#contentId, 'content', 'owner')")
+	@Caching(evict = {
+			@CacheEvict("answerlists"),
+			@CacheEvict(value = "contents", key = "#contentId"),
+			@CacheEvict(value = "contentlists", allEntries = true),
+			@CacheEvict(value = "lecturecontentlists", allEntries = true /*, condition = "#content.getQuestionVariant().equals('lecture')"*/),
+			@CacheEvict(value = "preparationcontentlists", allEntries = true /*, condition = "#content.getQuestionVariant().equals('preparation')"*/),
+			@CacheEvict(value = "flashcardcontentlists", allEntries = true /*, condition = "#content.getQuestionVariant().equals('flashcard')"*/) })
+	public void delete(final String contentId) {
+		final Content content = contentRepository.findOne(contentId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+
+		final Session session = sessionRepository.findOne(content.getSessionId());
+		if (session == null) {
+			throw new UnauthorizedException();
+		}
+
+		try {
+			final int count = answerRepository.deleteByContentId(contentId);
+			contentRepository.delete(contentId);
+			dbLogger.log("delete", "type", "content", "answerCount", count);
+		} catch (final IllegalArgumentException e) {
+			logger.error("Could not delete content {}.", contentId, e);
+		}
+
+		final DeleteQuestionEvent event = new DeleteQuestionEvent(this, session, content);
+		this.publisher.publishEvent(event);
+	}
+
+	@PreAuthorize("hasPermission(#session, 'owner')")
+	@Caching(evict = {
+			@CacheEvict(value = "contents", allEntries = true),
+			@CacheEvict(value = "contentlists", key = "#session.getId()"),
+			@CacheEvict(value = "lecturecontentlists", key = "#session.getId()", condition = "'lecture'.equals(#variant)"),
+			@CacheEvict(value = "preparationcontentlists", key = "#session.getId()", condition = "'preparation'.equals(#variant)"),
+			@CacheEvict(value = "flashcardcontentlists", key = "#session.getId()", condition = "'flashcard'.equals(#variant)") })
+	private void deleteBySessionAndVariant(final Session session, final String variant) {
+		final List<String> contentIds;
+		if ("all".equals(variant)) {
+			contentIds = contentRepository.findIdsBySessionId(session.getId());
+		} else {
+			contentIds = contentRepository.findIdsBySessionIdAndVariant(session.getId(), variant);
+		}
+
+		final int answerCount = answerRepository.deleteByContentIds(contentIds);
+		final int contentCount = contentRepository.deleteBySessionId(session.getId());
+		dbLogger.log("delete", "type", "question", "questionCount", contentCount);
+		dbLogger.log("delete", "type", "answer", "answerCount", answerCount);
+
+		final DeleteAllQuestionsEvent event = new DeleteAllQuestionsEvent(this, session);
+		this.publisher.publishEvent(event);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public void deleteAllContent(final String sessionkey) {
+		final Session session = getSessionWithAuthCheck(sessionkey);
+		deleteBySessionAndVariant(session, "all");
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public void deleteLectureQuestions(final String sessionkey) {
+		final Session session = getSessionWithAuthCheck(sessionkey);
+		deleteBySessionAndVariant(session, "lecture");
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public void deletePreparationQuestions(final String sessionkey) {
+		final Session session = getSessionWithAuthCheck(sessionkey);
+		deleteBySessionAndVariant(session, "preparation");
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public void deleteFlashcards(final String sessionkey) {
+		final Session session = getSessionWithAuthCheck(sessionkey);
+		deleteBySessionAndVariant(session, "flashcard");
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'content', 'owner')")
+	public void startNewPiRound(final String questionId, User user) {
+		final Content content = contentRepository.findOne(questionId);
+		final Session session = sessionRepository.findOne(content.getSessionId());
+
+		if (null == user) {
+			user = userService.getCurrentUser();
+		}
+
+		cancelDelayedPiRoundChange(questionId);
+
+		content.setPiRoundEndTime(0);
+		content.setVotingDisabled(true);
+		content.updateRoundManagementState();
+		update(content);
+
+		this.publisher.publishEvent(new PiRoundEndEvent(this, session, content));
+	}
+
+	@Override
+	@PreAuthorize("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.findOne(questionId);
+		final Session session = sessionRepository.findOne(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("hasPermission(#questionId, 'content', 'owner')")
+	public void cancelPiRoundChange(final String questionId) {
+		final Content content = contentRepository.findOne(questionId);
+		final Session session = sessionRepository.findOne(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("hasPermission(#questionId, 'content', 'owner')")
+	@CacheEvict("answerlists")
+	public void resetPiRoundState(final String questionId) {
+		final Content content = contentRepository.findOne(questionId);
+		final Session session = sessionRepository.findOne(content.getSessionId());
+		cancelDelayedPiRoundChange(questionId);
+
+		if ("freetext".equals(content.getQuestionType())) {
+			content.setPiRound(0);
+		} else {
+			content.setPiRound(1);
+		}
+
+		content.resetRoundManagementState();
+		answerRepository.deleteByContentId(content.getId());
+		update(content);
+		this.publisher.publishEvent(new PiRoundResetEvent(this, session, content));
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#questionId, 'content', 'owner')")
+	public void setVotingAdmission(final String questionId, final boolean disableVoting) {
+		final Content content = contentRepository.findOne(questionId);
+		final Session session = sessionRepository.findOne(content.getSessionId());
+		content.setVotingDisabled(disableVoting);
+
+		if (!disableVoting && !content.isActive()) {
+			content.setActive(true);
+			update(content);
+		} else {
+			update(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()")
+	@Caching(evict = { @CacheEvict(value = "contents", allEntries = true),
+			@CacheEvict(value = "contentlists", key = "#sessionId"),
+			@CacheEvict(value = "lecturecontentlists", key = "#sessionId"),
+			@CacheEvict(value = "preparationcontentlists", key = "#sessionId"),
+			@CacheEvict(value = "flashcardcontentlists", key = "#sessionId") })
+	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();
+		}
+		for (final Content q : contents) {
+			if (!"flashcard".equals(q.getQuestionType())) {
+				q.setVotingDisabled(disableVoting);
+			}
+		}
+		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.findBySessionId(session.getId());
+		setVotingAdmissionForAllQuestions(session.getId(), disableVoting);
+	}
+
+	private Session getSessionWithAuthCheck(final String sessionKeyword) {
+		final User user = userService.getCurrentUser();
+		final Session session = sessionRepository.findByKeyword(sessionKeyword);
+		if (user == null || session == null || !session.isCreator(user)) {
+			throw new UnauthorizedException();
+		}
+		return session;
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#questionId, 'content', 'owner')")
+	public void deleteAnswers(final String questionId) {
+		final Content content = contentRepository.findOne(questionId);
+		content.resetQuestionState();
+		update(content);
+		answerRepository.deleteByContentId(content.getId());
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<String> getUnAnsweredQuestionIds(final String sessionKey) {
+		final User user = getCurrentUser();
+		final Session session = getSession(sessionKey);
+		return contentRepository.findUnansweredIdsBySessionIdAndUser(session.getId(), 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 = get(questionId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+		return answerRepository.findByQuestionIdUserPiRound(questionId, userService.getCurrentUser(), content.getPiRound());
+	}
+
+	@Override
+	public void getFreetextAnswerAndMarkRead(final String answerId, final User user) {
+		final Answer answer = answerRepository.findOne(answerId);
+		if (answer == null) {
+			throw new NotFoundException();
+		}
+		if (answer.isRead()) {
+			return;
+		}
+		final Session session = sessionRepository.findOne(answer.getSessionId());
+		if (session.isCreator(user)) {
+			answer.setRead(true);
+			answerRepository.save(answer);
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Answer> getAnswers(final String questionId, final int piRound, final int offset, final int limit) {
+		final Content content = contentRepository.findOne(questionId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+		return "freetext".equals(content.getQuestionType())
+				? getFreetextAnswersByQuestionId(questionId, offset, limit)
+						: answerRepository.findByContentIdPiRound(content.getId(), piRound);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Answer> getAnswers(final String questionId, final int offset, final int limit) {
+		final Content content = get(questionId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+		if ("freetext".equals(content.getQuestionType())) {
+			return getFreetextAnswersByQuestionId(questionId, offset, limit);
+		} else {
+			return answerRepository.findByContentIdPiRound(content.getId(), content.getPiRound());
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Answer> getAllAnswers(final String questionId, final int offset, final int limit) {
+		final Content content = get(questionId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+		if ("freetext".equals(content.getQuestionType())) {
+			return getFreetextAnswersByQuestionId(questionId, offset, limit);
+		} else {
+			return answerRepository.findByContentId(content.getId());
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int countAnswersByQuestionIdAndRound(final String questionId) {
+		final Content content = get(questionId);
+		if (content == null) {
+			return 0;
+		}
+
+		if ("freetext".equals(content.getQuestionType())) {
+			return answerRepository.countByContentId(content.getId());
+		} else {
+			return answerRepository.countByContentIdRound(content.getId(), content.getPiRound());
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int countAnswersByQuestionIdAndRound(final String questionId, final int piRound) {
+		final Content content = get(questionId);
+		if (content == null) {
+			return 0;
+		}
+
+		return answerRepository.countByContentIdRound(content.getId(), piRound);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int countTotalAbstentionsByQuestionId(final String questionId) {
+		final Content content = get(questionId);
+		if (content == null) {
+			return 0;
+		}
+
+		return answerRepository.countByContentId(questionId);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int countTotalAnswersByQuestionId(final String questionId) {
+		final Content content = get(questionId);
+		if (content == null) {
+			return 0;
+		}
+
+		return answerRepository.countByContentId(content.getId());
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Answer> getFreetextAnswersByQuestionId(final String questionId, final int offset, final int limit) {
+		final List<Answer> answers = answerRepository.findByContentId(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> getMyAnswersBySessionKey(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 = getBySessionKey(sessionKey);
+		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.findByUserSessionId(userService.getCurrentUser(), session.getId());
+		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 countTotalAnswersBySessionKey(final String sessionKey) {
+		return answerRepository.countBySessionKey(sessionKey);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	@CacheEvict(value = "answerlists", key = "#contentId")
+	public Answer saveAnswer(final String contentId, final Answer answer) {
+		final User user = getCurrentUser();
+		final Content content = get(contentId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+		final Session session = sessionRepository.findOne(content.getSessionId());
+
+		answer.setUser(user.getUsername());
+		answer.setQuestionId(content.getId());
+		answer.setSessionId(session.getId());
+		answer.setQuestionVariant(content.getQuestionVariant());
+		answer.setQuestionValue(content.calculateValue(answer));
+		answer.setTimestamp(new Date().getTime());
+
+		if ("freetext".equals(content.getQuestionType())) {
+			answer.setPiRound(0);
+			imageUtils.generateThumbnailImage(answer);
+			if (content.isFixedAnswer() && content.getText() != null) {
+				answer.setAnswerTextRaw(answer.getAnswerText());
+
+				if (content.isStrictMode()) {
+					content.checkTextStrictOptions(answer);
+				}
+				answer.setQuestionValue(content.evaluateCorrectAnswerFixedText(answer.getAnswerTextRaw()));
+				answer.setSuccessfulFreeTextAnswer(content.isSuccessfulFreeTextAnswer(answer.getAnswerTextRaw()));
+			}
+		} else {
+			answer.setPiRound(content.getPiRound());
+		}
+
+		this.answerQueue.offer(new AnswerQueueElement(session, content, answer, user));
+
+		return answer;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	@CacheEvict(value = "answerlists", allEntries = true)
+	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 = get(answer.getQuestionId());
+		if ("freetext".equals(content.getQuestionType())) {
+			imageUtils.generateThumbnailImage(realAnswer);
+			content.checkTextStrictOptions(realAnswer);
+		}
+		final Session session = sessionRepository.findOne(content.getSessionId());
+		answer.setUser(user.getUsername());
+		answer.setQuestionId(content.getId());
+		answer.setSessionId(session.getId());
+		answerRepository.save(realAnswer);
+		this.publisher.publishEvent(new NewAnswerEvent(this, session, answer, user, content));
+
+		return answer;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	@CacheEvict(value = "answerlists", allEntries = true)
+	public void deleteAnswer(final String questionId, final String answerId) {
+		final Content content = contentRepository.findOne(questionId);
+		if (content == null) {
+			throw new NotFoundException();
+		}
+		final User user = userService.getCurrentUser();
+		final Session session = sessionRepository.findOne(content.getSessionId());
+		if (user == null || session == null || !session.isCreator(user)) {
+			throw new UnauthorizedException();
+		}
+		answerRepository.delete(answerId);
+
+		this.publisher.publishEvent(new DeleteAnswerEvent(this, session, content));
+	}
+
+	/* FIXME: caching */
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	//@Cacheable("lecturecontentlists")
+	public List<Content> getLectureQuestions(final String sessionkey) {
+		final Session session = getSession(sessionkey);
+		final User user = userService.getCurrentUser();
+		if (session.isCreator(user)) {
+			return contentRepository.findBySessionIdOnlyLectureVariant(session.getId());
+		} else {
+			return contentRepository.findBySessionIdOnlyLectureVariantAndActive(session.getId());
+		}
+	}
+
+	/* FIXME: caching */
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	//@Cacheable("flashcardcontentlists")
+	public List<Content> getFlashcards(final String sessionkey) {
+		final Session session = getSession(sessionkey);
+		final User user = userService.getCurrentUser();
+		if (session.isCreator(user)) {
+			return contentRepository.findBySessionIdOnlyFlashcardVariant(session.getId());
+		} else {
+			return contentRepository.findBySessionIdOnlyFlashcardVariantAndActive(session.getId());
+		}
+	}
+
+	/* FIXME: caching */
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	//@Cacheable("preparationcontentlists")
+	public List<Content> getPreparationQuestions(final String sessionkey) {
+		final Session session = getSession(sessionkey);
+		final User user = userService.getCurrentUser();
+		if (session.isCreator(user)) {
+			return contentRepository.findBySessionIdOnlyPreparationVariant(session.getId());
+		} else {
+			return contentRepository.findBySessionIdOnlyPreparationVariantAndActive(session.getId());
+		}
+	}
+
+	@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.findByKeyword(sessionkey);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		return session;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int countLectureQuestions(final String sessionkey) {
+		return contentRepository.countLectureVariantBySessionId(getSession(sessionkey).getId());
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int countFlashcards(final String sessionkey) {
+		return contentRepository.countFlashcardVariantBySessionId(getSession(sessionkey).getId());
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public int countPreparationQuestions(final String sessionkey) {
+		return contentRepository.countPreparationVariantBySessionId(getSession(sessionkey).getId());
+	}
+
+	@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.countBySessionIdLectureVariant(getSession(sessionkey).getId());
+	}
+
+	@Override
+	public Map<String, Object> countAnswersAndAbstentionsInternal(final String questionId) {
+		final Content content = get(questionId);
+		HashMap<String, Object> map = new HashMap<>();
+
+		if (content == null) {
+			return null;
+		}
+
+		map.put("_id", questionId);
+		map.put("answers", answerRepository.countByContentIdRound(content.getId(), content.getPiRound()));
+		map.put("abstentions", answerRepository.countByContentId(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.countBySessionIdPreparationVariant(getSession(sessionkey).getId());
+	}
+
+	/*
+	 * 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.findBySessionIdOnlyFlashcardVariantAndActive(getSession(sessionkey).getId()).size();
+	}
+
+	@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.findUnansweredIdsBySessionIdAndUserOnlyLectureVariant(session.getId(), 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.findUnansweredIdsBySessionIdAndUserOnlyPreparationVariant(session.getId(), user);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public void publishAll(final String sessionkey, final boolean publish) {
+		/* TODO: resolve redundancies */
+		final User user = getCurrentUser();
+		final Session session = getSession(sessionkey);
+		if (!session.isCreator(user)) {
+			throw new UnauthorizedException();
+		}
+		final List<Content> contents = contentRepository.findBySessionId(session.getId());
+		publishQuestions(sessionkey, publish, contents);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	@Caching(evict = { @CacheEvict(value = "contents", allEntries = true),
+			@CacheEvict(value = "contentlists", key = "#sessionId"),
+			@CacheEvict(value = "lecturecontentlists", key = "#sessionId"),
+			@CacheEvict(value = "preparationcontentlists", key = "#sessionId"),
+			@CacheEvict(value = "flashcardcontentlists", key = "#sessionId") })
+	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();
+		}
+		for (final Content content : contents) {
+			content.setActive(publish);
+		}
+		contentRepository.save(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()")
+	@CacheEvict(value = "answerlists", allEntries = true)
+	public void deleteAllQuestionsAnswers(final String sessionkey) {
+		final User user = getCurrentUser();
+		final Session session = getSession(sessionkey);
+		if (!session.isCreator(user)) {
+			throw new UnauthorizedException();
+		}
+
+		final List<Content> contents = contentRepository.findBySessionIdAndVariantAndActive(session.getId());
+		resetContentsRoundState(session.getId(), contents);
+		final List<String> contentIds = contents.stream().map(Content::getId).collect(Collectors.toList());
+		answerRepository.deleteAllAnswersForQuestions(contentIds);
+
+		this.publisher.publishEvent(new DeleteAllQuestionsAnswersEvent(this, session));
+	}
+
+	/* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */
+	@Override
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'owner')")
+	@CacheEvict(value = "answerlists", allEntries = true)
+	public void deleteAllPreparationAnswers(String sessionkey) {
+		final Session session = getSession(sessionkey);
+
+		final List<Content> contents = contentRepository.findBySessionIdAndVariantAndActive(session.getId(), "preparation");
+		resetContentsRoundState(session.getId(), contents);
+		final List<String> contentIds = contents.stream().map(Content::getId).collect(Collectors.toList());
+		answerRepository.deleteAllAnswersForQuestions(contentIds);
+
+		this.publisher.publishEvent(new DeleteAllPreparationAnswersEvent(this, session));
+	}
+
+	/* TODO: Only evict cache entry for the answer's question. This requires some refactoring. */
+	@Override
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'owner')")
+	@CacheEvict(value = "answerlists", allEntries = true)
+	public void deleteAllLectureAnswers(String sessionkey) {
+		final Session session = getSession(sessionkey);
+
+		final List<Content> contents = contentRepository.findBySessionIdAndVariantAndActive(session.getId(), "lecture");
+		resetContentsRoundState(session.getId(), contents);
+		final List<String> contentIds = contents.stream().map(Content::getId).collect(Collectors.toList());
+		answerRepository.deleteAllAnswersForQuestions(contentIds);
+
+		this.publisher.publishEvent(new DeleteAllLectureAnswersEvent(this, session));
+	}
+
+	@Caching(evict = {
+			@CacheEvict(value = "contents", allEntries = true),
+			@CacheEvict(value = "contentlists", key = "#sessionId"),
+			@CacheEvict(value = "lecturecontentlists", key = "#sessionId"),
+			@CacheEvict(value = "preparationcontentlists", key = "#sessionId"),
+			@CacheEvict(value = "flashcardcontentlists", key = "#sessionId") })
+	private void resetContentsRoundState(final String sessionId, final List<Content> contents) {
+		for (final Content q : contents) {
+			/* TODO: Check if setting the sessionId is necessary. */
+			q.setSessionId(sessionId);
+			q.resetQuestionState();
+		}
+		contentRepository.save(contents);
+	}
+
+	@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.findOne(questionId);
+		String imageData = content.getImage();
+
+		if (imageData == null) {
+			imageData = "";
+		}
+
+		return imageData;
+	}
+
+	@Override
+	public String getQuestionFcImage(String questionId) {
+		Content content = contentRepository.findOne(questionId);
+		String imageData = content.getFcImage();
+
+		if (imageData == null) {
+			imageData = "";
+		}
+
+		return imageData;
+	}
+}
diff --git a/src/main/java/de/thm/arsnova/services/EntityService.java b/src/main/java/de/thm/arsnova/services/EntityService.java
new file mode 100644
index 0000000000000000000000000000000000000000..28315ebcff0d05ea713d2e6a8d114365118cf92b
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/EntityService.java
@@ -0,0 +1,74 @@
+package de.thm.arsnova.services;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import de.thm.arsnova.entities.Entity;
+import de.thm.arsnova.entities.serialization.View;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.access.prepost.PreFilter;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+public class EntityService<T extends Entity> {
+	protected Class<T> type;
+	protected CrudRepository<T, String> repository;
+	private ObjectMapper objectMapper;
+
+	public EntityService(Class<T> type, CrudRepository<T, String> repository, ObjectMapper objectMapper) {
+		this.type = type;
+		this.repository = repository;
+		this.objectMapper = objectMapper;
+	}
+
+	@PreAuthorize("hasPermission(#id, #this.this.getTypeName(), 'read')")
+	public T get(final String id) {
+		return repository.findOne(id);
+	}
+
+	@PreAuthorize("hasPermission(#entity, 'create')")
+	public T create(final T entity) {
+		if (entity.getId() != null || entity.getRevision() != null) {
+			throw new IllegalArgumentException("Entity is not new.");
+		}
+		return repository.save(entity);
+	}
+
+	@PreAuthorize("hasPermission(#oldEntity, 'update')")
+	public T update(final T oldEntity, final T newEntity) {
+		newEntity.setId(oldEntity.getId());
+		return repository.save(newEntity);
+	}
+
+	@PreAuthorize("hasPermission(#entity, 'update')")
+	public T patch(final T entity, final Map<String, Object> changes) throws IOException {
+		ObjectReader reader = objectMapper.readerForUpdating(entity).withView(View.Public.class);
+		JsonNode tree = objectMapper.valueToTree(changes);
+		reader.readValue(tree);
+
+		return repository.save(entity);
+	}
+
+	@PreFilter(value = "hasPermission(filterObject, 'update')", filterTarget = "entities")
+	public Iterable<T> patch(final Collection<T> entities, final Map<String, Object> changes) throws IOException {
+		JsonNode tree = objectMapper.valueToTree(changes);
+		for (T entity : entities) {
+			ObjectReader reader = objectMapper.readerForUpdating(entity).withView(View.Public.class);
+			reader.readValue(tree);
+		}
+
+		return repository.save(entities);
+	}
+
+	@PreAuthorize("hasPermission(#entity, 'delete')")
+	public void delete(final T entity) {
+		repository.delete(entity);
+	}
+
+	public String getTypeName() {
+		return type.getSimpleName().toLowerCase();
+	}
+}
diff --git a/src/main/java/de/thm/arsnova/services/FeedbackService.java b/src/main/java/de/thm/arsnova/services/FeedbackService.java
index 0a4a69599944a7f9d26df23104dcbda994fbf955..efa40e16ecf997b44ddf02559e34f50568014d52 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 cleanFeedbackVotesBySessionKey(String keyword, int cleanupFeedbackDelayInMins);
 
-		if (Math.abs(count) < Z_THRESHOLD) {
-			throw new NoContentException();
-		}
-		return sum / count;
-	}
+	Feedback getBySessionKey(String keyword);
 
-	@Override
-	public long getAverageFeedbackRounded(final String sessionkey) {
-		return Math.round(getAverageFeedback(sessionkey));
-	}
+	int countFeedbackBySessionKey(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 calculateAverageFeedback(String sessionkey);
 
-		this.publisher.publishEvent(new NewFeedbackEvent(this, session));
-		return true;
-	}
+	long calculateRoundedAverageFeedback(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 save(String keyword, int value, User user);
 
-	@Override
-	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
-		this.publisher = publisher;
-	}
+	Integer getBySessionKeyAndUser(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 0000000000000000000000000000000000000000..3290cc2b66e2f7ae40a6ddf95a510617d95405a9
--- /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.cleanVotes(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 cleanFeedbackVotesBySessionKey(final String keyword, final int cleanupFeedbackDelayInMins) {
+		final Session session = sessionRepository.findByKeyword(keyword);
+		List<User> affectedUsers = feedbackStorage.cleanVotesBySession(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 getBySessionKey(final String keyword) {
+		final Session session = sessionRepository.findByKeyword(keyword);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		return feedbackStorage.getBySession(session);
+	}
+
+	@Override
+	public int countFeedbackBySessionKey(final String keyword) {
+		final Feedback feedback = this.getBySessionKey(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 calculateAverageFeedback(final String sessionkey) {
+		final Session session = sessionRepository.findByKeyword(sessionkey);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		final Feedback feedback = feedbackStorage.getBySession(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 calculateRoundedAverageFeedback(final String sessionkey) {
+		return Math.round(calculateAverageFeedback(sessionkey));
+	}
+
+	@Override
+	public boolean save(final String keyword, final int value, final User user) {
+		final Session session = sessionRepository.findByKeyword(keyword);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		feedbackStorage.save(session, value, user);
+
+		this.publisher.publishEvent(new NewFeedbackEvent(this, session));
+		return true;
+	}
+
+	@Override
+	public Integer getBySessionKeyAndUser(final String keyword, final User user) {
+		final Session session = sessionRepository.findByKeyword(keyword);
+		if (session == null) {
+			throw new NotFoundException();
+		}
+		return feedbackStorage.getBySessionAndUser(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 0000000000000000000000000000000000000000..279f703e778036684eb9c965594f642ca55bd506
--- /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 getBySession(Session session);
+	Integer getBySessionAndUser(Session session, User u);
+	void save(Session session, int value, User user);
+	Map<Session, List<User>> cleanVotes(int cleanupFeedbackDelay);
+	List<User> cleanVotesBySession(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 83%
rename from src/main/java/de/thm/arsnova/FeedbackStorage.java
rename to src/main/java/de/thm/arsnova/services/FeedbackStorageServiceImpl.java
index bb72be0d5e6bc89122922919464c18aff19ef98f..9c9f3940e3bd56ac6d1503a28f2d4ab3f0c20e76 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,7 +62,8 @@ public class FeedbackStorage {
 	private final Map<Session, Map<User, FeedbackStorageObject>> data =
 			new ConcurrentHashMap<>();
 
-	public Feedback getFeedback(final Session session) {
+	@Override
+	public Feedback getBySession(final Session session) {
 		int a = 0;
 		int b = 0;
 		int c = 0;
@@ -91,7 +94,8 @@ public class FeedbackStorage {
 		return new Feedback(a, b, c, d);
 	}
 
-	public Integer getMyFeedback(final Session session, final User u) {
+	@Override
+	public Integer getBySessionAndUser(final Session session, final User u) {
 		if (data.get(session) == null) {
 			return null;
 		}
@@ -105,8 +109,9 @@ public class FeedbackStorage {
 		return null;
 	}
 
+	@Override
 	@Transactional(isolation = Isolation.READ_COMMITTED)
-	public void saveFeedback(final Session session, final int value, final User user) {
+	public void save(final Session session, final int value, final User user) {
 		if (data.get(session) == null) {
 			data.put(session, new ConcurrentHashMap<User, FeedbackStorageObject>());
 		}
@@ -114,12 +119,13 @@ 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) {
+	public Map<Session, List<User>> cleanVotes(final int cleanupFeedbackDelay) {
 		final Map<Session, List<User>> removedFeedbackOfUsersInSession = new HashMap<>();
 		for (final Session session : data.keySet()) {
 			if (!session.getFeatures().isLiveClicker()) {
-				List<User> affectedUsers = cleanFeedbackVotesInSession(session, cleanupFeedbackDelay);
+				List<User> affectedUsers = cleanVotesBySession(session, cleanupFeedbackDelay);
 				if (!affectedUsers.isEmpty()) {
 					removedFeedbackOfUsersInSession.put(session, affectedUsers);
 				}
@@ -128,8 +134,9 @@ public class FeedbackStorage {
 		return removedFeedbackOfUsersInSession;
 	}
 
+	@Override
 	@Transactional(isolation = Isolation.READ_COMMITTED)
-	public List<User> cleanFeedbackVotesInSession(final Session session, final int cleanupFeedbackDelayInMins) {
+	public List<User> cleanVotesBySession(final Session session, final int cleanupFeedbackDelayInMins) {
 		final long timelimitInMillis = TimeUnit.MILLISECONDS.convert(cleanupFeedbackDelayInMins, TimeUnit.MINUTES);
 		final Date maxAllowedTime = new Date(System.currentTimeMillis() - timelimitInMillis);
 		final boolean forceClean = cleanupFeedbackDelayInMins == 0;
@@ -142,7 +149,7 @@ public class FeedbackStorage {
 				final User user = entry.getKey();
 				final FeedbackStorageObject feedback = entry.getValue();
 				final boolean timeIsUp = feedback.getTimestamp().before(maxAllowedTime);
-				final boolean isAwayFeedback = getMyFeedback(session, user).equals(Feedback.FEEDBACK_AWAY);
+				final boolean isAwayFeedback = getBySessionAndUser(session, user).equals(Feedback.FEEDBACK_AWAY);
 				if (forceClean || timeIsUp && !isAwayFeedback) {
 					sessionFeedbacks.remove(user);
 					affectedUsers.add(user);
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 9b4e4f97bf40433a750034b28a3ee53d03ee19ef..0000000000000000000000000000000000000000
--- 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 dc0210164abc326739e2a300fcc636f4ff7e710c..0000000000000000000000000000000000000000
--- 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 a9488e4134b60f43be582c9a6f521bff83010ff4..0000000000000000000000000000000000000000
--- 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 c6796f7cebdf90210ac34e4c197242d1eb9ccd7c..0000000000000000000000000000000000000000
--- 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 86f72807b887166dd54e9f0a680591b9841d87e2..0000000000000000000000000000000000000000
--- 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 37b4d0b6076aa568cab3f86b420eb8b5aee729b0..0000000000000000000000000000000000000000
--- 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 674525ec2afe51c9fd917f0d1a992710dc2eb9cf..177e721d586c6ac29933e6d5eef0cff76dc46a56 100644
--- a/src/main/java/de/thm/arsnova/services/MotdService.java
+++ b/src/main/java/de/thm/arsnova/services/MotdService.java
@@ -9,189 +9,53 @@
  *
  * 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 getByKey(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);
+
+	List<Motd> getCurrentSessionMotds(final Date clientdate, final String sessionkey);
+
+	List<Motd> filterMotdsByDate(List<Motd> list, Date clientdate);
+
+	List<Motd> filterMotdsByList(List<Motd> list, MotdList motdList);
+
+	void delete(Motd motd);
+
+	void deleteBySessionKey(final String sessionkey, Motd motd);
+
+	Motd save(Motd motd);
+
+	Motd save(final String sessionkey, final Motd motd);
+
+	Motd update(Motd motd);
+
+	Motd update(final String sessionkey, Motd motd);
+
+	MotdList getMotdListByUsername(final String username);
+
+	MotdList saveMotdList(MotdList motdList);
+
+	MotdList updateMotdList(MotdList motdList);
 }
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 0000000000000000000000000000000000000000..4c699c696ec3da47eb0452e402fdd0d98cb2121e
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/MotdServiceImpl.java
@@ -0,0 +1,229 @@
+/*
+ * 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.Qualifier;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Service;
+
+import 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 extends EntityService<Motd> implements MotdService {
+	private UserService userService;
+
+	private SessionService sessionService;
+
+	private MotdRepository motdRepository;
+
+	private MotdListRepository motdListRepository;
+
+	public MotdServiceImpl(
+			MotdRepository repository,
+			MotdListRepository motdListRepository,
+			UserService userService,
+			SessionService sessionService,
+			@Qualifier("defaultJsonMessageConverter") MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) {
+		super(Motd.class, repository, jackson2HttpMessageConverter.getObjectMapper());
+		this.motdRepository = repository;
+		this.motdListRepository = motdListRepository;
+		this.userService = userService;
+		this.sessionService = sessionService;
+	}
+
+  @Override
+  @PreAuthorize("isAuthenticated()")
+  public Motd getByKey(final String key) {
+    return motdRepository.findByKey(key);
+  }
+
+  @Override
+  @PreAuthorize("hasPermission('', 'motd', 'admin')")
+  public List<Motd> getAdminMotds() {
+    return motdRepository.findGlobalForAdmin();
+  }
+
+	@Override
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'owner')")
+	public List<Motd> getAllSessionMotds(final String sessionkey) {
+		return motdRepository.findBySessionKey(sessionkey);
+	}
+
+	@Override
+	@Cacheable(cacheNames = "motds", key = "('session').concat(#sessionkey)")
+	public List<Motd> getCurrentSessionMotds(final Date clientdate, final String sessionkey) {
+		final List<Motd> motds = motdRepository.findBySessionKey(sessionkey);
+		return filterMotdsByDate(motds, clientdate);
+	}
+
+	@Override
+	@Cacheable(cacheNames = "motds", key = "#audience")
+	public List<Motd> getCurrentMotds(final Date clientdate, final String audience) {
+		final List<Motd> motds;
+		switch (audience) {
+			case "all": motds = motdRepository.findGlobalForAll(); break;
+			case "loggedIn": motds = motdRepository.findGlobalForLoggedIn(); break;
+			case "students": motds = motdRepository.findForStudents(); break;
+			case "tutors": motds = motdRepository.findGlobalForTutors(); break;
+			default: throw new IllegalArgumentException("Invalid audience.");
+		}
+
+		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("hasPermission('', 'motd', 'admin')")
+	public Motd save(final Motd motd) {
+		return createOrUpdateMotd(motd);
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'owner')")
+	public Motd save(final String sessionkey, final Motd motd) {
+		Session session = sessionService.getByKey(sessionkey);
+		motd.setSessionId(session.getId());
+
+		return createOrUpdateMotd(motd);
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(1,'motd','admin')")
+	public Motd update(final Motd motd) {
+		return createOrUpdateMotd(motd);
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'owner')")
+	public Motd update(final String sessionkey, final Motd motd) {
+		return createOrUpdateMotd(motd);
+	}
+
+	@CacheEvict(cacheNames = "motds", key = "#motd.audience.concat(#motd.sessionkey)")
+	private Motd createOrUpdateMotd(final Motd motd) {
+		if (motd.getMotdkey() != null) {
+			Motd oldMotd = motdRepository.findByKey(motd.getMotdkey());
+			if (!(motd.getId().equals(oldMotd.getId()) && motd.getSessionkey().equals(oldMotd.getSessionkey())
+					&& motd.getAudience().equals(oldMotd.getAudience()))) {
+				throw new BadRequestException();
+			}
+		}
+
+		if (null != motd.getId()) {
+			Motd oldMotd = get(motd.getId());
+			motd.setMotdkey(oldMotd.getMotdkey());
+		} else {
+			motd.setMotdkey(sessionService.generateKey());
+		}
+		save(motd);
+
+		return motdRepository.save(motd);
+	}
+
+	@Override
+	@PreAuthorize("hasPermission('', 'motd', 'admin')")
+	@CacheEvict(cacheNames = "motds", key = "#motd.audience.concat(#motd.sessionkey)")
+	public void delete(Motd motd) {
+		motdRepository.delete(motd);
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'owner')")
+	public void deleteBySessionKey(final String sessionkey, Motd motd) {
+		motdRepository.delete(motd);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	@Cacheable(cacheNames = "motdlist", key = "#username")
+	public MotdList getMotdListByUsername(final String username) {
+		final User user = userService.getCurrentUser();
+		if (username.equals(user.getUsername()) && !"guest".equals(user.getType())) {
+			return motdListRepository.findByUsername(username);
+		}
+		return null;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	@CachePut(cacheNames = "motdlist", key = "#motdList.username")
+	public MotdList saveMotdList(MotdList motdList) {
+		final User user = userService.getCurrentUser();
+		if (user.getUsername().equals(motdList.getUsername())) {
+			return motdListRepository.save(motdList);
+		}
+		return null;
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public MotdList updateMotdList(MotdList motdList) {
+		final User user = userService.getCurrentUser();
+		if (user.getUsername().equals(motdList.getUsername())) {
+			return motdListRepository.save(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 0257be71d75230b9d3dcfc2a33ea491d571f3f06..58f6877910dfcee6f6f8b885ba787e562dd4332a 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 getByKey(String keyword);
+
+	Session getForAdmin(final String keyword);
+
+	Session getInternal(String keyword, User user);
+
+	Session save(Session session);
+
+	boolean isKeyAvailable(String keyword);
+
+	String generateKey();
+
+	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 countSessionsByCourses(List<Course> courses);
+
+	int activeUsers(String sessionkey);
+
+	Session setActive(String sessionkey, Boolean lock);
+
+	Session join(String keyword, UUID socketId);
+
+	Session update(String sessionkey, Session session);
+
+	Session updateCreator(String sessionkey, String newCreator);
+
+	Session updateInternal(Session session, User user);
+
+	int[] deleteCascading(Session session);
+
+	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 getFeatures(String sessionkey);
 
-	@Autowired
-	private SessionRepository sessionRepository;
+	SessionFeature updateFeatures(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 0000000000000000000000000000000000000000..78dbf631ee96cd1fce1a692db38cb2b6b5ddbee4
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/SessionServiceImpl.java
@@ -0,0 +1,612 @@
+/*
+ * 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.persistance.AnswerRepository;
+import de.thm.arsnova.persistance.CommentRepository;
+import de.thm.arsnova.persistance.ContentRepository;
+import de.thm.arsnova.persistance.LogEntryRepository;
+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.ektorp.UpdateConflictException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Caching;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+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 extends EntityService<Session> implements SessionService, ApplicationEventPublisherAware {
+	private static final long SESSION_INACTIVITY_CHECK_INTERVAL_MS = 30 * 60 * 1000L;
+
+	private static final Logger logger = LoggerFactory.getLogger(SessionServiceImpl.class);
+
+	private LogEntryRepository dbLogger;
+
+	private SessionRepository sessionRepository;
+
+	private ContentRepository contentRepository;
+
+	private AnswerRepository answerRepository;
+
+	private CommentRepository commentRepository;
+
+	private VisitedSessionRepository visitedSessionRepository;
+
+	private UserService userService;
+
+	private FeedbackService feedbackService;
+
+	private ScoreCalculatorFactory scoreCalculatorFactory;
+
+	private ConnectorClient connectorClient;
+
+	private ImageUtils imageUtils;
+
+	@Value("${session.guest-session.cleanup-days:0}")
+	private int guestSessionInactivityThresholdDays;
+
+	@Value("${pp.logofilesize_b}")
+	private int uploadFileSizeByte;
+
+	private ApplicationEventPublisher publisher;
+
+	public SessionServiceImpl(
+			SessionRepository repository,
+			ContentRepository contentRepository,
+			AnswerRepository answerRepository,
+			CommentRepository commentRepository,
+			VisitedSessionRepository visitedSessionRepository,
+			LogEntryRepository dbLogger,
+			UserService userService,
+			FeedbackService feedbackService,
+			ScoreCalculatorFactory scoreCalculatorFactory,
+			ImageUtils imageUtils,
+			@Qualifier("defaultJsonMessageConverter") MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) {
+		super(Session.class, repository, jackson2HttpMessageConverter.getObjectMapper());
+		this.sessionRepository = repository;
+		this.contentRepository = contentRepository;
+		this.answerRepository = answerRepository;
+		this.commentRepository = commentRepository;
+		this.visitedSessionRepository = visitedSessionRepository;
+		this.dbLogger = dbLogger;
+		this.userService = userService;
+		this.feedbackService = feedbackService;
+		this.scoreCalculatorFactory = scoreCalculatorFactory;
+		this.imageUtils = imageUtils;
+	}
+
+	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());
+		}
+	}
+
+	@Autowired(required = false)
+	public void setConnectorClient(ConnectorClient connectorClient) {
+		this.connectorClient = connectorClient;
+	}
+
+	@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;
+			int totalCount[] = new int[] {0, 0, 0};
+			List<Session> inactiveSessions = sessionRepository.findInactiveGuestSessionsMetadata(lastActivityBefore);
+			for (Session session : inactiveSessions) {
+				int[] count = deleteCascading(session);
+				totalCount[0] += count[0];
+				totalCount[1] += count[1];
+				totalCount[2] += count[2];
+			}
+
+			if (!inactiveSessions.isEmpty()) {
+				logger.info("Deleted {} inactive guest sessions.", inactiveSessions.size());
+				dbLogger.log("cleanup", "type", "session",
+						"sessionCount", inactiveSessions.size(),
+						"questionCount", totalCount[1],
+						"answerCount", totalCount[2],
+						"commentCount", totalCount[3]);
+			}
+		}
+	}
+
+	@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 join(final String keyword, final UUID socketId) {
+		/* Socket.IO solution */
+
+		Session session = null != keyword ? sessionRepository.findByKeyword(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())) {
+			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;
+	}
+
+	@CachePut(value = "sessions")
+	private Session updateSessionOwnerActivity(final Session session) {
+		try {
+			/* Do not clutter CouchDB. Only update once every 3 hours. */
+			if (session.getLastOwnerActivity() > System.currentTimeMillis() - 3 * 3600000) {
+				return session;
+			}
+
+			session.setLastOwnerActivity(System.currentTimeMillis());
+			save(session);
+
+			return session;
+		} catch (final UpdateConflictException e) {
+			logger.error("Failed to update lastOwnerActivity for session {}.", session, e);
+			return session;
+		}
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public Session getByKey(final String keyword) {
+		final User user = userService.getCurrentUser();
+		return this.getInternal(keyword, user);
+	}
+
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'owner')")
+	public Session getForAdmin(final String keyword) {
+		return sessionRepository.findByKeyword(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 getInternal(final String keyword, final User user) {
+		final Session session = sessionRepository.findByKeyword(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.findByUsername(username, 0, 0);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<Session> getMySessions(final int offset, final int limit) {
+		return sessionRepository.findByUser(userService.getCurrentUser(), offset, limit);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<SessionInfo> getPublicPoolSessionsInfo() {
+		return sessionRepository.findInfosForPublicPool();
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<SessionInfo> getMyPublicPoolSessionsInfo() {
+		return sessionRepository.findInfosForPublicPoolByUser(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.findVisitedByUsername(userService.getCurrentUser().getUsername(), offset, limit);
+	}
+
+	@Override
+	@PreAuthorize("hasPermission('', 'motd', 'admin')")
+	public List<Session> getUserVisitedSessions(String username) {
+		return sessionRepository.findVisitedByUsername(username, 0, 0);
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public List<SessionInfo> getMyVisitedSessionsInfo(final int offset, final int limit) {
+		return sessionRepository.findInfoForVisitedByUser(userService.getCurrentUser(), offset, limit);
+	}
+
+	@Override
+	@PreAuthorize("hasPermission('', 'session', 'create')")
+	@Caching(evict = @CacheEvict(cacheNames = "sessions", key = "#result.keyword"))
+	public Session save(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);
+
+		session.setKeyword(generateKey());
+		session.setCreator(userService.getCurrentUser().getUsername());
+		session.setActive(true);
+		session.setFeedbackLock(false);
+
+		final Session result = save(session);
+		this.publisher.publishEvent(new NewSessionEvent(this, result));
+		return result;
+	}
+
+	@Override
+	public boolean isKeyAvailable(final String keyword) {
+		return getByKey(keyword) == null;
+	}
+
+	@Override
+	public String generateKey() {
+		final int low = 10000000;
+		final int high = 100000000;
+		final String keyword = String
+				.valueOf((int) (Math.random() * (high - low) + low));
+
+		if (isKeyAvailable(keyword)) {
+			return keyword;
+		}
+		return generateKey();
+	}
+
+	@Override
+	public int countSessionsByCourses(final List<Course> courses) {
+		final List<Session> sessions = sessionRepository.findSessionsByCourses(courses);
+		if (sessions == null) {
+			return 0;
+		}
+		return sessions.size();
+	}
+
+	@Override
+	public int activeUsers(final String sessionkey) {
+		return userService.getUsersBySessionKey(sessionkey).size();
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'owner')")
+	public Session setActive(final String sessionkey, final Boolean lock) {
+		final Session session = sessionRepository.findByKeyword(sessionkey);
+		session.setActive(lock);
+		this.publisher.publishEvent(new StatusSessionEvent(this, session));
+		sessionRepository.save(session);
+
+		return session;
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#session, 'owner')")
+	@CachePut(value = "sessions", key = "#session")
+	public Session update(final String sessionkey, final Session session) {
+		final Session existingSession = sessionRepository.findByKeyword(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());
+
+		sessionRepository.save(existingSession);
+
+		return session;
+	}
+
+	@Override
+	@PreAuthorize("hasPermission('', 'motd', 'admin')")
+	@Caching(evict = { @CacheEvict("sessions"), @CacheEvict(cacheNames = "sessions", key = "#sessionkey.keyword") })
+	public Session updateCreator(String sessionkey, String newCreator) {
+		final Session session = sessionRepository.findByKeyword(sessionkey);
+		if (session == null) {
+			throw new NullPointerException("Could not load session " + sessionkey + ".");
+		}
+
+		session.setCreator(newCreator);
+		save(session);
+
+		return save(session);
+	}
+
+	/*
+	 * 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 updateInternal(final Session session, final User user) {
+		if (session.isCreator(user)) {
+			sessionRepository.save(session);
+			return session;
+		}
+		return null;
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#session, 'owner')")
+	@CacheEvict("sessions")
+	public int[] deleteCascading(final Session session) {
+		int[] count = new int[] {0, 0, 0};
+		List<String> contentIds = contentRepository.findIdsBySessionId(session.getId());
+		count[2] = commentRepository.deleteBySessionId(session.getId());
+		count[1] = answerRepository.deleteByContentIds(contentIds);
+		count[0] = contentRepository.deleteBySessionId(session.getId());
+		sessionRepository.delete(session);
+		logger.debug("Deleted session document {} and related data.", session.getId());
+		dbLogger.log("delete", "type", "session", "id", session.getId());
+
+		this.publisher.publishEvent(new DeleteSessionEvent(this, session));
+
+		return count;
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'read')")
+	public ScoreStatistics getLearningProgress(final String sessionkey, final String type, final String questionVariant) {
+		final Session session = sessionRepository.findByKeyword(sessionkey);
+		ScoreCalculator scoreCalculator = scoreCalculatorFactory.create(type, questionVariant);
+		return scoreCalculator.getCourseProgress(session);
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'read')")
+	public ScoreStatistics getMyLearningProgress(final String sessionkey, final String type, final String questionVariant) {
+		final Session session = sessionRepository.findByKeyword(sessionkey);
+		final User user = userService.getCurrentUser();
+		ScoreCalculator scoreCalculator = scoreCalculatorFactory.create(type, questionVariant);
+		return scoreCalculator.getMyProgress(session, user);
+	}
+
+	@Override
+	@PreAuthorize("hasPermission('', 'session', 'create')")
+	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("hasPermission(#sessionkey, 'session', 'owner')")
+	public ImportExportSession exportSession(String sessionkey, Boolean withAnswerStatistics, Boolean withFeedbackQuestions) {
+		return sessionRepository.exportSession(sessionkey, withAnswerStatistics, withFeedbackQuestions);
+	}
+
+	@Override
+	@PreAuthorize("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
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'read')")
+	public SessionFeature getFeatures(String sessionkey) {
+		return sessionRepository.findByKeyword(sessionkey).getFeatures();
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'owner')")
+	public SessionFeature updateFeatures(String sessionkey, SessionFeature features) {
+		final Session session = sessionRepository.findByKeyword(sessionkey);
+		final User user = userService.getCurrentUser();
+		session.setFeatures(features);
+		this.publisher.publishEvent(new FeatureChangeEvent(this, session));
+		sessionRepository.save(session);
+
+		return session.getFeatures();
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'owner')")
+	public boolean lockFeedbackInput(String sessionkey, Boolean lock) {
+		final Session session = sessionRepository.findByKeyword(sessionkey);
+		final User user = userService.getCurrentUser();
+		if (!lock) {
+			feedbackService.cleanFeedbackVotesBySessionKey(sessionkey, 0);
+		}
+
+		session.setFeedbackLock(lock);
+		this.publisher.publishEvent(new LockFeedbackEvent(this, session));
+		sessionRepository.save(session);
+
+		return session.getFeedbackLock();
+	}
+
+	@Override
+	@PreAuthorize("hasPermission(#sessionkey, 'session', 'owner')")
+	public boolean flipFlashcards(String sessionkey, Boolean flip) {
+		final Session session = sessionRepository.findByKeyword(sessionkey);
+		final User user = userService.getCurrentUser();
+		session.setFlipFlashcards(flip);
+		this.publisher.publishEvent(new FlipFlashcardsEvent(this, session));
+		sessionRepository.save(session);
+
+		return 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 ddf091b7250fcb066dd4661e399c479e69d33f97..f2742a5cdf74e912021c15d5e96923e092d35e23 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 0000000000000000000000000000000000000000..0d57a8443069a2094972a222e1366135d02ec4f5
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/StatisticsServiceImpl.java
@@ -0,0 +1,60 @@
+/*
+ * 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.cache.annotation.Cacheable;
+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 {
+	private StatisticsRepository statisticsRepository;
+
+	private UserService userService;
+
+	public StatisticsServiceImpl(
+			StatisticsRepository repository,
+			UserService userService) {
+		this.statisticsRepository = repository;
+		this.userService = userService;
+	}
+
+	private Statistics statistics = new Statistics();
+
+	@Scheduled(initialDelay = 0, fixedRate = 10000)
+	private void refreshStatistics() {
+		statistics = loadStatistics();
+	}
+
+	@Cacheable("statistics")
+	private Statistics loadStatistics() {
+		return 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 fe8fdbc632103524944114306e8539c1c49eb44d..d48a3bf38ce6db2d4e7d017afafb82c8d3efc8fe 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> getUsersBySessionKey(String keyword);
 
-	@Autowired
-	private UserRepository userRepository;
+	String getSessionByUsername(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 getByUsername(String username);
 
-	@Value("${security.user-db.reset-password-path}")
-	private String resetPasswordPath;
+	DbUser create(String username, String password);
 
-	@Value("${mail.sender.address}")
-	private String mailSenderAddress;
+	DbUser update(DbUser dbUser);
 
-	@Value("${mail.sender.name}")
-	private String mailSenderName;
+	DbUser deleteByUsername(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 0000000000000000000000000000000000000000..2adce602e2084a0f5dd9f93d5dddaf60fa3f5300
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/services/UserServiceImpl.java
@@ -0,0 +1,547 @@
+/*
+ * 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.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<>();
+
+	private UserRepository userRepository;
+
+	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>());
+	}
+
+	public UserServiceImpl(UserRepository repository, JavaMailSender mailSender) {
+		this.userRepository = repository;
+		this.mailSender = mailSender;
+	}
+
+	@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> getUsersBySessionKey(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 getSessionByUsername(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 getByUsername(String username) {
+		return userRepository.findByUsername(username.toLowerCase());
+	}
+
+	@Override
+	public DbUser create(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.findByUsername(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.save(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 update(DbUser dbUser) {
+		if (null != dbUser.getId()) {
+			return userRepository.save(dbUser);
+		}
+
+		return null;
+	}
+
+	@Override
+	public DbUser deleteByUsername(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 = getByUsername(username);
+		if (null == dbUser) {
+			throw new NotFoundException();
+		}
+
+		userRepository.delete(dbUser);
+
+		return dbUser;
+	}
+
+	@Override
+	public void initiatePasswordReset(String username) {
+		DbUser dbUser = getByUsername(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.save(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);
+			update(dbUser);
+
+			return false;
+		}
+
+		dbUser.setPassword(encodePassword(password));
+		dbUser.setPasswordResetKey(null);
+		if (null == update(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 37b6c63974f639cca4e76bdf8563b25f5e9dada7..2e28d96b4507fffdb6726b1f1516a205a8be3fc9 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 32b5b6f52d6d89331a5eee267292a8543c9bfdff..88ecf31bdcebf055ae412bd02d37d15966b187ed 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 40a3cf091767162acab5f1ccce971564b8fe3291..f7fdcb69d444090abfbd08f2232362a30cdc23da 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 21f2fbd3d966ccd6b92749ddde815fb184a15fae..309c9bef05500783130a7796d139a6e9e64bc270 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 e3f0f3174f956c0712ac5002faa7349f5416f12d..8dbaa8a7a817b1d61ca3b967f82db7e23d18cd9d 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 072682946fc1b6c489c79b38edbf9cfc4155303f..f7125e807491fea6a71b8c7e4b1681f65b15a6de 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 55%
rename from src/main/java/de/thm/arsnova/domain/LearningProgressFactory.java
rename to src/main/java/de/thm/arsnova/services/score/ScoreCalculatorFactoryImpl.java
index a5596c0b73bafa8a37d2ed287ce4b9e66853606b..c68c2a1766ed318b0369e3cf2a812f92c95b14b2 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
@@ -56,82 +56,82 @@ public class LearningProgressFactory implements NovaEventVisitor, ILearningProgr
 	@Override
 	public void visit(DeleteCommentEvent deleteCommentEvent) { }
 
-	@CacheEvict(value = "learningprogress", key = "#event.Session")
+	@CacheEvict(value = "score", 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")
+	@CacheEvict(value = "score", 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")
+	@CacheEvict(value = "score", 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")
+	@CacheEvict(value = "score", 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")
+	@CacheEvict(value = "score", 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")
+	@CacheEvict(value = "score", 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")
+	@CacheEvict(value = "score", 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")
+	@CacheEvict(value = "score", 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")
+	@CacheEvict(value = "score", 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")
+	@CacheEvict(value = "score", 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")
+	@CacheEvict(value = "score", 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")
+	@CacheEvict(value = "score", 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")
+	@CacheEvict(value = "score", 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 2a11b6d4cb61ea2d1c5033509ee8abbb2cd16fe5..ded6ef1d05dcc215cd3026115c1d703a0d4aab43 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 dddd611dfa4590df07ecbeeb13ad27eb6633251e..9a31de8e2d0b1b7eb0617d79527d46599aee12b5 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 62%
rename from src/main/java/de/thm/arsnova/domain/VariantLearningProgress.java
rename to src/main/java/de/thm/arsnova/services/score/VariantScoreCalculator.java
index 7e1c069701fa3fe336d6f7a1042185a69d080ac0..003125a0e3078a9acd41419fd1f2a97037af9b87 100644
--- a/src/main/java/de/thm/arsnova/domain/VariantLearningProgress.java
+++ b/src/main/java/de/thm/arsnova/services/score/VariantScoreCalculator.java
@@ -15,29 +15,35 @@
  * 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;
+import org.springframework.cache.annotation.Cacheable;
 
 /**
- * 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;
 	}
 
-	private void loadProgress(final Session session) {
+	@Cacheable("score")
+	private Score loadProgress(final Session session) {
+		return sessionStatisticsRepository.getLearningProgress(session);
+	}
+
+	private void refreshProgress(final Session session) {
 		this.courseScore = sessionStatisticsRepository.getLearningProgress(session);
 	}
 
@@ -46,17 +52,17 @@ abstract class VariantLearningProgress implements LearningProgress {
 	}
 
 	@Override
-	public LearningProgressValues getCourseProgress(Session session) {
-		this.loadProgress(session);
+	public ScoreStatistics getCourseProgress(Session session) {
+		this.refreshProgress(session);
 		this.filterVariant();
 		return this.createCourseProgress();
 	}
 
-	protected abstract LearningProgressValues createCourseProgress();
+	protected abstract ScoreStatistics createCourseProgress();
 
 	@Override
-	public LearningProgressValues getMyProgress(Session session, User user) {
-		this.loadProgress(session);
+	public ScoreStatistics getMyProgress(Session session, User user) {
+		this.refreshProgress(session);
 		this.filterVariant();
 		return this.createMyProgress(user);
 	}
@@ -67,6 +73,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 6349cc8444a3800897cbb49275e848bfbcd10643..4c676add1d22953286321535c6f22df2549a2823 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 6f9fcf448f962b93d83d52b59f2a5b593c83a479..1a764ce6cb711a4d2ff5b1ff80506cc0727be0ea 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 313e330cd6628d70f771b8ee14b5cd83bce79cbe..800b9314d17d3360a04b7e7ede303df35a725715 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 85%
rename from src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java
rename to src/main/java/de/thm/arsnova/websocket/ArsnovaSocketioServerImpl.java
index b348dc32cc48c729bde2d61fbad4c0b28c0d26ff..8cb3664e12550816a022ed8d0a4d85c606983542 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;
@@ -29,24 +29,30 @@ import com.corundumstudio.socketio.listener.DisconnectListener;
 import com.corundumstudio.socketio.protocol.Packet;
 import com.corundumstudio.socketio.protocol.PacketType;
 import de.thm.arsnova.entities.Comment;
+import de.thm.arsnova.entities.ScoreOptions;
 import de.thm.arsnova.entities.User;
-import de.thm.arsnova.entities.transport.LearningProgressOptions;
 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.CommentService;
+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;
 import org.springframework.beans.factory.annotation.Required;
 import org.springframework.scheduling.annotation.Async;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.PreDestroy;
@@ -64,21 +70,24 @@ 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);
+	@Autowired
+	private CommentService commentService;
+
+	private static final Logger logger = LoggerFactory.getLogger(ArsnovaSocketioServerImpl.class);
 
 	private int portNumber;
 	private String hostIp;
@@ -87,8 +96,9 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 	private String storepass;
 	private final Configuration config;
 	private SocketIOServer server;
+	private boolean securityInitialized;
 
-	public ARSnovaSocketIOServer() {
+	public ArsnovaSocketioServerImpl() {
 		config = new Configuration();
 	}
 
@@ -138,15 +148,15 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 
 					return;
 				}
-				final String sessionKey = userService.getSessionForUser(u.getUsername());
-				final de.thm.arsnova.entities.Session session = sessionService.getSessionInternal(sessionKey, u);
+				final String sessionKey = userService.getSessionByUsername(u.getUsername());
+				final de.thm.arsnova.entities.Session session = sessionService.getInternal(sessionKey, u);
 
 				if (session.getFeedbackLock()) {
 					logger.debug("Feedback save blocked: {}", u, sessionKey, data.getValue());
 				} else {
 					logger.debug("Feedback recieved: {}", u, sessionKey, data.getValue());
 					if (null != sessionKey) {
-						feedbackService.saveFeedback(sessionKey, data.getValue(), u);
+						feedbackService.save(sessionKey, data.getValue(), u);
 					}
 				}
 			}
@@ -162,12 +172,12 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 
 					return;
 				}
-				final String oldSessionKey = userService.getSessionForUser(u.getUsername());
+				final String oldSessionKey = userService.getSessionByUsername(u.getUsername());
 				if (null != session.getKeyword() && session.getKeyword().equals(oldSessionKey)) {
 					return;
 				}
 
-				if (null != sessionService.joinSession(session.getKeyword(), client.getSessionId())) {
+				if (null != sessionService.join(session.getKeyword(), client.getSessionId())) {
 					/* active user count has to be sent to the client since the broadcast is
 					 * not always sent as long as the polling solution is active simultaneously */
 					reportActiveUserCountForSession(session.getKeyword());
@@ -179,19 +189,20 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 			}
 		});
 
+		/* TODO: This listener expects a Comment entity but only uses the ID. Reduce transmitted data. */
 		server.addEventListener(
 				"readInterposedQuestion",
-				de.thm.arsnova.entities.transport.Comment.class,
-				new DataListener<de.thm.arsnova.entities.transport.Comment>() {
+				Comment.class,
+				new DataListener<Comment>() {
 			@Override
 			@Timed(name = "readInterposedQuestionEvent.onData")
 			public void onData(
 					SocketIOClient client,
-					de.thm.arsnova.entities.transport.Comment comment,
+					Comment comment,
 					AckRequest ackRequest) {
 				final User user = userService.getUser2SocketId(client.getSessionId());
 				try {
-					contentService.readInterposedQuestionInternal(comment.getId(), user);
+					commentService.getAndMarkReadInternal(comment.getId(), user);
 				} catch (NotFoundException | UnauthorizedException e) {
 					logger.error("Loading of comment {} failed for user {} with exception {}", comment.getId(), user, e.getMessage());
 				}
@@ -203,7 +214,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 			public void onData(SocketIOClient client, String answerId, AckRequest ackRequest) {
 				final User user = userService.getUser2SocketId(client.getSessionId());
 				try {
-					contentService.readFreetextAnswer(answerId, user);
+					contentService.getFreetextAnswerAndMarkRead(answerId, user);
 				} catch (NotFoundException | UnauthorizedException e) {
 					logger.error("Marking answer {} as read failed for user {} with exception {}", answerId, user, e.getMessage());
 				}
@@ -212,17 +223,18 @@ 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 String sessionKey = userService.getSessionByUsername(user.getUsername());
+				final de.thm.arsnova.entities.Session session = sessionService.getInternal(sessionKey, user);
 				if (session.isCreator(user)) {
-					session.setLearningProgressOptions(progressOptions.toEntity());
-					sessionService.updateSessionInternal(session, user);
-					broadcastInSession(session.getKeyword(), "learningProgressOptions", progressOptions.toEntity());
+					session.setLearningProgressOptions(scoreOptions);
+					sessionService.updateInternal(session, user);
+					broadcastInSession(session.getKeyword(), "learningProgressOptions", scoreOptions);
 				}
 			}
 		});
@@ -231,7 +243,9 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 			@Override
 			@Timed
 			public void onConnect(final SocketIOClient client) {
-				/* No implementation - only used for monitoring */
+				if (!securityInitialized) {
+					initializeSecurity();
+				}
 			}
 		});
 
@@ -247,7 +261,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 					return;
 				}
 				final String username = userService.getUser2SocketId(client.getSessionId()).getUsername();
-				final String sessionKey = userService.getSessionForUser(username);
+				final String sessionKey = userService.getSessionByUsername(username);
 				userService.removeUserFromSessionBySocketId(client.getSessionId());
 				userService.removeUser2SocketId(client.getSessionId());
 				if (null != sessionKey) {
@@ -357,8 +371,8 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 	 * relevant Socket.IO data, the client needs to know after joining a session.
 	 */
 	public void reportSessionDataToClient(final String sessionKey, final User user, final SocketIOClient client) {
-		final de.thm.arsnova.entities.Session session = sessionService.getSessionInternal(sessionKey, user);
-		final de.thm.arsnova.entities.SessionFeature features = sessionService.getSessionFeatures(sessionKey);
+		final de.thm.arsnova.entities.Session session = sessionService.getInternal(sessionKey, user);
+		final de.thm.arsnova.entities.SessionFeature features = sessionService.getFeatures(sessionKey);
 
 		client.sendEvent("unansweredLecturerQuestions", contentService.getUnAnsweredLectureQuestionIds(sessionKey, user));
 		client.sendEvent("unansweredPreparationQuestions", contentService.getUnAnsweredPreparationQuestionIds(sessionKey, user));
@@ -366,7 +380,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 		client.sendEvent("countPreparationQuestionAnswers", contentService.countPreparationQuestionAnswersInternal(sessionKey));
 		client.sendEvent("activeUserCountData", sessionService.activeUsers(sessionKey));
 		client.sendEvent("learningProgressOptions", session.getLearningProgressOptions());
-		final de.thm.arsnova.entities.Feedback fb = feedbackService.getFeedback(sessionKey);
+		final de.thm.arsnova.entities.Feedback fb = feedbackService.getBySessionKey(sessionKey);
 		client.sendEvent("feedbackData", fb.getValues());
 
 		if (features.isFlashcard() || features.isFlashcardFeature()) {
@@ -375,7 +389,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 		}
 
 		try {
-			final long averageFeedback = feedbackService.getAverageFeedbackRounded(sessionKey);
+			final long averageFeedback = feedbackService.calculateRoundedAverageFeedback(sessionKey);
 			client.sendEvent("feedbackDataRoundedAverage", averageFeedback);
 		} catch (final NoContentException e) {
 			final Object object = null; // can't directly use "null".
@@ -384,10 +398,10 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 	}
 
 	public void reportUpdatedFeedbackForSession(final de.thm.arsnova.entities.Session session) {
-		final de.thm.arsnova.entities.Feedback fb = feedbackService.getFeedback(session.getKeyword());
+		final de.thm.arsnova.entities.Feedback fb = feedbackService.getBySessionKey(session.getKeyword());
 		broadcastInSession(session.getKeyword(), "feedbackData", fb.getValues());
 		try {
-			final long averageFeedback = feedbackService.getAverageFeedbackRounded(session.getKeyword());
+			final long averageFeedback = feedbackService.calculateRoundedAverageFeedback(session.getKeyword());
 			broadcastInSession(session.getKeyword(), "feedbackDataRoundedAverage", averageFeedback);
 		} catch (final NoContentException e) {
 			broadcastInSession(session.getKeyword(), "feedbackDataRoundedAverage", null);
@@ -395,10 +409,10 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 	}
 
 	public void reportFeedbackForUserInSession(final de.thm.arsnova.entities.Session session, final User user) {
-		final de.thm.arsnova.entities.Feedback fb = feedbackService.getFeedback(session.getKeyword());
+		final de.thm.arsnova.entities.Feedback fb = feedbackService.getBySessionKey(session.getKeyword());
 		Long averageFeedback;
 		try {
-			averageFeedback = feedbackService.getAverageFeedbackRounded(session.getKeyword());
+			averageFeedback = feedbackService.calculateRoundedAverageFeedback(session.getKeyword());
 		} catch (final NoContentException e) {
 			averageFeedback = null;
 		}
@@ -416,7 +430,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 	}
 
 	public void reportActiveUserCountForSession(final String sessionKey) {
-		final int count = userService.getUsersInSession(sessionKey).size();
+		final int count = userService.getUsersBySessionKey(sessionKey).size();
 
 		broadcastInSession(sessionKey, "activeUserCountData", count);
 	}
@@ -460,7 +474,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 		 * all connected clients and if send feedback, if user is in current
 		 * session
 		 */
-		final Set<User> users = userService.getUsersInSession(sessionKey);
+		final Set<User> users = userService.getUsersBySessionKey(sessionKey);
 
 		for (final SocketIOClient c : server.getAllClients()) {
 			final User u = userService.getUser2SocketId(c.getSessionId());
@@ -506,7 +520,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 	public void visit(NewAnswerEvent event) {
 		final String sessionKey = event.getSession().getKeyword();
 		this.reportAnswersToLecturerQuestionAvailable(event.getSession(), new Content(event.getContent()));
-		broadcastInSession(sessionKey, "countQuestionAnswersByQuestionId", contentService.getAnswerAndAbstentionCountInternal(event.getContent().getId()));
+		broadcastInSession(sessionKey, "countQuestionAnswersByQuestionId", contentService.countAnswersAndAbstentionsInternal(event.getContent().getId()));
 		broadcastInSession(sessionKey, "countLectureQuestionAnswers", contentService.countLectureQuestionAnswersInternal(sessionKey));
 		broadcastInSession(sessionKey, "countPreparationQuestionAnswers", contentService.countPreparationQuestionAnswersInternal(sessionKey));
 
@@ -665,7 +679,7 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 	}
 
 	@Override
-	public void visit(ChangeLearningProgressEvent event) {
+	public void visit(ChangeScoreEvent event) {
 		broadcastInSession(event.getSession().getKeyword(), "learningProgressChange", null);
 	}
 
@@ -674,4 +688,13 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 
 	@Override
 	public void visit(DeleteSessionEvent event) { }
+
+	private void initializeSecurity() {
+		Authentication auth = new AnonymousAuthenticationToken("websocket", "websocket",
+				AuthorityUtils.createAuthorityList("ROLE_WEBSOCKET_ACCESS"));
+		SecurityContext context = SecurityContextHolder.createEmptyContext();
+		context.setAuthentication(auth);
+		SecurityContextHolder.setContext(context);
+		securityInitialized = true;
+	}
 }
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 6c740888f5a7d433f9fd151acebb131702f568ba..8bcfb88e045948e72f364d35720416d406a61ed0 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 f462baa7bb7e62b81f15e512774f630d9e848d80..73e84c3ec3b9a6ed2a5861bb9088d5b82dbbd741 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 211e10d6a6a39a4b3ac036b1db46d9c47a0da639..0f7966b14bb2aefa953714f0b24d9ddecf43f0ae 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 88f7c53d27e342ff6f0e804cd2943e03d4d97ec1..9e13bb2233dcd5c5863c7099e5ea0ef5470fca96 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 98beea04a699cb10d38d1a3d1cac7edf0cef847c..0af4ab419bcf0c192d252606e1b41c0c15afb63d 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 d4f65e727417e57da0546892bab342168a7d09d4..3fd6e43c013b192de303d345594ca8382b09eec3 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/caching.md b/src/site/markdown/development/caching.md
index 01d4a81ccd8fe2d9230e10c5cd4dfa10f2a89433..ec8baaf7a8432df245c1e78380f84b4e528b1e0c 100644
--- a/src/site/markdown/development/caching.md
+++ b/src/site/markdown/development/caching.md
@@ -19,7 +19,7 @@ Caching should only be used with domain objects, where the `hashCode` and `equal
 public ResultObject notVeryCacheable(String sessionId, String questionVariant, String subject) { ... }
 ```
 
-Therefore, you should always work with domain objects like `Session`, `Question`, or even your own, newly defined objects:
+Therefore, you should always work with domain objects like `Session`, `Content`, or even your own, newly defined objects:
 
 ```java
 @Cacheable("verycacheable")
@@ -46,14 +46,14 @@ Here is a list of all caches, their keys, and a short description.
 
 Cache name | Key | Description
 -----------|-----|------------
-`skillquestions`| `Session` entity | Contains all questions for the specified session irrespective of their variant.
-`lecturequestions` | `Session` entity | Contains all "lecture" variant questions for the specified session.
-`preparationquestions` | `Session` entity | Contains all "preparation" variant questions for the specified session.
-`flashcardquestions` | `Session` entity | Contains all "flashcard" variant questions for the specified session.
-`questions` | `Question` entity | Contains single question objects.
-`questions` | database id of question | Although it shares the name of the previously mentioned cache, it is in essence a different cache because the keys are different. This means that the same `Question` object might be associated with two different keys.
-`answers`| `Question` entity | Contains single answer objects.
-`learningprogress` | `Session` entity | Contains `CourseScore` objects to calculate the learning progress values for the specified session.
+`contentlists`| database id of session | Contains all contents for the specified session irrespective of their variant.
+`lecturecontentlists` | database id of session | Contains all "lecture" variant contents for the specified session.
+`preparationcontentlists` | database id of session | Contains all "preparation" variant contents for the specified session.
+`flashcardcontentlists` | database id of session | Contains all "flashcard" variant contents for the specified session.
+`contents` | `Content` entity | Contains single content objects.
+`contents` | database id of content | Although it shares the name of the previously mentioned cache, it is in essence a different cache because the keys are different. This means that the same `Content` object might be associated with two different keys.
+`answerlists`| database id of content | Contains single answer objects.
+`score` | `Session` entity | Contains `CourseScore` objects to calculate the score values for the specified session.
 `sessions` | keyword of session | Contains sessions identified by their keywords.
 `sessions` | database id of session | Although it shares the name of the previously mentioned cache, it is in essence a different cache because the keys are different. This means that the same `Session` object might be associated with two different keys.
 `statistics` | -- | Contains a single, global statistics object.
diff --git a/src/site/markdown/development/event-system.md b/src/site/markdown/development/event-system.md
index d9f4459a1066df3a0ba403feeafd9fe9dcc77dd0..71132e71bbc0d34254e47be3f277bf7090ec352e 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 f2759881edb031f59b10e8070fe79f706b9400ad..eea3d564fbee6a679fea09baf9971d1e63e38aa5 100644
--- a/src/test/java/de/thm/arsnova/config/TestAppConfig.java
+++ b/src/test/java/de/thm/arsnova/config/TestAppConfig.java
@@ -1,8 +1,9 @@
 package de.thm.arsnova.config;
 
+import de.thm.arsnova.persistance.UserRepository;
 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,10 +11,12 @@ 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;
 import org.springframework.context.support.SimpleThreadScope;
+import org.springframework.mail.javamail.JavaMailSender;
 import org.springframework.mock.web.MockServletContext;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.support.AnnotationConfigContextLoader;
@@ -23,7 +26,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 +61,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,7 +71,8 @@ public class TestAppConfig {
 	}
 
 	@Bean
-	public StubUserService stubUserService() {
-		return new StubUserService();
+	@Primary
+	public StubUserService stubUserService(UserRepository repository, JavaMailSender mailSender) {
+		return new StubUserService(repository, mailSender);
 	}
 }
diff --git a/src/test/java/de/thm/arsnova/config/TestSecurityConfig.java b/src/test/java/de/thm/arsnova/config/TestSecurityConfig.java
index 6ad50678aca2eccf4883f5bfaffc82e5d3e98d58..17716f734f469fa3f553bc96ff864adfd91d9026 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/EntityServiceTest.java b/src/test/java/de/thm/arsnova/services/EntityServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3d3a2b5056f2215a5a126be489f61b7402f9e3d
--- /dev/null
+++ b/src/test/java/de/thm/arsnova/services/EntityServiceTest.java
@@ -0,0 +1,125 @@
+package de.thm.arsnova.services;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import de.thm.arsnova.config.AppConfig;
+import de.thm.arsnova.config.TestAppConfig;
+import de.thm.arsnova.config.TestPersistanceConfig;
+import de.thm.arsnova.config.TestSecurityConfig;
+import de.thm.arsnova.entities.Session;
+import de.thm.arsnova.persistance.SessionRepository;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.AdditionalAnswers.*;
+import static org.mockito.Mockito.*;
+import static org.junit.Assert.*;
+
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@WebAppConfiguration
+@ContextConfiguration(classes = {AppConfig.class, TestAppConfig.class, TestPersistanceConfig.class, TestSecurityConfig.class})
+@ActiveProfiles("test")
+public class EntityServiceTest {
+	@Autowired
+	@Qualifier("defaultJsonMessageConverter")
+	private MappingJackson2HttpMessageConverter jackson2HttpMessageConverter;
+
+	@Autowired
+	private SessionRepository sessionRepository;
+
+	@Test
+	@WithMockUser(username="TestUser")
+	public void testPatch() throws IOException {
+		final ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper();
+		final EntityService<Session> entityService = new EntityService<>(Session.class, sessionRepository, objectMapper);
+
+		when(sessionRepository.save(any(Session.class))).then(returnsFirstArg());
+
+		final String originalId = "d8833f0d78964a9487ded02ba2dfbbad";
+		final String originalName = "Test Session";
+		final String originalCreator = "TestUser";
+		final boolean originalActive = false;
+		final Session session = new Session();
+		session.setId(originalId);
+		session.setName(originalName);
+		session.setActive(originalActive);
+		session.setCreator(originalCreator);
+
+		final String patchedName = "Patched Session";
+		final boolean patchedActive = true;
+		final Map<String, Object> patchedValues = new HashMap<>();
+		patchedValues.put("name", patchedName);
+		patchedValues.put("active", patchedActive);
+		patchedValues.put("creator", "Should not be changeable.");
+
+		entityService.patch(session, patchedValues);
+
+		assertEquals(originalId, session.getId());
+		assertEquals(patchedName, session.getName());
+		assertEquals(patchedActive, session.isActive());
+		assertEquals(originalCreator, session.getCreator());
+	}
+
+	@Test
+	@WithMockUser(username="TestUser")
+	public void testPatchWithList() throws IOException {
+		final ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper();
+		final EntityService<Session> entityService = new EntityService<>(Session.class, sessionRepository, objectMapper);
+
+		when(sessionRepository.save(any(Session.class))).then(returnsFirstArg());
+
+		List<Session> sessions = new ArrayList<>();
+		final String originalId1 = "d8833f0d78964a9487ded02ba2dfbbad";
+		final String originalName1 = "Test Session 1";
+		final String originalCreator1 = "TestUser";
+		final boolean originalActive1 = false;
+		final Session session1 = new Session();
+		session1.setId(originalId1);
+		session1.setName(originalName1);
+		session1.setActive(originalActive1);
+		session1.setCreator(originalCreator1);
+		sessions.add(session1);
+		final String originalId2 = "3dc8cbff05da49d5980f6c001a6ea867";
+		final String originalName2 = "Test Session 2";
+		final String originalCreator2 = "TestUser";
+		final boolean originalActive2 = false;
+		final Session session2 = new Session();
+		session2.setId(originalId2);
+		session2.setName(originalName2);
+		session2.setActive(originalActive2);
+		session2.setCreator(originalCreator2);
+		sessions.add(session2);
+
+		final String patchedName = "Patched Session";
+		final boolean patchedActive = true;
+		final Map<String, Object> patchedValues = new HashMap<>();
+		patchedValues.put("name", patchedName);
+		patchedValues.put("active", patchedActive);
+		patchedValues.put("creator", "Should not be changeable.");
+
+		entityService.patch(sessions, patchedValues);
+
+		assertEquals(originalId1, session1.getId());
+		assertEquals(patchedName, session1.getName());
+		assertEquals(patchedActive, session1.isActive());
+		assertEquals(originalCreator1, session1.getCreator());
+		assertEquals(originalId2, session2.getId());
+		assertEquals(patchedName, session2.getName());
+		assertEquals(patchedActive, session2.isActive());
+		assertEquals(originalCreator2, session2.getCreator());
+	}
+}
diff --git a/src/test/java/de/thm/arsnova/services/StubUserService.java b/src/test/java/de/thm/arsnova/services/StubUserService.java
index b072375b65222022d1a67282e5528308d0e62269..278f02b36c469b1659dc48436a23e23ad876f7cf 100644
--- a/src/test/java/de/thm/arsnova/services/StubUserService.java
+++ b/src/test/java/de/thm/arsnova/services/StubUserService.java
@@ -18,12 +18,18 @@
 package de.thm.arsnova.services;
 
 import de.thm.arsnova.entities.User;
+import de.thm.arsnova.persistance.UserRepository;
+import org.springframework.mail.javamail.JavaMailSender;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 
-public class StubUserService extends UserService {
+public class StubUserService extends UserServiceImpl {
 
 	private User stubUser = null;
 
+	public StubUserService(UserRepository repository, JavaMailSender mailSender) {
+		super(repository, mailSender);
+	}
+
 	public void setUserAuthenticated(boolean isAuthenticated) {
 		this.setUserAuthenticated(isAuthenticated, "ptsr00");
 	}
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 8adf79257cd12f28dafe08d6f6e91f6524628e3e..d46874fc7d0a7f5e82cc50fddd981bf06faa34cf 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 6f42527dc61881ea06d533e29c7932e85c464317..0909300b0764d1f9dccb9eaef42acf8163610e2f 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 5c27e1eb596eaf7dbd2809fd3154dd6c797bb232..a7f9c67d0b609489e6d9c5213b7025f7a7685bd2 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)