From 747f9b62cb5584c02fa749d9d6f3f5c1adb4bedf Mon Sep 17 00:00:00 2001 From: Daniel Gerhardt <code@dgerhardt.net> Date: Sun, 16 Jul 2017 01:09:01 +0200 Subject: [PATCH] Introduce new abstractions for services and repositories Repositories now implement Spring Data interfaces. Basic CouchDB operations are now implemented once by `CouchDbCrudRepository` instead of having redundant methods to handle each design document. --- pom.xml | 4 + .../arsnova/persistance/AnswerRepository.java | 3 +- .../persistance/CommentRepository.java | 3 +- .../persistance/ContentRepository.java | 3 +- .../arsnova/persistance/MotdRepository.java | 3 +- .../persistance/SessionRepository.java | 3 +- .../arsnova/persistance/UserRepository.java | 7 +- .../couchdb/CouchDbAnswerRepository.java | 5 +- .../couchdb/CouchDbCommentRepository.java | 5 +- .../couchdb/CouchDbContentRepository.java | 5 +- .../couchdb/CouchDbCrudRepository.java | 122 ++++++++++++++++++ .../couchdb/CouchDbMotdRepository.java | 5 +- .../couchdb/CouchDbSessionRepository.java | 5 +- .../couchdb/CouchDbUserRepository.java | 15 +-- .../security/DbUserDetailsService.java | 2 +- .../thm/arsnova/services/UserServiceImpl.java | 12 +- 16 files changed, 163 insertions(+), 39 deletions(-) create mode 100644 src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCrudRepository.java diff --git a/pom.xml b/pom.xml index aa4a9b35e..8e65c40e3 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> diff --git a/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java b/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java index 34f363c34..303dfce94 100644 --- a/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/AnswerRepository.java @@ -21,10 +21,11 @@ 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 { +public interface AnswerRepository extends CrudRepository<Answer, String> { Answer get(String id); Answer getMyAnswer(User me, String questionId, int piRound); List<Answer> getAnswers(String contentId, int piRound); diff --git a/src/main/java/de/thm/arsnova/persistance/CommentRepository.java b/src/main/java/de/thm/arsnova/persistance/CommentRepository.java index 182066fec..fb8a4dcd2 100644 --- a/src/main/java/de/thm/arsnova/persistance/CommentRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/CommentRepository.java @@ -3,10 +3,11 @@ package de.thm.arsnova.persistance; import de.thm.arsnova.entities.Comment; import de.thm.arsnova.entities.CommentReadingCount; import de.thm.arsnova.entities.User; +import org.springframework.data.repository.CrudRepository; import java.util.List; -public interface CommentRepository { +public interface CommentRepository extends CrudRepository<Comment, String> { int getInterposedCount(String sessionKey); CommentReadingCount getInterposedReadingCount(String sessionId); CommentReadingCount getInterposedReadingCount(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 e2438013e..3c8d138cd 100644 --- a/src/main/java/de/thm/arsnova/persistance/ContentRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/ContentRepository.java @@ -2,10 +2,11 @@ package de.thm.arsnova.persistance; import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.User; +import org.springframework.data.repository.CrudRepository; import java.util.List; -public interface ContentRepository { +public interface ContentRepository extends CrudRepository<Content, String> { List<Content> getQuestions(Object... keys); Content getQuestion(String id); Content saveQuestion(String sessionId, Content content); diff --git a/src/main/java/de/thm/arsnova/persistance/MotdRepository.java b/src/main/java/de/thm/arsnova/persistance/MotdRepository.java index 1c3714698..7b47069d8 100644 --- a/src/main/java/de/thm/arsnova/persistance/MotdRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/MotdRepository.java @@ -18,10 +18,11 @@ package de.thm.arsnova.persistance; import de.thm.arsnova.entities.Motd; +import org.springframework.data.repository.CrudRepository; import java.util.List; -public interface MotdRepository { +public interface MotdRepository extends CrudRepository<Motd, String> { List<Motd> getAdminMotds(); List<Motd> getMotdsForAll(); List<Motd> getMotdsForLoggedIn(); diff --git a/src/main/java/de/thm/arsnova/persistance/SessionRepository.java b/src/main/java/de/thm/arsnova/persistance/SessionRepository.java index a248b2ab4..9b41a6bcd 100644 --- a/src/main/java/de/thm/arsnova/persistance/SessionRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/SessionRepository.java @@ -23,10 +23,11 @@ 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 { +public interface SessionRepository extends CrudRepository<Session, String> { Session getSessionFromId(String sessionId); Session getSessionFromKeyword(String keyword); Session saveSession(User user, 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 7807f66aa..c4480fac9 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 673767d4f..7fae98121 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbAnswerRepository.java @@ -20,7 +20,6 @@ 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; @@ -35,7 +34,7 @@ import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; -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); @@ -53,7 +52,7 @@ public class CouchDbAnswerRepository extends CouchDbRepositorySupport<Answer> im private ApplicationEventPublisher publisher; public CouchDbAnswerRepository(final CouchDbConnector db, final boolean createIfNotExists) { - super(Answer.class, db, createIfNotExists); + super(Answer.class, db, "by_sessionid", createIfNotExists); } @Scheduled(fixedDelay = 5000) 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 92ac64fea..1d4f333d7 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbCommentRepository.java @@ -14,14 +14,13 @@ 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 @@ -31,7 +30,7 @@ public class CouchDbCommentRepository extends CouchDbRepositorySupport<Comment> private SessionRepository sessionRepository; public CouchDbCommentRepository(final CouchDbConnector db, final boolean createIfNotExists) { - super(Comment.class, db, createIfNotExists); + super(Comment.class, db, "by_sessionid", createIfNotExists); } @Override 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 2062894b7..e849580ed 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbContentRepository.java @@ -12,7 +12,6 @@ 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; @@ -29,7 +28,7 @@ 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 @@ -39,7 +38,7 @@ public class CouchDbContentRepository extends CouchDbRepositorySupport<Content> private AnswerRepository answerRepository; public CouchDbContentRepository(final CouchDbConnector db, final boolean createIfNotExists) { - super(Content.class, db, createIfNotExists); + super(Content.class, db, "by_sessionid", createIfNotExists); } @Cacheable("skillquestions") 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 000000000..7166c93da --- /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/CouchDbMotdRepository.java b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java index dc5f4ee9f..d8478f838 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbMotdRepository.java @@ -21,7 +21,6 @@ import de.thm.arsnova.entities.Motd; import de.thm.arsnova.persistance.MotdRepository; import de.thm.arsnova.services.SessionService; import org.ektorp.CouchDbConnector; -import org.ektorp.support.CouchDbRepositorySupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -31,14 +30,14 @@ 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 SessionService sessionService; public CouchDbMotdRepository(final CouchDbConnector db, final boolean createIfNotExists) { - super(Motd.class, db, createIfNotExists); + super(Motd.class, db, "by_sessionkey", createIfNotExists); } @Override 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 0858de683..e451be0e7 100644 --- a/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java +++ b/src/main/java/de/thm/arsnova/persistance/couchdb/CouchDbSessionRepository.java @@ -36,7 +36,6 @@ 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; @@ -55,7 +54,7 @@ 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 @@ -68,7 +67,7 @@ public class CouchDbSessionRepository extends CouchDbRepositorySupport<Session> private MotdRepository motdRepository; public CouchDbSessionRepository(final CouchDbConnector db, final boolean createIfNotExists) { - super(Session.class, db, createIfNotExists); + super(Session.class, db, "by_keyword", createIfNotExists); } @Override 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 6aec2b929..e2e5e9134 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(final CouchDbConnector db, final boolean createIfNotExists) { - super(DbUser.class, db, createIfNotExists); + super(DbUser.class, db, "by_username", createIfNotExists); } private void log(Object... strings) { @@ -46,7 +46,7 @@ public class CouchDbUserRepository extends CouchDbRepositorySupport<DbUser> impl } @Override - public DbUser createOrUpdateUser(final DbUser user) { + public DbUser save(final DbUser user) { final String id = user.getId(); if (null != id) { @@ -59,20 +59,19 @@ public class CouchDbUserRepository extends CouchDbRepositorySupport<DbUser> impl } @Override - public DbUser findUserByUsername(final String 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."); } } diff --git a/src/main/java/de/thm/arsnova/security/DbUserDetailsService.java b/src/main/java/de/thm/arsnova/security/DbUserDetailsService.java index 8e3b308cb..5351d98c5 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/services/UserServiceImpl.java b/src/main/java/de/thm/arsnova/services/UserServiceImpl.java index 17ff05b0b..08fdf672d 100644 --- a/src/main/java/de/thm/arsnova/services/UserServiceImpl.java +++ b/src/main/java/de/thm/arsnova/services/UserServiceImpl.java @@ -339,7 +339,7 @@ public class UserServiceImpl implements UserService { @Override public DbUser getDbUser(String username) { - return userRepository.findUserByUsername(username.toLowerCase()); + return userRepository.findByUsername(username.toLowerCase()); } @Override @@ -360,7 +360,7 @@ public class UserServiceImpl implements UserService { return null; } - if (null != userRepository.findUserByUsername(lcUsername)) { + if (null != userRepository.findByUsername(lcUsername)) { logger.info("User registration failed. {} already exists.", lcUsername); return null; @@ -372,7 +372,7 @@ public class UserServiceImpl implements UserService { dbUser.setActivationKey(RandomStringUtils.randomAlphanumeric(32)); dbUser.setCreation(System.currentTimeMillis()); - DbUser result = userRepository.createOrUpdateUser(dbUser); + DbUser result = userRepository.save(dbUser); if (null != result) { sendActivationEmail(result); } else { @@ -436,7 +436,7 @@ public class UserServiceImpl implements UserService { @Override public DbUser updateDbUser(DbUser dbUser) { if (null != dbUser.getId()) { - return userRepository.createOrUpdateUser(dbUser); + return userRepository.save(dbUser); } return null; @@ -456,7 +456,7 @@ public class UserServiceImpl implements UserService { throw new NotFoundException(); } - userRepository.deleteUser(dbUser); + userRepository.delete(dbUser); return dbUser; } @@ -478,7 +478,7 @@ public class UserServiceImpl implements UserService { dbUser.setPasswordResetKey(RandomStringUtils.randomAlphanumeric(32)); dbUser.setPasswordResetTime(System.currentTimeMillis()); - if (null == userRepository.createOrUpdateUser(dbUser)) { + if (null == userRepository.save(dbUser)) { logger.error("Password reset failed. {} could not be updated.", username); } -- GitLab