diff --git a/pom.xml b/pom.xml index 74722f5c2e3512aa7961500520ff570fed817a05..ed9b73f593aec493f187c7eacd2313bbe53085a8 100644 --- a/pom.xml +++ b/pom.xml @@ -258,7 +258,7 @@ com.github.leleuj.springframework.security </groupId> <artifactId>spring-security-oauth-client</artifactId> - <version>1.0.0-SNAPSHOT</version> + <version>1.0.0</version> </dependency> <dependency> <groupId>io.netty</groupId> diff --git a/src/main/java/de/thm/arsnova/controller/QuestionController.java b/src/main/java/de/thm/arsnova/controller/QuestionController.java index f2d1baade8533e2374c55eca5b25891a654c7be9..3f7b63a05c4243c2a766f63491a2ea096069fc4d 100644 --- a/src/main/java/de/thm/arsnova/controller/QuestionController.java +++ b/src/main/java/de/thm/arsnova/controller/QuestionController.java @@ -219,5 +219,16 @@ public class QuestionController extends AbstractController { ) { return questionService.getAnswerCount(sessionKey, questionId); } + + @RequestMapping(value = "/session/{sessionKey}/question/{questionId}/freetextanswers", method = RequestMethod.GET) + @ResponseBody + public final List<Answer> getFreetextAnswers( + @PathVariable final String sessionKey, + @PathVariable final String questionId, + final HttpServletResponse response + ) { + return questionService.getFreetextAnswers(sessionKey, questionId); + } + } diff --git a/src/main/java/de/thm/arsnova/controller/StatisticsController.java b/src/main/java/de/thm/arsnova/controller/StatisticsController.java new file mode 100644 index 0000000000000000000000000000000000000000..89346a5e36884a544bc47e9637b3029ad93e62a7 --- /dev/null +++ b/src/main/java/de/thm/arsnova/controller/StatisticsController.java @@ -0,0 +1,22 @@ +package de.thm.arsnova.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import de.thm.arsnova.services.IStatisticsService; + +@Controller +public class StatisticsController { + + @Autowired + private IStatisticsService statisticsService; + + @RequestMapping(method = RequestMethod.GET, value = "/statistics/activeusercount") + @ResponseBody + public final int countActiveUsers() { + return statisticsService.countActiveUsers(); + } +} diff --git a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java index 94de69dfc10c7a65ab0de31284e557a66f57dd44..0273a70b7854a7100881e5e8e550b8c425d07cc7 100644 --- a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java +++ b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java @@ -142,7 +142,10 @@ public class CouchDBDao implements IDatabaseDao { } catch (IOException e) { LOGGER.error("Could not delete Feedback document " + d.getId()); } catch (JSONException e) { - LOGGER.error("Could not delete Feedback document {}, error is: {} ", new Object[] {d.getId(), e}); + LOGGER.error( + "Could not delete Feedback document {}, error is: {} ", + new Object[] {d.getId(), e} + ); } } if (!results.isEmpty()) { @@ -182,7 +185,10 @@ public class CouchDBDao implements IDatabaseDao { List<Session> result = new ArrayList<Session>(); for (Document d : sessions.getResults()) { - Session session = (Session) JSONObject.toBean(d.getJSONObject().getJSONObject("value"), Session.class); + Session session = (Session) JSONObject.toBean( + d.getJSONObject().getJSONObject("value"), + Session.class + ); session.set_id(d.getId()); result.add(session); } @@ -212,12 +218,18 @@ public class CouchDBDao implements IDatabaseDao { MorpherRegistry morpherRegistry = JSONUtils.getMorpherRegistry(); Morpher dynaMorpher = new BeanMorpher(PossibleAnswer.class, morpherRegistry); morpherRegistry.registerMorpher(dynaMorpher); - for (Document d : questions.getResults()) { - Question q = (Question) JSONObject.toBean(d.getJSONObject().getJSONObject("value"), Question.class); - Collection<PossibleAnswer> answers = JSONArray.toCollection(d.getJSONObject().getJSONObject("value") - .getJSONArray("possibleAnswers"), PossibleAnswer.class); - q.setPossibleAnswers(new ArrayList<PossibleAnswer>(answers)); - result.add(q); + for (Document document : questions.getResults()) { + Question question = (Question) JSONObject.toBean( + document.getJSONObject().getJSONObject("value"), + Question.class + ); + Collection<PossibleAnswer> answers = JSONArray.toCollection( + document.getJSONObject().getJSONObject("value") + .getJSONArray("possibleAnswers"), + PossibleAnswer.class + ); + question.setPossibleAnswers(new ArrayList<PossibleAnswer>(answers)); + result.add(question); } return result; @@ -254,8 +266,10 @@ public class CouchDBDao implements IDatabaseDao { if (results.getJSONArray("rows").optJSONObject(0) == null) { return null; } - return (Session) JSONObject.toBean(results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), - Session.class); + return (Session) JSONObject.toBean( + results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), + Session.class + ); } catch (UnsupportedEncodingException e) { return null; } @@ -298,7 +312,7 @@ public class CouchDBDao implements IDatabaseDao { } private Feedback createFeedbackObject(final ViewResults results) { - int values[] = {0, 0, 0, 0}; + int[] values = {0, 0, 0, 0}; JSONArray rows = results.getJSONArray("rows"); try { @@ -336,7 +350,11 @@ public class CouchDBDao implements IDatabaseDao { } @Override - public final boolean saveFeedback(final String keyword, final int value, final de.thm.arsnova.entities.User user) { + public final boolean saveFeedback( + final String keyword, + final int value, + final de.thm.arsnova.entities.User user + ) { String sessionId = this.getSessionId(keyword); if (sessionId == null) { return false; @@ -382,7 +400,12 @@ public class CouchDBDao implements IDatabaseDao { private List<Document> findPreviousFeedback(final String sessionId, final de.thm.arsnova.entities.User user) { View view = new View("understanding/by_user"); try { - view.setKey(URLEncoder.encode("[\"" + sessionId + "\", \"" + user.getUsername() + "\"]", "UTF-8")); + view.setKey( + URLEncoder.encode( + "[\"" + sessionId + "\",\"" + user.getUsername() + "\"]", + "UTF-8" + ) + ); } catch (UnsupportedEncodingException e) { return Collections.<Document> emptyList(); } @@ -468,11 +491,17 @@ public class CouchDBDao implements IDatabaseDao { private Database getDatabase() { if (database == null) { try { - com.fourspaces.couchdb.Session session = new com.fourspaces.couchdb.Session(databaseHost, databasePort); + com.fourspaces.couchdb.Session session = new com.fourspaces.couchdb.Session( + databaseHost, + databasePort + ); database = session.getDatabase(databaseName); } catch (Exception e) { - LOGGER.error("Cannot connect to CouchDB database '" + databaseName + "' on host '" + databaseHost - + "' using port " + databasePort); + LOGGER.error( + "Cannot connect to CouchDB database '" + databaseName + + "' on host '" + databaseHost + + "' using port " + databasePort + ); } } @@ -488,7 +517,7 @@ 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 + q.put("number", 0); // TODO This number has to get incremented // automatically q.put("releasedFor", question.getReleasedFor()); q.put("possibleAnswers", question.getPossibleAnswers()); @@ -517,10 +546,15 @@ public class CouchDBDao implements IDatabaseDao { } Question q = (Question) JSONObject.toBean( - results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), Question.class); + results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), + Question.class + ); JSONArray possibleAnswers = results.getJSONArray("rows").optJSONObject(0).optJSONObject("value") .getJSONArray("possibleAnswers"); - Collection<PossibleAnswer> answers = JSONArray.toCollection(possibleAnswers, PossibleAnswer.class); + Collection<PossibleAnswer> answers = JSONArray.toCollection( + possibleAnswers, + PossibleAnswer.class + ); q.setPossibleAnswers(new ArrayList<PossibleAnswer>(answers)); if (s.get_id().equals(q.getSessionId())) { @@ -601,7 +635,12 @@ public class CouchDBDao implements IDatabaseDao { } View view = new View("understanding/by_user"); - view.setKey(URLEncoder.encode("[\"" + sessionId + "\", \"" + user.getUsername() + "\"]", "UTF-8")); + view.setKey( + URLEncoder.encode( + "[\"" + sessionId + "\", \"" + user.getUsername() + "\"]", + "UTF-8" + ) + ); ViewResults results = this.getDatabase().view(view); JSONArray rows = results.getJSONArray("rows"); @@ -676,8 +715,10 @@ public class CouchDBDao implements IDatabaseDao { } catch (IOException e) { LOGGER.error( - "IOException: Could not delete question and its answers with id {}. Connection to CouchDB available?", - questionId); + "IOException: Could not delete question and its answers with id {}." + + " Connection to CouchDB available?", + questionId + ); } } @@ -695,7 +736,13 @@ public class CouchDBDao implements IDatabaseDao { try { View view = new View("answer/by_user"); - view.setKey("[" + URLEncoder.encode("\"" + user.getUsername() + "\",\"" + s.get_id() + "\"", "UTF-8") + "]"); + view.setKey( + "[" + URLEncoder.encode( + "\"" + user.getUsername() + "\",\"" + s.get_id() + "\"", + "UTF-8" + ) + + "]" + ); ViewResults anseweredQuestions = this.getDatabase().view(view); List<String> answered = new ArrayList<String>(); @@ -732,13 +779,21 @@ public class CouchDBDao implements IDatabaseDao { try { View view = new View("answer/by_question_and_user"); - view.setKey("[" + URLEncoder.encode("\"" + questionId + "\",\"" + user.getUsername() + "\"", "UTF-8") + "]"); + view.setKey( + "[" + URLEncoder.encode( + "\"" + questionId + "\",\"" + user.getUsername() + "\"", + "UTF-8" + ) + + "]" + ); ViewResults results = this.getDatabase().view(view); if (results.getResults().isEmpty()) { throw new NotFoundException(); } - return (Answer) JSONObject.toBean(results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), - Answer.class); + return (Answer) JSONObject.toBean( + results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), + Answer.class + ); } catch (UnsupportedEncodingException e) { LOGGER.error( "Error while retrieving answer for user {} and question {}, {}", @@ -800,4 +855,55 @@ public class CouchDBDao implements IDatabaseDao { } return 0; } + + @Override + public final int getActiveUsers(final long since) { + try { + View view = new View("statistic/count_active_users"); + view.setStartKey(String.valueOf(since)); + ViewResults results = this.getDatabase().view(view); + LOGGER.info("getActiveUsers() {}", results); + if ( + results == null + || results.getResults().isEmpty() + || results.getJSONArray("rows").size() == 0 + ) { + return 0; + } + return results.getJSONArray("rows").optJSONObject(0).getInt("value"); + } catch (Exception e) { + LOGGER.error("Error while retrieving active users count", e); + } + return 0; + } + + @Override + public List<Answer> getFreetextAnswers(String sessionKey, String questionId) { + Session s = this.getSessionFromKeyword(sessionKey); + if (s == null) { + throw new NotFoundException(); + } + + try { + View view = new View("skill_question/freetext_answers"); + view.setKey(URLEncoder.encode("\"" + questionId + "\"", "UTF-8")); + ViewResults results = this.getDatabase().view(view); + if (results.getResults().isEmpty()) { + throw new NotFoundException(); + } + List<Answer> answers = new ArrayList<Answer>(); + for (Document d : results.getResults()) { + Answer a = (Answer) JSONObject.toBean(d.getJSONObject().getJSONObject("value"), Answer.class); + a.setSessionId(s.get_id()); + a.setQuestionId(questionId); + answers.add(a); + } + return answers; + + } catch (UnsupportedEncodingException e) { + LOGGER.error("Error while retrieving freetext answers", e); + } + + return null; + } } diff --git a/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java b/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java index 524a8d98ac560bbb8b26e2e4636cd3b53886a1ec..dc5f7ebd245de090b819951ea719f5971871f85a 100644 --- a/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java +++ b/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java @@ -23,8 +23,8 @@ import java.util.List; import de.thm.arsnova.entities.Answer; import de.thm.arsnova.entities.Feedback; -import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.LoggedIn; +import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; @@ -70,4 +70,8 @@ public interface IDatabaseDao { List<Answer> getAnswers(String sessionKey, String questionId); int getAnswerCount(String sessionKey, String questionId); + + List<Answer> getFreetextAnswers(String sessionKey, String questionId); + + int getActiveUsers(long since); } diff --git a/src/main/java/de/thm/arsnova/entities/Answer.java b/src/main/java/de/thm/arsnova/entities/Answer.java index 3a3ef5d4581c88fd2e984068e8a6a06898f6f8dd..4159ea62f2faaa1ca71d818a798c59988d4bc991 100644 --- a/src/main/java/de/thm/arsnova/entities/Answer.java +++ b/src/main/java/de/thm/arsnova/entities/Answer.java @@ -11,6 +11,7 @@ public class Answer { private String answerText; private String answerSubject; private String user; + private long timestamp; private int answerCount; public Answer() { @@ -65,6 +66,15 @@ public class Answer { this.answerText = answerText; } + public final String getText() { + return answerText; + } + + public final void setText(final String answerText) { + this.answerText = answerText; + } + + public final String getAnswerSubject() { return answerSubject; } @@ -72,6 +82,14 @@ public class Answer { public final void setAnswerSubject(final String answerSubject) { this.answerSubject = answerSubject; } + + public final String getSubject() { + return answerSubject; + } + + public final void setSubject(final String answerSubject) { + this.answerSubject = answerSubject; + } public final String getUser() { return user; @@ -81,6 +99,14 @@ public class Answer { this.user = user; } + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + public final int getAnswerCount() { return answerCount; } diff --git a/src/main/java/de/thm/arsnova/exceptions/package-info.java b/src/main/java/de/thm/arsnova/exceptions/package-info.java index 609e116927c642cf07c14b696d6d15ba5a924610..8eb89c8019f1eb2130d93eca2c90b6215046937a 100644 --- a/src/main/java/de/thm/arsnova/exceptions/package-info.java +++ b/src/main/java/de/thm/arsnova/exceptions/package-info.java @@ -1 +1 @@ -package de.thm.arsnova.exceptions; \ No newline at end of file +package de.thm.arsnova.exceptions; diff --git a/src/main/java/de/thm/arsnova/services/IQuestionService.java b/src/main/java/de/thm/arsnova/services/IQuestionService.java index ebc3704b9b510f42725c142ce107a5eaeb08aae6..a6977fe0ff1e41cf04722f3b91e6f4c5a953596d 100644 --- a/src/main/java/de/thm/arsnova/services/IQuestionService.java +++ b/src/main/java/de/thm/arsnova/services/IQuestionService.java @@ -44,4 +44,6 @@ public interface IQuestionService { List<Answer> getAnswers(String sessionKey, String questionId); int getAnswerCount(String sessionKey, String questionId); + + List<Answer> getFreetextAnswers(String sessionKey, String questionId); } diff --git a/src/main/java/de/thm/arsnova/services/IStatisticsService.java b/src/main/java/de/thm/arsnova/services/IStatisticsService.java new file mode 100644 index 0000000000000000000000000000000000000000..65a1b74ae3de1bba67146d5490770c84013f10aa --- /dev/null +++ b/src/main/java/de/thm/arsnova/services/IStatisticsService.java @@ -0,0 +1,6 @@ +package de.thm.arsnova.services; + +public interface IStatisticsService { + + int countActiveUsers(); +} diff --git a/src/main/java/de/thm/arsnova/services/QuestionService.java b/src/main/java/de/thm/arsnova/services/QuestionService.java index 7572c77f447130853ecc604ccc3e1470681a6632..e8677527457f0d1a10796bdf18b9ae987c0da40c 100644 --- a/src/main/java/de/thm/arsnova/services/QuestionService.java +++ b/src/main/java/de/thm/arsnova/services/QuestionService.java @@ -112,4 +112,10 @@ public class QuestionService implements IQuestionService { public int getAnswerCount(String sessionKey, String questionId) { return databaseDao.getAnswerCount(sessionKey, questionId); } + + @Override + @Authenticated + public List<Answer> getFreetextAnswers(String sessionKey, String questionId) { + return databaseDao.getFreetextAnswers(sessionKey, questionId); + } } diff --git a/src/main/java/de/thm/arsnova/services/StatisticsService.java b/src/main/java/de/thm/arsnova/services/StatisticsService.java new file mode 100644 index 0000000000000000000000000000000000000000..87c2e942eccbf9d384dafca9f4133776a53dd055 --- /dev/null +++ b/src/main/java/de/thm/arsnova/services/StatisticsService.java @@ -0,0 +1,21 @@ +package de.thm.arsnova.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import de.thm.arsnova.dao.IDatabaseDao; + +@Service +public class StatisticsService implements IStatisticsService { + + private static final int SINCEDURATION = 3 * 60 * 1000; + + @Autowired + private IDatabaseDao databaseDao; + + @Override + public final int countActiveUsers() { + long since = System.currentTimeMillis() - SINCEDURATION; + return databaseDao.getActiveUsers(since); + } +} diff --git a/src/main/java/de/thm/arsnova/services/package-info.java b/src/main/java/de/thm/arsnova/services/package-info.java index 12742b6cbbe5149e7ac1dd84c4ca49e7f6a6efc6..ff6c1eba938b0bfbed079b7c9d7ceeb621115f6d 100644 --- a/src/main/java/de/thm/arsnova/services/package-info.java +++ b/src/main/java/de/thm/arsnova/services/package-info.java @@ -1 +1 @@ -package de.thm.arsnova.services; \ No newline at end of file +package de.thm.arsnova.services; diff --git a/src/test/java/de/thm/arsnova/controller/StatisticsControllerTest.java b/src/test/java/de/thm/arsnova/controller/StatisticsControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8e79f54f4877d73f5c42d524d3eef57568990812 --- /dev/null +++ b/src/test/java/de/thm/arsnova/controller/StatisticsControllerTest.java @@ -0,0 +1,58 @@ +package de.thm.arsnova.controller; + +import static org.junit.Assert.assertEquals; + +import javax.inject.Inject; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.servlet.HandlerAdapter; +import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter; + +import de.thm.arsnova.services.StubUserService; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { + "file:src/main/webapp/WEB-INF/arsnova-servlet.xml", + "file:src/main/webapp/WEB-INF/spring/spring-main.xml", + "file:src/test/resources/test-config.xml" +}) +public class StatisticsControllerTest { + + @Inject + private ApplicationContext applicationContext; + private MockHttpServletRequest request; + private MockHttpServletResponse response; + private HandlerAdapter handlerAdapter; + + @Autowired + private StatisticsController statisticsController; + + @Autowired + private StubUserService userService; + + @Before + public final void setUp() { + this.request = new MockHttpServletRequest(); + this.response = new MockHttpServletResponse(); + handlerAdapter = applicationContext.getBean(AnnotationMethodHandlerAdapter.class); + } + + @Test + public final void testShouldNotGetUnknownSession() throws Exception { + userService.setUserAuthenticated(true); + + request.setMethod("GET"); + request.setRequestURI("/statistics/activeusercount"); + handlerAdapter.handle(request, response, statisticsController); + + assertEquals("0", response.getContentAsString()); + } +} diff --git a/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java b/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java index c2e8a13d18d1bf631516789bb254cc0cd83f23c7..c16ea9516aca46fdc6e4796ea5815a5c04c5a820 100644 --- a/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java +++ b/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java @@ -1,6 +1,7 @@ package de.thm.arsnova.dao; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -218,4 +219,16 @@ public class StubDatabaseDao implements IDatabaseDao { return 0; } + @Override + public List<Answer> getFreetextAnswers(String sessionKey, String questionId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getActiveUsers(long since) { + // TODO Auto-generated method stub + return 0; + } + } diff --git a/src/test/java/de/thm/arsnova/services/SessionServiceTest.java b/src/test/java/de/thm/arsnova/services/SessionServiceTest.java index f324547473909aeacc2e83c5bdea1811ea618f5b..6159c06017cef4f15df1b236cfb38850f22276bb 100644 --- a/src/test/java/de/thm/arsnova/services/SessionServiceTest.java +++ b/src/test/java/de/thm/arsnova/services/SessionServiceTest.java @@ -40,10 +40,10 @@ import de.thm.arsnova.exceptions.UnauthorizedException; public class SessionServiceTest { @Autowired - ISessionService sessionService; + private ISessionService sessionService; @Autowired - StubUserService userService; + private StubUserService userService; @Test public void testShouldGenerateSessionKeyword() { diff --git a/src/test/java/de/thm/arsnova/services/StatisticsServiceTest.java b/src/test/java/de/thm/arsnova/services/StatisticsServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5638188f902093736a594ec1470867979540c409 --- /dev/null +++ b/src/test/java/de/thm/arsnova/services/StatisticsServiceTest.java @@ -0,0 +1,29 @@ +package de.thm.arsnova.services; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { + "file:src/main/webapp/WEB-INF/arsnova-servlet.xml", + "file:src/main/webapp/WEB-INF/spring/spring-main.xml", + "file:src/test/resources/test-config.xml" +}) +public class StatisticsServiceTest { + + @Autowired + private IStatisticsService statisticsService; + + @Test + public final void testShouldReturnCurrentActiveUsers() { + int actual = statisticsService.countActiveUsers(); + //assertEquals(0, actual); + } + +}