GitLab wurde erfolgreich aktualisiert. Dank regelmäßiger Updates bleibt das THM GitLab sicher und Sie profitieren von den neuesten Funktionen. Danke für Ihre Geduld.

Commit 245f281f authored by Daniel Gerhardt's avatar Daniel Gerhardt

Migrate remaining CouchDB code to Ektorp

Code for the following domains has been migrated:
* VisitedSession
* MotdList
* Statistics
* LearingProgress
parent c18ffc9b
......@@ -23,30 +23,12 @@ import com.fasterxml.jackson.databind.SerializationFeature;
import de.thm.arsnova.ImageUtils;
import de.thm.arsnova.connector.client.ConnectorClient;
import de.thm.arsnova.connector.client.ConnectorClientImpl;
import de.thm.arsnova.entities.Answer;
import de.thm.arsnova.entities.Comment;
import de.thm.arsnova.entities.DbUser;
import de.thm.arsnova.entities.LogEntry;
import de.thm.arsnova.entities.Motd;
import de.thm.arsnova.entities.Content;
import de.thm.arsnova.entities.Session;
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.AnswerRepository;
import de.thm.arsnova.persistance.CommentRepository;
import de.thm.arsnova.persistance.ContentRepository;
import de.thm.arsnova.persistance.LogEntryRepository;
import de.thm.arsnova.persistance.MotdRepository;
import de.thm.arsnova.persistance.SessionRepository;
import de.thm.arsnova.persistance.UserRepository;
import de.thm.arsnova.persistance.couchdb.CouchDbAnswerRepository;
import de.thm.arsnova.persistance.couchdb.CouchDbCommentRepository;
import de.thm.arsnova.persistance.couchdb.CouchDbContentRepository;
import de.thm.arsnova.persistance.couchdb.CouchDbLogEntryRepository;
import de.thm.arsnova.persistance.couchdb.CouchDbMotdRepository;
import de.thm.arsnova.persistance.couchdb.CouchDbSessionRepository;
import de.thm.arsnova.persistance.couchdb.CouchDbUserRepository;
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;
......@@ -321,6 +303,26 @@ public class AppConfig extends WebMvcConfigurerAdapter {
return new CouchDbUserRepository(DbUser.class, couchDbConnector(), false);
}
@Bean
public VisitedSessionRepository visitedSessionRepository() throws Exception {
return new CouchDbVisitedSessionRepository(VisitedSession.class, couchDbConnector(), false);
}
@Bean
public MotdListRepository motdListRepository() throws Exception {
return new CouchDbMotdListRepository(MotdList.class, couchDbConnector(), false);
}
@Bean
public StatisticsRepository statisticsRepository() throws Exception {
return new CouchDbStatisticsRepository(Object.class, couchDbConnector(), false);
}
@Bean
public SessionStatisticsRepository sessionStatisticsRepository() throws Exception {
return new CouchDbSessionStatisticsRepository(Object.class, couchDbConnector(), false);
}
@Bean(name = "connectorClient")
public ConnectorClient connectorClient() {
if (!connectorEnable) {
......
......@@ -21,16 +21,13 @@ import com.fourspaces.couchdb.Database;
import com.fourspaces.couchdb.Document;
import com.fourspaces.couchdb.View;
import com.fourspaces.couchdb.ViewResults;
import com.google.common.collect.Lists;
import de.thm.arsnova.connector.model.Course;
import de.thm.arsnova.domain.CourseScore;
import de.thm.arsnova.entities.*;
import de.thm.arsnova.persistance.ContentRepository;
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 net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
......@@ -38,18 +35,12 @@ import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Database implementation based on CouchDB.
......@@ -73,8 +64,6 @@ import java.util.Set;
@Service("databaseDao")
public class CouchDBDao implements IDatabaseDao {
private static final int BULK_PARTITION_SIZE = 500;
@Autowired
private ISessionService sessionService;
......@@ -169,84 +158,6 @@ public class CouchDBDao implements IDatabaseDao {
return results == null || results.getResults().isEmpty() || results.getJSONArray("rows").isEmpty();
}
@Cacheable("statistics")
@Override
public Statistics getStatistics() {
final Statistics stats = new Statistics();
try {
final View statsView = new View("statistics/statistics");
final View creatorView = new View("statistics/unique_session_creators");
final View studentUserView = new View("statistics/active_student_users");
statsView.setGroup(true);
creatorView.setGroup(true);
studentUserView.setGroup(true);
final ViewResults statsResults = getDatabase().view(statsView);
final ViewResults creatorResults = getDatabase().view(creatorView);
final ViewResults studentUserResults = getDatabase().view(studentUserView);
if (!isEmptyResults(statsResults)) {
final JSONArray rows = statsResults.getJSONArray("rows");
for (int i = 0; i < rows.size(); i++) {
final JSONObject row = rows.getJSONObject(i);
final int value = row.getInt("value");
switch (row.getString("key")) {
case "openSessions":
stats.setOpenSessions(stats.getOpenSessions() + value);
break;
case "closedSessions":
stats.setClosedSessions(stats.getClosedSessions() + value);
break;
case "deletedSessions":
/* Deleted sessions are not exposed separately for now. */
stats.setClosedSessions(stats.getClosedSessions() + value);
break;
case "answers":
stats.setAnswers(stats.getAnswers() + value);
break;
case "lectureQuestions":
stats.setLectureQuestions(stats.getLectureQuestions() + value);
break;
case "preparationQuestions":
stats.setPreparationQuestions(stats.getPreparationQuestions() + value);
break;
case "interposedQuestions":
stats.setInterposedQuestions(stats.getInterposedQuestions() + value);
break;
case "conceptQuestions":
stats.setConceptQuestions(stats.getConceptQuestions() + value);
break;
case "flashcards":
stats.setFlashcards(stats.getFlashcards() + value);
break;
}
}
}
if (!isEmptyResults(creatorResults)) {
final JSONArray rows = creatorResults.getJSONArray("rows");
Set<String> creators = new HashSet<>();
for (int i = 0; i < rows.size(); i++) {
final JSONObject row = rows.getJSONObject(i);
creators.add(row.getString("key"));
}
stats.setCreators(creators.size());
}
if (!isEmptyResults(studentUserResults)) {
final JSONArray rows = studentUserResults.getJSONArray("rows");
Set<String> students = new HashSet<>();
for (int i = 0; i < rows.size(); i++) {
final JSONObject row = rows.getJSONObject(i);
students.add(row.getString("key"));
}
stats.setActiveStudents(students.size());
}
return stats;
} catch (final Exception e) {
logger.error("Could not retrieve session count.", e);
}
return stats;
}
/**
* Adds convenience methods to CouchDB4J's view class.
*/
......@@ -272,135 +183,4 @@ public class CouchDBDao implements IDatabaseDao {
setKeys(sessionIds);
}
}
@Override
public int deleteInactiveGuestVisitedSessionLists(long lastActivityBefore) {
try {
View view = new View("logged_in/by_last_activity_for_guests");
view.setEndKey(lastActivityBefore);
List<Document> results = this.getDatabase().view(view).getResults();
int count = 0;
List<List<Document>> partitions = Lists.partition(results, BULK_PARTITION_SIZE);
for (List<Document> partition: partitions) {
final List<Document> newDocs = new ArrayList<>();
for (final Document oldDoc : partition) {
final Document newDoc = new Document();
newDoc.setId(oldDoc.getId());
newDoc.setRev(oldDoc.getJSONObject("value").getString("_rev"));
newDoc.put("_deleted", true);
newDocs.add(newDoc);
logger.debug("Marked logged_in document {} for deletion.", oldDoc.getId());
/* Use log type 'user' since effectively the user is deleted in case of guests */
dbLogger.log("delete", "type", "user", "id", oldDoc.getId());
}
if (!newDocs.isEmpty()) {
if (getDatabase().bulkSaveDocuments(newDocs.toArray(new Document[newDocs.size()]))) {
count += newDocs.size();
} else {
logger.error("Could not bulk delete visited session lists.");
}
}
}
if (count > 0) {
logger.info("Deleted {} visited session lists of inactive users.", count);
dbLogger.log("cleanup", "type", "visitedsessions", "count", count);
}
return count;
} catch (IOException e) {
logger.error("Could not delete visited session lists of inactive users.", e);
}
return 0;
}
@Cacheable("learningprogress")
@Override
public CourseScore getLearningProgress(final Session session) {
final View maximumValueView = new View("learning_progress/maximum_value_of_question");
final View answerSumView = new View("learning_progress/question_value_achieved_for_user");
maximumValueView.setStartKeyArray(session.getId());
maximumValueView.setEndKeyArray(session.getId(), "{}");
answerSumView.setStartKeyArray(session.getId());
answerSumView.setEndKeyArray(session.getId(), "{}");
final List<Document> maximumValueResult = getDatabase().view(maximumValueView).getResults();
final List<Document> answerSumResult = getDatabase().view(answerSumView).getResults();
CourseScore courseScore = new CourseScore();
// no results found
if (maximumValueResult.isEmpty() && answerSumResult.isEmpty()) {
return courseScore;
}
// collect mapping (questionId -> max value)
for (Document d : maximumValueResult) {
String questionId = d.getJSONArray("key").getString(1);
JSONObject value = d.getJSONObject("value");
int questionScore = value.getInt("value");
String questionVariant = value.getString("questionVariant");
int piRound = value.getInt("piRound");
courseScore.addQuestion(questionId, questionVariant, piRound, questionScore);
}
// collect mapping (questionId -> (user -> value))
for (Document d : answerSumResult) {
String username = d.getJSONArray("key").getString(1);
JSONObject value = d.getJSONObject("value");
String questionId = value.getString("questionId");
int userscore = value.getInt("score");
int piRound = value.getInt("piRound");
courseScore.addAnswer(questionId, piRound, username, userscore);
}
return courseScore;
}
@Override
@Cacheable(cacheNames = "motdlist", key = "#p0")
public MotdList getMotdListForUser(final String username) {
View view = new View("motdlist/doc_by_username");
view.setKey(username);
ViewResults results = this.getDatabase().view(view);
MotdList motdlist = new MotdList();
for (final Document d : results.getResults()) {
motdlist.set_id(d.getId());
motdlist.set_rev(d.getJSONObject("value").getString("_rev"));
motdlist.setUsername(d.getJSONObject("value").getString("username"));
motdlist.setMotdkeys(d.getJSONObject("value").getString("motdkeys"));
}
return motdlist;
}
@Override
@CachePut(cacheNames = "motdlist", key = "#p0.username")
public MotdList createOrUpdateMotdList(MotdList motdlist) {
try {
String id = motdlist.get_id();
String rev = motdlist.get_rev();
Document d = new Document();
if (null != id) {
d = database.getDocument(id, rev);
}
d.put("type", "motdlist");
d.put("username", motdlist.getUsername());
d.put("motdkeys", motdlist.getMotdkeys());
database.saveDocument(d, id);
motdlist.set_id(d.getId());
motdlist.set_rev(d.getRev());
return motdlist;
} catch (IOException e) {
logger.error("Could not save MotD list {}.", motdlist, e);
}
return null;
}
}
......@@ -17,25 +17,9 @@
*/
package de.thm.arsnova.dao;
import de.thm.arsnova.domain.CourseScore;
import de.thm.arsnova.entities.*;
import java.util.List;
/**
* All methods the database must support.
*/
public interface IDatabaseDao {
int deleteInactiveGuestVisitedSessionLists(long lastActivityBefore);
CourseScore getLearningProgress(Session session);
Statistics getStatistics();
<T> T getObjectFromId(String documentId, Class<T> klass);
MotdList getMotdListForUser(final String username);
MotdList createOrUpdateMotdList(MotdList motdlist);
}
......@@ -17,8 +17,8 @@
*/
package de.thm.arsnova.domain;
import de.thm.arsnova.dao.IDatabaseDao;
import de.thm.arsnova.events.*;
import de.thm.arsnova.persistance.SessionStatisticsRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.context.ApplicationEventPublisher;
......@@ -34,7 +34,7 @@ import org.springframework.stereotype.Component;
public class LearningProgressFactory implements NovaEventVisitor, ILearningProgressFactory, ApplicationEventPublisherAware {
@Autowired
private IDatabaseDao databaseDao;
private SessionStatisticsRepository sessionStatisticsRepository;
private ApplicationEventPublisher publisher;
......@@ -42,9 +42,9 @@ public class LearningProgressFactory implements NovaEventVisitor, ILearningProgr
public LearningProgress create(String progressType, String questionVariant) {
VariantLearningProgress learningProgress;
if ("questions".equals(progressType)) {
learningProgress = new QuestionBasedLearningProgress(databaseDao);
learningProgress = new QuestionBasedLearningProgress(sessionStatisticsRepository);
} else {
learningProgress = new PointBasedLearningProgress(databaseDao);
learningProgress = new PointBasedLearningProgress(sessionStatisticsRepository);
}
learningProgress.setQuestionVariant(questionVariant);
return learningProgress;
......
......@@ -17,17 +17,17 @@
*/
package de.thm.arsnova.domain;
import de.thm.arsnova.dao.IDatabaseDao;
import de.thm.arsnova.entities.User;
import de.thm.arsnova.entities.transport.LearningProgressValues;
import de.thm.arsnova.persistance.SessionStatisticsRepository;
/**
* Calculates learning progress based on a question's value.
*/
public class PointBasedLearningProgress extends VariantLearningProgress {
public PointBasedLearningProgress(IDatabaseDao dao) {
super(dao);
public PointBasedLearningProgress(SessionStatisticsRepository sessionStatisticsRepository) {
super(sessionStatisticsRepository);
}
@Override
......
......@@ -17,9 +17,9 @@
*/
package de.thm.arsnova.domain;
import de.thm.arsnova.dao.IDatabaseDao;
import de.thm.arsnova.entities.User;
import de.thm.arsnova.entities.transport.LearningProgressValues;
import de.thm.arsnova.persistance.SessionStatisticsRepository;
/**
* Calculates learning progress based on overall correctness of an answer. A question is answered correctly if and
......@@ -27,8 +27,8 @@ import de.thm.arsnova.entities.transport.LearningProgressValues;
*/
public class QuestionBasedLearningProgress extends VariantLearningProgress {
public QuestionBasedLearningProgress(IDatabaseDao dao) {
super(dao);
public QuestionBasedLearningProgress(SessionStatisticsRepository sessionStatisticsRepository) {
super(sessionStatisticsRepository);
}
@Override
......
......@@ -17,10 +17,10 @@
*/
package de.thm.arsnova.domain;
import de.thm.arsnova.dao.IDatabaseDao;
import de.thm.arsnova.entities.Session;
import de.thm.arsnova.entities.User;
import de.thm.arsnova.entities.transport.LearningProgressValues;
import de.thm.arsnova.persistance.SessionStatisticsRepository;
/**
* Base class for the learning progress feature that allows filtering on the question variant.
......@@ -31,14 +31,14 @@ abstract class VariantLearningProgress implements LearningProgress {
private String questionVariant;
private final IDatabaseDao databaseDao;
private final SessionStatisticsRepository sessionStatisticsRepository;
public VariantLearningProgress(final IDatabaseDao dao) {
this.databaseDao = dao;
public VariantLearningProgress(final SessionStatisticsRepository sessionStatisticsRepository) {
this.sessionStatisticsRepository = sessionStatisticsRepository;
}
private void loadProgress(final Session session) {
this.courseScore = databaseDao.getLearningProgress(session);
this.courseScore = sessionStatisticsRepository.getLearningProgress(session);
}
public void setQuestionVariant(final String variant) {
......
......@@ -17,6 +17,8 @@
*/
package de.thm.arsnova.entities;
import com.fasterxml.jackson.annotation.JsonView;
import de.thm.arsnova.entities.serialization.View;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
......@@ -24,45 +26,52 @@ import io.swagger.annotations.ApiModelProperty;
* This class represents a list of motdkeys for a user.
*/
@ApiModel(value = "motdlist", description = "the motdlist to save the messages a user has confirmed to be read")
public class MotdList {
public class MotdList implements Entity {
private String id;
private String rev;
private String motdkeys;
private String username;
private String _id;
private String _rev;
@ApiModelProperty(required = true, value = "the couchDB ID")
@JsonView({View.Persistence.class, View.Public.class})
public String getId() {
return id;
}
@JsonView({View.Persistence.class, View.Public.class})
public void setId(final String id) {
this.id = id;
}
@JsonView({View.Persistence.class, View.Public.class})
public void setRevision(final String rev) {
this.rev = rev;
}
@JsonView({View.Persistence.class, View.Public.class})
public String getRevision() {
return rev;
}
@ApiModelProperty(required = true, value = "the motdkeylist")
@JsonView({View.Persistence.class, View.Public.class})
public String getMotdkeys() {
return motdkeys;
}
@JsonView({View.Persistence.class, View.Public.class})
public void setMotdkeys(String motds) {
motdkeys = motds;
}
@ApiModelProperty(required = true, value = "the username")
@JsonView({View.Persistence.class, View.Public.class})
public String getUsername() {
return username;
}
@JsonView({View.Persistence.class, View.Public.class})
public void setUsername(final String u) {
username = u;
}
@ApiModelProperty(required = true, value = "the couchDB ID")
public String get_id() {
return _id;
}
public void set_id(final String id) {
_id = id;
}
public void set_rev(final String rev) {
_rev = rev;
}
public String get_rev() {
return _rev;
}
}
......@@ -27,6 +27,7 @@ import de.thm.arsnova.entities.Entity;
import de.thm.arsnova.entities.LogEntry;
import de.thm.arsnova.entities.Motd;
import de.thm.arsnova.entities.Content;
import de.thm.arsnova.entities.MotdList;
import de.thm.arsnova.entities.Session;
import java.util.HashMap;
......@@ -39,6 +40,7 @@ public class CouchDbTypeFieldConverter implements Converter<Class<? extends Enti
typeMapping.put(LogEntry.class, "log");
typeMapping.put(DbUser.class, "userdetails");
typeMapping.put(Motd.class, "motd");
typeMapping.put(MotdList.class, "motdlist");
typeMapping.put(Session.class, "session");
typeMapping.put(Comment.class, "interposed_question");
typeMapping.put(Content.class, "skill_question");
......
package de.thm.arsnova.persistance;
import de.thm.arsnova.entities.MotdList;
public interface MotdListRepository {
MotdList getMotdListForUser(final String username);
MotdList createOrUpdateMotdList(MotdList motdlist);
}
package de.thm.arsnova.persistance;
import de.thm.arsnova.domain.CourseScore;
import de.thm.arsnova.entities.Session;
public interface SessionStatisticsRepository {
CourseScore getLearningProgress(Session session);
}
package de.thm.arsnova.persistance;
import de.thm.arsnova.entities.Statistics;
public interface StatisticsRepository {
Statistics getStatistics();
}