diff --git a/pom.xml b/pom.xml index 25badea9e5aea07f49f1ce8391b45a89939b75cd..62999f7c4d4a5bdada9a88073631a96fc3f11b0d 100644 --- a/pom.xml +++ b/pom.xml @@ -318,6 +318,12 @@ <version>2.25.0</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <version>1.9.5</version> + <scope>test</scope> + </dependency> <!-- <dependency> <groupId>org.codehaus.groovy</groupId> diff --git a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java index e6968a9f23b6dc33a495d4915bcea48a3847615f..5fa519c1c8b76807a5d66d2fac8f025a5e3b93d1 100644 --- a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java +++ b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java @@ -181,11 +181,11 @@ public class LecturerQuestionController extends AbstractController { @RequestMapping(value = "/unanswered", method = RequestMethod.GET) @ResponseBody - public final List<String> getUnAnsweredSkillQuestions( + public final List<String> getUnAnsweredSkillQuestionIds( @RequestParam final String sessionkey, final HttpServletResponse response ) { - List<String> answers = questionService.getUnAnsweredQuestions(sessionkey); + List<String> answers = questionService.getUnAnsweredQuestionIds(sessionkey); if (answers == null || answers.isEmpty()) { throw new NoContentException(); } diff --git a/src/main/java/de/thm/arsnova/controller/SessionController.java b/src/main/java/de/thm/arsnova/controller/SessionController.java index 60abe91e7cffd9b9d55554e28571c8e7781d2fca..6d46ace9596a0a11ab8de143bce45c90349229b2 100644 --- a/src/main/java/de/thm/arsnova/controller/SessionController.java +++ b/src/main/java/de/thm/arsnova/controller/SessionController.java @@ -39,7 +39,6 @@ import org.springframework.web.bind.annotation.ResponseStatus; import de.thm.arsnova.connector.model.Course; import de.thm.arsnova.entities.LoggedIn; -import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; import de.thm.arsnova.services.ISessionService; @@ -65,6 +64,13 @@ public class SessionController extends AbstractController { return sessionService.joinSession(sessionkey); } + @RequestMapping(value = "/{sessionkey}", method = RequestMethod.DELETE) + @ResponseBody + public final void deleteSession(@PathVariable final String sessionkey) { + User user = userService.getCurrentUser(); + sessionService.deleteSession(sessionkey, user); + } + @RequestMapping(value = "/{sessionkey}/online", method = RequestMethod.POST) @ResponseBody @ResponseStatus(HttpStatus.CREATED) @@ -160,7 +166,7 @@ public class SessionController extends AbstractController { return sessions; } - + @RequestMapping(value = "/{sessionkey}/lock", method = RequestMethod.POST) @ResponseBody public final Session lockSession( diff --git a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java index 0a1362ec24425ef1597f76601b9f8c1941cf7123..a48c1f0eae6e240f2062cca9194808cc835c7c8e 100644 --- a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java +++ b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java @@ -557,14 +557,14 @@ public class CouchDBDao implements IDatabaseDao { q.put("subject", question.getSubject()); q.put("text", question.getText()); q.put("active", question.isActive()); - q.put("number", 0); // TODO This number has to get incremented - // automatically + q.put("number", 0); // TODO: This number is now unused. A clean up is necessary. q.put("releasedFor", question.getReleasedFor()); q.put("possibleAnswers", question.getPossibleAnswers()); q.put("noCorrect", question.isNoCorrect()); q.put("piRound", question.getPiRound()); q.put("showStatistic", question.isShowStatistic()); q.put("showAnswer", question.isShowAnswer()); + q.put("abstention", question.isAbstention()); try { database.saveDocument(q); question.set_id(q.getId()); @@ -589,6 +589,7 @@ public class CouchDBDao implements IDatabaseDao { q.put("piRound", question.getPiRound()); q.put("showStatistic", question.isShowStatistic()); q.put("showAnswer", question.isShowAnswer()); + q.put("abstention", question.isAbstention()); this.database.saveDocument(q); question.set_rev(q.getRev()); @@ -772,7 +773,7 @@ public class CouchDBDao implements IDatabaseDao { } @Override - public final void deleteQuestion(final Question question) { + public final void deleteQuestionWithAnswers(final Question question) { try { this.deleteAnswers(question); this.deleteDocument(question.get_id()); @@ -802,7 +803,7 @@ public class CouchDBDao implements IDatabaseDao { } @Override - public final List<String> getUnAnsweredQuestions(final Session session, final User user) { + public final List<String> getUnAnsweredQuestionIds(final Session session, final User user) { try { View view = new View("answer/by_user"); view.setKey( @@ -987,7 +988,7 @@ public class CouchDBDao implements IDatabaseDao { @Override public List<Answer> getFreetextAnswers(String questionId) { try { - View view = new View("skill_question/freetext_answers"); + View view = new View("skill_question/freetext_answers_full"); view.setKey(URLEncoder.encode("\"" + questionId + "\"", "UTF-8")); ViewResults results = this.getDatabase().view(view); if (results.getResults().isEmpty()) { @@ -996,8 +997,6 @@ public class CouchDBDao implements IDatabaseDao { List<Answer> answers = new ArrayList<Answer>(); for (Document d : results.getResults()) { Answer a = (Answer) JSONObject.toBean(d.getJSONObject().getJSONObject("value"), Answer.class); - a.setAnswerSubject(d.getJSONObject().getJSONObject("value").getString("subject")); - a.setAnswerText(d.getJSONObject().getJSONObject("value").getString("text")); a.setQuestionId(questionId); answers.add(a); } @@ -1021,7 +1020,7 @@ public class CouchDBDao implements IDatabaseDao { } try { - View view = new View("answer/by_user_and_session"); + View view = new View("answer/by_user_and_session_full"); view.setKey( "[" + URLEncoder.encode("\"" + user.getUsername() + "\",\"" + s.get_id() + "\"", "UTF-8") + "]" ); @@ -1359,6 +1358,7 @@ public class CouchDBDao implements IDatabaseDao { a.put("timestamp", answer.getTimestamp()); a.put("user", user.getUsername()); a.put("piRound", answer.getPiRound()); + a.put("abstention", answer.isAbstention()); this.database.saveDocument(a); answer.set_id(a.getId()); answer.set_rev(a.getRev()); @@ -1376,6 +1376,7 @@ public class CouchDBDao implements IDatabaseDao { a.put("answerSubject", answer.getAnswerSubject()); a.put("answerText", answer.getAnswerText()); a.put("timestamp", answer.getTimestamp()); + a.put("abstention", answer.isAbstention()); this.database.saveDocument(a); answer.set_rev(a.getRev()); return answer; @@ -1507,4 +1508,13 @@ public class CouchDBDao implements IDatabaseDao { } return null; } + + @Override + public void deleteSession(Session session) { + try { + this.deleteDocument(session.get_id()); + } catch (IOException e) { + LOGGER.error("Could not delete session {}", session); + } + } } diff --git a/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java b/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java index 10f30b505e1466cd31337493135f09ae9be43911..72afe35aef7a11305700c3c69915b7c30fe3659e 100644 --- a/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java +++ b/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java @@ -68,9 +68,9 @@ public interface IDatabaseDao { List<String> getQuestionIds(Session session, User user); - void deleteQuestion(Question question); + void deleteQuestionWithAnswers(Question question); - List<String> getUnAnsweredQuestions(Session session, User user); + List<String> getUnAnsweredQuestionIds(Session session, User user); Answer getMyAnswer(String questionId, int piRound); @@ -135,4 +135,6 @@ public interface IDatabaseDao { Session lockSession(Session session, Boolean lock); List<String> getActiveUsers(int timeDifference); + + void deleteSession(Session session); } diff --git a/src/main/java/de/thm/arsnova/entities/Answer.java b/src/main/java/de/thm/arsnova/entities/Answer.java index f18284a292e992146e7f66d3f75812a3d074b521..414d6d6547b663801bf92c68c49137953160a335 100644 --- a/src/main/java/de/thm/arsnova/entities/Answer.java +++ b/src/main/java/de/thm/arsnova/entities/Answer.java @@ -14,6 +14,7 @@ public class Answer { private String user; private long timestamp; private int answerCount = 1; + private boolean abstention; // Currently available only for freetext answers! public Answer() { this.type = "skill_question_answer"; @@ -106,6 +107,14 @@ public class Answer { public final void setAnswerCount(final int answerCount) { this.answerCount = answerCount; } + + public boolean isAbstention() { + return abstention; + } + + public void setAbstention(boolean abstention) { + this.abstention = abstention; + } @Override public final String toString() { diff --git a/src/main/java/de/thm/arsnova/entities/Question.java b/src/main/java/de/thm/arsnova/entities/Question.java index ee0b29eafcb3295678622d070fb14095d6ec2aab..de56243c017d4932b02ae8ae76fbe832d2b9045c 100644 --- a/src/main/java/de/thm/arsnova/entities/Question.java +++ b/src/main/java/de/thm/arsnova/entities/Question.java @@ -39,6 +39,7 @@ public class Question { private int piRound; private boolean showStatistic; // sic private boolean showAnswer; + private boolean abstention; private String _id; private String _rev; @@ -178,6 +179,14 @@ public class Question { this.showAnswer = showAnswer; } + public boolean isAbstention() { + return abstention; + } + + public void setAbstention(boolean abstention) { + this.abstention = abstention; + } + public final String get_id() { return _id; } diff --git a/src/main/java/de/thm/arsnova/services/FeedbackService.java b/src/main/java/de/thm/arsnova/services/FeedbackService.java index 53a0ee4f793ebd5cca1061025b17b78ed6f55410..21e808ce9930a55bda773905fb70d681f12f21ab 100644 --- a/src/main/java/de/thm/arsnova/services/FeedbackService.java +++ b/src/main/java/de/thm/arsnova/services/FeedbackService.java @@ -19,7 +19,8 @@ package de.thm.arsnova.services; -import java.util.HashSet; +import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -128,8 +129,8 @@ public class FeedbackService implements IFeedbackService { this.server.reportDeletedFeedback(e.getKey(), e.getValue()); } } - for(String session : allAffectedSessions) { - this.server.reportUpdatedFeedbackForSession(session); + for (String session : allAffectedSessions) { + this.server.reportUpdatedFeedbackForSession(session); } } @@ -138,4 +139,82 @@ public class FeedbackService implements IFeedbackService { public final Integer getMyFeedback(final String keyword, final User user) { return this.databaseDao.getMyFeedback(keyword, user); } + + private static class FeedbackStorageObject { + private int value; + private Date timestamp; + + public FeedbackStorageObject(int initValue) { + this.value = initValue; + this.timestamp = new Date(); + } + + public int getValue() { + return value; + } + public Date getTimestamp() { + return timestamp; + } + } + + private static class FeedbackStorage { + private Map<String, Map<String, FeedbackStorageObject>> data; + + public Feedback getFeedback(String keyword) { + int a = 0; + int b = 0; + int c = 0; + int d = 0; + + if (data.get(keyword) == null) { + return new Feedback(0, 0, 0, 0); + } + + for (FeedbackStorageObject fso : data.get(keyword).values()) { + switch (fso.getValue()) { + case 0: + a++; + break; + case 1: + b++; + break; + case 2: + c++; + break; + case 3: + d++; + break; + } + } + + return new Feedback(a, b, c, d); + } + + public boolean saveFeedback(String keyword, int value, User user) { + if (data.get(keyword) == null) { + data.put(keyword, new HashMap<String, FeedbackStorageObject>()); + } + data.get(keyword).put(user.getUsername(), new FeedbackStorageObject(value)); + return true; + } + + public void cleanFeedbackVotes(int cleanupFeedbackDelay) { + for (String keyword : data.keySet()) { + this.cleanSessionFeedbackVotes(keyword, cleanupFeedbackDelay); + } + } + + private void cleanSessionFeedbackVotes(String keyword, int cleanupFeedbackDelay) { + final long timelimitInMillis = 60000 * (long) cleanupFeedbackDelay; + final long maxAllowedTimeInMillis = System.currentTimeMillis() - timelimitInMillis; + + Map<String, FeedbackStorageObject> sessionFeedbacks = data.get(keyword); + + for ( FeedbackStorageObject fso : sessionFeedbacks.values() ) { + if (fso.getTimestamp().getTime() < maxAllowedTimeInMillis) { + sessionFeedbacks.remove(fso); + } + } + } + } } diff --git a/src/main/java/de/thm/arsnova/services/IQuestionService.java b/src/main/java/de/thm/arsnova/services/IQuestionService.java index b51281e46847cbfbf5e1eef65b2443053c3619b6..389e1442a4726e0e9dc696db7426f76377ae0908 100644 --- a/src/main/java/de/thm/arsnova/services/IQuestionService.java +++ b/src/main/java/de/thm/arsnova/services/IQuestionService.java @@ -41,7 +41,7 @@ public interface IQuestionService { void deleteQuestion(String questionId); - List<String> getUnAnsweredQuestions(String sessionKey); + List<String> getUnAnsweredQuestionIds(String sessionKey); Answer getMyAnswer(String questionId); diff --git a/src/main/java/de/thm/arsnova/services/ISessionService.java b/src/main/java/de/thm/arsnova/services/ISessionService.java index 5687d1172bbc249d207eb0c62fff1cd586e087cb..7efe5e0491ac75935814e5b3af7c07b2822590ef 100644 --- a/src/main/java/de/thm/arsnova/services/ISessionService.java +++ b/src/main/java/de/thm/arsnova/services/ISessionService.java @@ -49,4 +49,6 @@ public interface ISessionService { Session setActive(String sessionkey, Boolean lock); Session joinSession(String keyword, UUID socketId); + + void deleteSession(String sessionkey, User user); } diff --git a/src/main/java/de/thm/arsnova/services/QuestionService.java b/src/main/java/de/thm/arsnova/services/QuestionService.java index acb949662acb2d3caab4c00cbf73299a14b39994..b1efb373be88e396240eec04d50eae175ab247aa 100644 --- a/src/main/java/de/thm/arsnova/services/QuestionService.java +++ b/src/main/java/de/thm/arsnova/services/QuestionService.java @@ -146,7 +146,7 @@ public class QuestionService implements IQuestionService { if (user == null || session == null || !session.isCreator(user)) { throw new UnauthorizedException(); } - databaseDao.deleteQuestion(question); + databaseDao.deleteQuestionWithAnswers(question); } @Override @@ -182,7 +182,7 @@ public class QuestionService implements IQuestionService { @Override @Authenticated - public List<String> getUnAnsweredQuestions(String sessionKey) { + public List<String> getUnAnsweredQuestionIds(String sessionKey) { User user = userService.getCurrentUser(); if (user == null) { throw new UnauthorizedException(); @@ -191,7 +191,7 @@ public class QuestionService implements IQuestionService { if (session == null) { throw new NotFoundException(); } - return databaseDao.getUnAnsweredQuestions(session, user); + return databaseDao.getUnAnsweredQuestionIds(session, user); } @Override diff --git a/src/main/java/de/thm/arsnova/services/SessionService.java b/src/main/java/de/thm/arsnova/services/SessionService.java index a003462b49082e5841a85034c4989115860e6e37..1a4473b3f81de6dca9f56b6a98f12b9459c4038b 100644 --- a/src/main/java/de/thm/arsnova/services/SessionService.java +++ b/src/main/java/de/thm/arsnova/services/SessionService.java @@ -21,7 +21,6 @@ package de.thm.arsnova.services; import java.io.Serializable; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -36,6 +35,7 @@ import de.thm.arsnova.connector.client.ConnectorClient; import de.thm.arsnova.connector.model.Course; import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.LoggedIn; +import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; import de.thm.arsnova.exceptions.ForbiddenException; @@ -56,7 +56,7 @@ public class SessionService implements ISessionService { @Autowired private ARSnovaSocketIOServer socketIoServer; - @Autowired(required=false) + @Autowired(required = false) private ConnectorClient connectorClient; public void setDatabaseDao(final IDatabaseDao newDatabaseDao) { @@ -117,26 +117,22 @@ public class SessionService implements ISessionService { if (connectorClient == null) { return mySessions; } - + List<Session> courseSessions = databaseDao.getCourseSessions( connectorClient.getCourses(user.getUsername()).getCourse() ); - + Map<String, Session> allAvailableSessions = new HashMap<String, Session>(); - + for (Session session : mySessions) { allAvailableSessions.put(session.get_id(), session); } - for (Session session : courseSessions) { allAvailableSessions.put(session.get_id(), session); } - - List<Session> result = new ArrayList<Session>(allAvailableSessions.values()); - - return result; + return new ArrayList<Session>(allAvailableSessions.values()); } - + @Override public final List<Session> getMyVisitedSessions(final User user) { return databaseDao.getMyVisitedSessions(user); @@ -152,7 +148,6 @@ public class SessionService implements ISessionService { throw new ForbiddenException(); } } - return databaseDao.saveSession(session); } @@ -196,7 +191,7 @@ public class SessionService implements ISessionService { Session session = databaseDao.getSessionFromKeyword(sessionkey); return databaseDao.countActiveUsers(session, since); } - + public static class SessionNameComperator implements Comparator<Session>, Serializable { private static final long serialVersionUID = 1L; @@ -205,8 +200,8 @@ public class SessionService implements ISessionService { return session1.getName().compareToIgnoreCase(session2.getName()); } } - - public static class SessionShortNameComperator implements Comparator<Session>, Serializable{ + + public static class SessionShortNameComperator implements Comparator<Session>, Serializable { private static final long serialVersionUID = 1L; @Override @@ -229,4 +224,17 @@ public class SessionService implements ISessionService { Session session = databaseDao.getSessionFromKeyword(sessionkey); return databaseDao.lockSession(session, lock); } + + @Override + @Authenticated + public void deleteSession(String sessionkey, User user) { + Session session = databaseDao.getSession(sessionkey); + if (!session.isCreator(user)) { + throw new ForbiddenException(); + } + for (Question q : databaseDao.getSkillQuestions(sessionkey)) { + databaseDao.deleteQuestionWithAnswers(q); + } + databaseDao.deleteSession(session); + } } diff --git a/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java b/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java index 1aed9229d4c128cb29f6c2affc5940e1d5503b9f..37750ff80cbf436768c341d0ff483e2a30e2e9ec 100644 --- a/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java +++ b/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java @@ -367,7 +367,7 @@ public class StubDatabaseDao implements IDatabaseDao { } @Override - public List<String> getUnAnsweredQuestions(Session session, User user) { + public List<String> getUnAnsweredQuestionIds(Session session, User user) { // TODO Auto-generated method stub return null; } @@ -379,7 +379,7 @@ public class StubDatabaseDao implements IDatabaseDao { } @Override - public void deleteQuestion(Question question) { + public void deleteQuestionWithAnswers(Question question) { // TODO Auto-generated method stub } @@ -432,4 +432,9 @@ public class StubDatabaseDao implements IDatabaseDao { // TODO Auto-generated method stub return null; } + + @Override + public void deleteSession(Session session) { + // TODO Auto-generated method stub + } } diff --git a/src/test/java/de/thm/arsnova/services/SessionServiceTest.java b/src/test/java/de/thm/arsnova/services/SessionServiceTest.java index f7c4a52f6628a93398d3601ce5ccc934a4bd7386..c6ede4f8dfd05792f6996ab9a81aed41f0cb3a35 100644 --- a/src/test/java/de/thm/arsnova/services/SessionServiceTest.java +++ b/src/test/java/de/thm/arsnova/services/SessionServiceTest.java @@ -22,14 +22,23 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.util.Arrays; + +import static org.mockito.Mockito.*; + import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.util.ReflectionTestUtils; +import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.dao.StubDatabaseDao; +import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.Session; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.exceptions.UnauthorizedException; @@ -55,7 +64,7 @@ public class SessionServiceTest { databaseDao.cleanupTestData(); userService.setUserAuthenticated(false); } - + @Test public void testShouldGenerateSessionKeyword() { System.out.println(sessionService.generateKeyword()); @@ -104,4 +113,42 @@ public class SessionServiceTest { sessionService.saveSession(session); assertNotNull(sessionService.joinSession("11111111")); } + + @Test + public void testShouldDeleteAllSessionData() { + IDatabaseDao tempDatabase = (IDatabaseDao) ReflectionTestUtils.getField(getTargetObject(sessionService), "databaseDao"); + try { + userService.setUserAuthenticated(true); + + Session session = new Session(); + session.setCreator(userService.getCurrentUser().getUsername()); + Question q1 = new Question(); + Question q2 = new Question(); + + IDatabaseDao mockDatabase = mock(IDatabaseDao.class); + when(mockDatabase.getSkillQuestions(anyString())).thenReturn(Arrays.asList(q1, q2)); + when(mockDatabase.getSession(anyString())).thenReturn(session); + ReflectionTestUtils.setField(getTargetObject(sessionService), "databaseDao", mockDatabase); + + sessionService.deleteSession(session.getKeyword(), userService.getCurrentUser()); + + verify(mockDatabase).deleteQuestionWithAnswers(q1); + verify(mockDatabase).deleteQuestionWithAnswers(q2); + verify(mockDatabase).deleteSession(session); + } finally { + ReflectionTestUtils.setField(getTargetObject(sessionService), "databaseDao", tempDatabase); + } + } + + @SuppressWarnings("unchecked") + public static <T> T getTargetObject(Object proxy) { + if ((AopUtils.isJdkDynamicProxy(proxy))) { + try { + return (T) getTargetObject(((Advised) proxy).getTargetSource().getTarget()); + } catch (Exception e) { + throw new RuntimeException("Failed to unproxy target.", e); + } + } + return (T) proxy; + } } \ No newline at end of file