diff --git a/README.md b/README.md index d6d63e6add5e162dad365ed5cf30ba762b66b328..0e1831feda20e2492b7014bc6c7e43f388e883f9 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,51 @@ -# ARSnova 2 +# ARSnova -This project really brings you *two* different versions of ARSnova: `arsnova-js` (ARSnova 2) and `arsnova-legacy-js` (ARSnova 1). +ARSnova is a modern approach to Audience Response Systems (ARS). It is released under the GPLv3 license, and is offered as a Software as a Service free of charge. Head over to [arsnova.thm.de](https://arsnova.thm.de/) to see it in action. -The first one is currently under heavy development and is not ready for production use. The second one is the tried-and-true ARSnova for your mobile device. However, `arsnova-legacy-js` will not receive any major updates and is nearing its end of life. It will be superseded by `arsnova-js`. + + +ARSnova consists of two projects: the mobile client and the server. This repository contains the server code. You will find the client at thm-projects/arsnova-st2-js. However, you do not need to download both respositories in order to get started. ## Getting started -Both versions of ARSnova will be deployed alongside each other, so you get to choose which one you would like to use. By default, `arsnova-legacy-js` is served via `index.html` and optionally via `developer.html`. If you want to get your hands dirty, you should open `dojo-index.html` and try out the redesigned ARSnova 2. It will work on any major browser instead of being for Webkit browsers only. +This is the main repository. Almost all dependencies (including the mobile client) are managed for you by Maven. The mobile client is served via `index.html`, and optionally via `developer.html`. -## Deployment +## Configuration You will need to do some configuration work upfront. - * Add a new directory "arsnova" in /etc and create a copy of arsnova.properties.example named arsnova.properties in this directory. + * Add a new directory "arsnova" in `/etc`, and create a copy of arsnova.properties.example named arsnova.properties in this directory. * Change settings to match your environment -## Server configuration +### Server + +In order to build up a full featured server installation containing ARSnova and CouchDB you have to install at least the following services: -In order to build up a full featured server installation containing ARSnova2 and CouchDB you have to install at least the following services: * Apache Tomcat 7.0.29 (or newer) - * Apache Webserver 2.2 or newer with buildin mod_proxy, mod_proxy_ajp and mod_proxy_http + * Apache Webserver 2.2 or newer with builtin modules `mod_proxy`, `mod_proxy_ajp` and `mod_proxy_http` * Apache CouchDB -Make sure all services are installed. Next step is to configure the Apache Webserver. Find the configuration file or create a new one for use with a virtal host. This depends on your needs. At least you should have a configuration containing these settings: +Make sure all services are installed. Next step is to configure the Apache Webserver. Find the configuration file or create a new one for use with a virtual host. This depends on your needs. At least you should have a configuration containing these settings: + + <Location /> + ProxyPass ajp://127.0.0.1:8009/ + ProxyPassReverse ajp://127.0.0.1:8009/ + </Location> + +All requests will be sent to your Apache Tomcat servlet container, using AJP running on port 8009. + +To enable the required Apache Webserver modules simply type: -<Location /couchdb/> - ProxyPass http://127.0.0.1:5984/ - ProxyPassReverse http://127.0.0.1:5984/ -</Location> -<Location /> - ProxyPass ajp://127.0.0.1:8009/ - ProxyPassReverse ajp://127.0.0.1:8009/ -</Location> + # a2enmod proxy + # a2enmod proxy_ajp + # a2enmod proxy_http -This will redirect all requests for "/couchdb/..." to your Apache CouchDB server, running on port 5984. -All other requests will be send to your Apache Tomcat servelt container, using AJP running on port 8009. +The configuration is ready for development usage. Finally, you should (re)start all services. ARSnova is now listening on HTTP port 80. -To enable the needed Apache Webserver simply type: +### Database -# a2enmod proxy -# a2enmod proxy_ajp -# a2enmod proxy_http +We provide a script that will set up all database essentials. This "Setup Tool" is located at <https://scm.thm.de/arsnova/setuptool>. Make sure you have configured your database credentials inside the ARSnova configuration file: you will need to have the entries `couchdb.username` and `couchdb.password`. -The configuration is ready for development usage. +## Credits -Finally you should (re)start all services. ARSnova2 is now listening on HTTP port 80. \ No newline at end of file +ARSnova is powered by Technische Hochschule Mittelhessen - University of Applied Sciences. \ No newline at end of file diff --git a/pom.xml b/pom.xml index b653c6c418112cafc414aaca90c52fd4c7224d9c..0e2110d6785daddb370a08d3884f7ba03b555165 100644 --- a/pom.xml +++ b/pom.xml @@ -19,12 +19,6 @@ <organization>Technische Hochschule Mittelhessen</organization> <organizationUrl>http://www.thm.de</organizationUrl> </developer> - <developer> - <name>Julian Hochstetter</name> - <organization>Technische Hochschule Mittelhessen</organization> - <organizationUrl>http://www.thm.de</organizationUrl> - <email>julian.hochstetter@mni.thm.de</email> - </developer> <developer> <name>Jan Kammer</name> <organization>Technische Hochschule Mittelhessen</organization> @@ -86,15 +80,19 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> - <version>2.4</version> - <configuration></configuration> + <version>2.7</version> + <configuration> + <dependencyLocationsEnabled>false</dependencyLocationsEnabled> + <dependencyDetailsEnabled>false</dependencyDetailsEnabled> + </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>2.9</version> + <version>2.9.1</version> <configuration></configuration> </plugin> + <!-- <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> @@ -104,11 +102,12 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> - <version>2.9.1</version> + <version>2.10</version> <configuration> <configLocation>ARSnova-checkstyle-checker.xml</configLocation> </configuration> </plugin> + --> </plugins> </reporting> @@ -136,18 +135,6 @@ </repositories> <dependencies> - <dependency> - <groupId>de.thm.arsnova</groupId> - <artifactId>arsnova-js</artifactId> - <version>0.0.1-SNAPSHOT</version> - <type>war</type> - </dependency> - <dependency> - <groupId>de.thm.arsnova</groupId> - <artifactId>arsnova-legacy-js</artifactId> - <version>1.0.0-SNAPSHOT</version> - <type>war</type> - </dependency> <dependency> <groupId>de.thm.arsnova</groupId> <artifactId>arsnova-light-js</artifactId> @@ -271,15 +258,10 @@ <artifactId>spring-security-oauth-client</artifactId> <version>1.0.0</version> </dependency> - <dependency> - <groupId>io.netty</groupId> - <artifactId>netty</artifactId> - <version>3.5.7.Final</version> - </dependency> <dependency> <groupId>com.corundumstudio.socketio</groupId> <artifactId>netty-socketio</artifactId> - <version>1.0.0-SNAPSHOT</version> + <version>1.0.1</version> </dependency> <dependency> <groupId>javax.inject</groupId> @@ -290,7 +272,7 @@ <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> - <version>2.1.0</version> + <version>2.2.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> @@ -412,7 +394,7 @@ <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> - <version>2.5.1</version> + <version>2.5.2</version> <configuration> <formats> <format>xml</format> @@ -428,7 +410,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> - <version>2.9.1</version> + <version>2.10</version> <configuration> <configLocation>ARSnova-checkstyle-checker.xml</configLocation> </configuration> diff --git a/src/main/java/de/thm/arsnova/aop/UserSessionAspect.java b/src/main/java/de/thm/arsnova/aop/UserSessionAspect.java index 1cc0eea7ada5747ccd0d0894e44a1bfa0edf25be..56f1158cd966511b8efb2b74a0b0cb3163ef079d 100644 --- a/src/main/java/de/thm/arsnova/aop/UserSessionAspect.java +++ b/src/main/java/de/thm/arsnova/aop/UserSessionAspect.java @@ -8,8 +8,6 @@ import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import de.thm.arsnova.entities.Session; -import de.thm.arsnova.events.Publisher; -import de.thm.arsnova.services.IUserService; import de.thm.arsnova.services.UserSessionService; @Aspect @@ -18,12 +16,6 @@ public class UserSessionAspect { @Autowired private UserSessionService userSessionService; - @Autowired - private IUserService userService; - - @Autowired - private Publisher publisher; - /** Sets current user and ARSnova session in session scoped UserSessionService * * @param jp @@ -35,7 +27,6 @@ public class UserSessionAspect { returning="session" ) public final void joinSessionAdvice(final JoinPoint jp, final String keyword, final Session session) { - userSessionService.setUser(userService.getCurrentUser()); userSessionService.setSession(session); } @@ -46,13 +37,14 @@ public class UserSessionAspect { * @param socketId * @param session */ + /* FIXME This is not working because of scoping problems @AfterReturning( pointcut="execution(public * de.thm.arsnova.services.SessionService.joinSession(..)) && args(keyword, socketId)", returning="session" ) public final void joinSessionAdviceWithWebsocket(final JoinPoint jp, final String keyword, final UUID socketId, final Session session) { - userSessionService.setUser(userService.getCurrentUser()); userSessionService.setSession(session); userSessionService.setSocketId(socketId); } + */ } diff --git a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java index 5fa519c1c8b76807a5d66d2fac8f025a5e3b93d1..fec00c97a4bb5dbe430e1b7a7b96179d81c1fdfd 100644 --- a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java +++ b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java @@ -146,6 +146,12 @@ public class LecturerQuestionController extends AbstractController { return questions; } + @RequestMapping(value = { "/" }, method = RequestMethod.DELETE) + @ResponseBody + public final void deleteSkillQuestions(@RequestParam final String sessionkey, final HttpServletResponse response) { + this.questionService.deleteAllQuestions(sessionkey); + } + @RequestMapping(value = "/count", method = RequestMethod.GET) @ResponseBody public final int getSkillQuestionCount(@RequestParam final String sessionkey, final HttpServletResponse response) { @@ -154,22 +160,6 @@ public class LecturerQuestionController extends AbstractController { return questionService.getSkillQuestionCount(sessionkey); } - /* - * TODO is this used anywhere? - @RequestMapping(value = "/ids", method = RequestMethod.GET) - @ResponseBody - public final List<String> getQuestionIds( - @RequestParam final String sessionkey, - final HttpServletResponse response - ) { - List<String> questions = questionService.getQuestionIds(sessionkey); - if (questions == null || questions.isEmpty()) { - throw new NotFoundException(); - } - return questions; - } - */ - @RequestMapping(value = "/{questionId}", method = RequestMethod.DELETE) @ResponseBody public final void deleteAnswersAndQuestion( diff --git a/src/main/java/de/thm/arsnova/controller/LoginController.java b/src/main/java/de/thm/arsnova/controller/LoginController.java index a15924681869865f11db1a7b251e72ce837020fb..2ef3ee7982aa2550710e1e1a56dc603fc668b18a 100644 --- a/src/main/java/de/thm/arsnova/controller/LoginController.java +++ b/src/main/java/de/thm/arsnova/controller/LoginController.java @@ -58,6 +58,7 @@ import org.springframework.web.servlet.view.RedirectView; 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.UserSessionService; @@ -97,9 +98,12 @@ public class LoginController extends AbstractController { @RequestParam(value = "referer", required = false) final String forcedReferer, @RequestParam(value = "successurl", required = false) final String successUrl, @RequestParam(value = "failureurl", required = false) final String failureUrl, + @RequestParam(value = "role", required = false) UserSessionService.Role role, final HttpServletRequest request, final HttpServletResponse response ) throws IOException, ServletException { + userSessionService.setRole(role); + String referer = request.getHeader("referer"); if (null != forcedReferer && null != referer && !UrlUtils.isAbsoluteUrl(referer)) { /* Use a url from a request parameter as referer as long as the url is not absolute (to prevent @@ -117,17 +121,19 @@ public class LoginController extends AbstractController { null == failureUrl ? referer : failureUrl ); + View result = null; + if ("cas".equals(type)) { casEntryPoint.commence(request, response, null); } else if ("twitter".equals(type)) { String authUrl = twitterProvider.getAuthorizationUrl(new HttpUserSession(request)); - return new RedirectView(authUrl); + result = new RedirectView(authUrl); } else if ("facebook".equals(type)) { String authUrl = facebookProvider.getAuthorizationUrl(new HttpUserSession(request)); - return new RedirectView(authUrl); + result = new RedirectView(authUrl); } else if ("google".equals(type)) { String authUrl = googleProvider.getAuthorizationUrl(new HttpUserSession(request)); - return new RedirectView(authUrl); + result = new RedirectView(authUrl); } else if ("guest".equals(type)) { List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); authorities.add(new SimpleGrantedAuthority("ROLE_GUEST")); @@ -146,9 +152,10 @@ public class LoginController extends AbstractController { SecurityContextHolder.getContext().setAuthentication(token); request.getSession(true).setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()); - return new RedirectView(null == successUrl ? referer + "#auth/checkLogin" : successUrl); + result = new RedirectView(null == successUrl ? referer + "#auth/checkLogin" : successUrl); } - return null; + + return result; } @RequestMapping(value = { "/auth/ldaplogin" }, method = RequestMethod.POST) @@ -195,6 +202,7 @@ public class LoginController extends AbstractController { @RequestMapping(value = { "/auth/", "/whoami" }, method = RequestMethod.GET) @ResponseBody public final User whoami() { + userSessionService.setUser(userService.getCurrentUser()); return userService.getCurrentUser(); } @@ -219,12 +227,30 @@ public class LoginController extends AbstractController { @RequestMapping(value = { "/test/me" }, method = RequestMethod.GET) @ResponseBody public final User me() { - return userSessionService.getUser(); + User me = userSessionService.getUser(); + if (me == null) { + throw new UnauthorizedException(); + } + return me; } @RequestMapping(value = { "/test/mysession" }, method = RequestMethod.GET) @ResponseBody public final Session mysession() { - return userSessionService.getSession(); + Session mysession = userSessionService.getSession(); + if (mysession == null) { + throw new UnauthorizedException(); + } + return mysession; + } + + @RequestMapping(value = { "/test/myrole" }, method = RequestMethod.GET) + @ResponseBody + public final UserSessionService.Role myrole() { + UserSessionService.Role myrole = userSessionService.getRole(); + if (myrole == null) { + throw new UnauthorizedException(); + } + return myrole; } } diff --git a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java index fa2345703d0b483d4c0e5b6d9833c8b6489f86fa..34aff2eff911121fc0c60b21b98807d34ea80c42 100644 --- a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java +++ b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java @@ -25,19 +25,13 @@ import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; import net.sf.ezmorph.Morpher; import net.sf.ezmorph.MorpherRegistry; import net.sf.ezmorph.bean.BeanMorpher; import net.sf.json.JSONArray; -import net.sf.json.JSONException; import net.sf.json.JSONObject; import net.sf.json.util.JSONUtils; @@ -56,7 +50,6 @@ import com.fourspaces.couchdb.ViewResults; import de.thm.arsnova.connector.model.Course; import de.thm.arsnova.entities.Answer; -import de.thm.arsnova.entities.Feedback; import de.thm.arsnova.entities.FoodVote; import de.thm.arsnova.entities.InterposedQuestion; import de.thm.arsnova.entities.InterposedReadingCount; @@ -114,59 +107,6 @@ public class CouchDBDao implements IDatabaseDao { this.userService = service; } - /** - * This method cleans up old feedback votes at the scheduled interval. - */ - @Override - public final void cleanFeedbackVotes(final int cleanupFeedbackDelay) { - final long timelimitInMillis = 60000 * (long) cleanupFeedbackDelay; - final long maxAllowedTimeInMillis = System.currentTimeMillis() - timelimitInMillis; - - Map<String, Set<String>> affectedUsers = new HashMap<String, Set<String>>(); - Set<String> allAffectedSessions = new HashSet<String>(); - - List<Document> results = findFeedbackForDeletion(maxAllowedTimeInMillis); - for (Document d : results) { - try { - // Read the required document data - Document feedback = this.getDatabase().getDocument(d.getId()); - String arsInternalSessionId = feedback.getString("sessionId"); - String user = feedback.getString("user"); - - // Store user and session data for later. We need this to - // communicate the changes back to the users. - Set<String> affectedArsSessions = affectedUsers.get(user); - if (affectedArsSessions == null) { - affectedArsSessions = new HashSet<String>(); - } - affectedArsSessions.add(getSessionKeyword(arsInternalSessionId)); - affectedUsers.put(user, affectedArsSessions); - allAffectedSessions.addAll(affectedArsSessions); - - this.database.deleteDocument(feedback); - LOGGER.debug("Cleaning up Feedback document " + d.getId()); - } 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} - ); - } - } - if (!results.isEmpty()) { - feedbackService.broadcastFeedbackChanges(affectedUsers, allAffectedSessions); - } - } - - private List<Document> findFeedbackForDeletion(final long maxAllowedTimeInMillis) { - View cleanupFeedbackView = new View("understanding/cleanup"); - cleanupFeedbackView.setStartKey("null"); - cleanupFeedbackView.setEndKey(String.valueOf(maxAllowedTimeInMillis)); - ViewResults feedbackForCleanup = this.getDatabase().view(cleanupFeedbackView); - return feedbackForCleanup.getResults(); - } - @Override public final Session getSession(final String keyword) { Session result = this.getSessionFromKeyword(keyword); @@ -182,28 +122,24 @@ public class CouchDBDao implements IDatabaseDao { @Override public final List<Session> getMySessions(final User user) { - try { - View view = new View("session/by_creator"); - view.setStartKey("[" + URLEncoder.encode("\"" + user.getUsername() + "\"", "UTF-8") + "]"); - view.setEndKey("[" + URLEncoder.encode("\"" + user.getUsername() + "\",{}", "UTF-8") + "]"); + NovaView view = new NovaView("session/by_creator"); + view.setStartKeyArray(user.getUsername()); + view.setEndKeyArray(user.getUsername(), "{}"); - ViewResults sessions = this.getDatabase().view(view); + ViewResults sessions = this.getDatabase().view(view); - List<Session> result = new ArrayList<Session>(); - for (Document d : sessions.getResults()) { - Session session = (Session) JSONObject.toBean( - d.getJSONObject().getJSONObject("value"), - Session.class - ); - session.setCreator(d.getJSONObject().getJSONArray("key").getString(0)); - session.setName(d.getJSONObject().getJSONArray("key").getString(1)); - session.set_id(d.getId()); - result.add(session); - } - return result; - } catch (UnsupportedEncodingException e) { - return null; + List<Session> result = new ArrayList<Session>(); + for (Document d : sessions.getResults()) { + Session session = (Session) JSONObject.toBean( + d.getJSONObject().getJSONObject("value"), + Session.class + ); + session.setCreator(d.getJSONObject().getJSONArray("key").getString(0)); + session.setName(d.getJSONObject().getJSONArray("key").getString(1)); + session.set_id(d.getId()); + result.add(session); } + return result; } @Override @@ -214,20 +150,22 @@ public class CouchDBDao implements IDatabaseDao { } User user = this.userService.getCurrentUser(); - View view = null; + NovaView view = null; try { + String viewName; if (session.getCreator().equals(user.getUsername())) { - view = new View("skill_question/by_session_sorted_by_subject_and_text"); + viewName = "skill_question/by_session_sorted_by_subject_and_text"; } else { if (user.getType().equals(User.THM)) { - view = new View("skill_question/by_session_for_thm_full"); + viewName = "skill_question/by_session_for_thm_full"; } else { - view = new View("skill_question/by_session_for_all_full"); + viewName = "skill_question/by_session_for_all_full"; } } - view.setStartKey("[" + URLEncoder.encode("\"" + session.get_id() + "\"", "UTF-8") + "]"); - view.setEndKey("[" + URLEncoder.encode("\"" + session.get_id() + "\",{}", "UTF-8") + "]"); + view = new NovaView(viewName); + view.setStartKeyArray(session.get_id()); + view.setEndKeyArray(session.get_id(), "{}"); ViewResults questions = this.getDatabase().view(view); if (questions == null || questions.isEmpty()) { @@ -258,8 +196,6 @@ public class CouchDBDao implements IDatabaseDao { } return result; - } catch (UnsupportedEncodingException e) { - return null; } catch (IOException e) { return null; } @@ -267,63 +203,49 @@ public class CouchDBDao implements IDatabaseDao { @Override public final int getSkillQuestionCount(final Session session) { - try { - View view = new View("skill_question/count_by_session"); - view.setKey(URLEncoder.encode("\"" + session.get_id() + "\"", "UTF-8")); - ViewResults results = this.getDatabase().view(view); - - if (results.getJSONArray("rows").optJSONObject(0) == null) { - return 0; - } - - return results.getJSONArray("rows").optJSONObject(0).optInt("value"); + NovaView view = new NovaView("skill_question/count_by_session"); + view.setKey(session.get_id()); + ViewResults results = this.getDatabase().view(view); - } catch (UnsupportedEncodingException e) { + if (results.getJSONArray("rows").optJSONObject(0) == null) { return 0; } + + return results.getJSONArray("rows").optJSONObject(0).optInt("value"); } @Override public final Session getSessionFromKeyword(final String keyword) { - try { - View view = new View("session/by_keyword"); - view.setKey(URLEncoder.encode("\"" + keyword + "\"", "UTF-8")); - ViewResults results = this.getDatabase().view(view); + NovaView view = new NovaView("session/by_keyword"); + view.setKey(keyword); + ViewResults results = this.getDatabase().view(view); - if (results.getJSONArray("rows").optJSONObject(0) == null) { - return null; - } - return (Session) JSONObject.toBean( - results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), - Session.class - ); - } catch (UnsupportedEncodingException e) { + if (results.getJSONArray("rows").optJSONObject(0) == null) { return null; } + return (Session) JSONObject.toBean( + results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), + Session.class + ); } @Override public final Session getSessionFromId(final String sessionId) { - try { - View view = new View("session/by_id"); - view.setKey(URLEncoder.encode("\"" + sessionId + "\"", "UTF-8")); - ViewResults results = this.getDatabase().view(view); + View view = new View("session/by_id"); + view.setKey(sessionId); + ViewResults results = this.getDatabase().view(view); - if (results.getJSONArray("rows").optJSONObject(0) == null) { - return null; - } - return (Session) JSONObject.toBean( - results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), - Session.class - ); - } catch (UnsupportedEncodingException e) { + if (results.getJSONArray("rows").optJSONObject(0) == null) { return null; } + return (Session) JSONObject.toBean( + results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), + Session.class + ); } @Override public final Session saveSession(final Session session) { - Document sessionDocument = new Document(); sessionDocument.put("type", "session"); sessionDocument.put("name", session.getName()); @@ -338,160 +260,9 @@ public class CouchDBDao implements IDatabaseDao { } catch (IOException e) { return null; } - return this.getSession(sessionDocument.getString("keyword")); } - @Override - public final Feedback getFeedback(final String keyword) { - String sessionId = this.getSessionId(keyword); - if (sessionId == null) { - throw new NotFoundException(); - } - View view = new View("understanding/by_session"); - view.setGroup(true); - view.setStartKey(URLEncoder.encode("[\"" + sessionId + "\"]")); - view.setEndKey(URLEncoder.encode("[\"" + sessionId + "\",{}]")); - ViewResults results = this.getDatabase().view(view); - - LOGGER.debug("Feedback: {}", results.getJSONArray("rows")); - - return this.createFeedbackObject(results); - } - - private Feedback createFeedbackObject(final ViewResults results) { - int[] values = {0, 0, 0, 0}; - JSONArray rows = results.getJSONArray("rows"); - - try { - for (int i = Feedback.MIN_FEEDBACK_TYPE; i <= Feedback.MAX_FEEDBACK_TYPE; i++) { - String key = rows.optJSONObject(i).optJSONArray("key").getString(1); - JSONObject feedback = rows.optJSONObject(i); - - if (key.equals("Bitte schneller")) { - values[Feedback.FEEDBACK_FASTER] = feedback.getInt("value"); - } - if (key.equals("Kann folgen")) { - values[Feedback.FEEDBACK_OK] = feedback.getInt("value"); - } - if (key.equals("Zu schnell")) { - values[Feedback.FEEDBACK_SLOWER] = feedback.getInt("value"); - } - if (key.equals("Nicht mehr dabei")) { - values[Feedback.FEEDBACK_AWAY] = feedback.getInt("value"); - } - } - } catch (Exception e) { - return new Feedback( - values[Feedback.FEEDBACK_FASTER], - values[Feedback.FEEDBACK_OK], - values[Feedback.FEEDBACK_SLOWER], - values[Feedback.FEEDBACK_AWAY] - ); - } - return new Feedback( - values[Feedback.FEEDBACK_FASTER], - values[Feedback.FEEDBACK_OK], - values[Feedback.FEEDBACK_SLOWER], - values[Feedback.FEEDBACK_AWAY] - ); - } - - @Override - 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; - } - if (!(value >= Feedback.MIN_FEEDBACK_TYPE && value <= Feedback.MAX_FEEDBACK_TYPE)) { - return false; - } - - Document feedback = new Document(); - List<Document> postedFeedback = findPreviousFeedback(sessionId, user); - - // Feedback can only be posted once. If there already is some feedback, - // we need to update it. - if (!postedFeedback.isEmpty()) { - for (Document f : postedFeedback) { - // Use the first found feedback and update value and timestamp - try { - feedback = this.getDatabase().getDocument(f.getId()); - feedback.put("value", feedbackValueToString(value)); - feedback.put("timestamp", System.currentTimeMillis()); - } catch (IOException e) { - return false; - } - break; - } - } else { - feedback.put("type", "understanding"); - feedback.put("user", user.getUsername()); - feedback.put("sessionId", sessionId); - feedback.put("timestamp", System.currentTimeMillis()); - feedback.put("value", feedbackValueToString(value)); - } - - try { - this.getDatabase().saveDocument(feedback); - } catch (IOException e) { - return false; - } - - return true; - } - - 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" - ) - ); - } catch (UnsupportedEncodingException e) { - return Collections.<Document> emptyList(); - } - ViewResults results = this.getDatabase().view(view); - return results.getResults(); - } - - private String feedbackValueToString(final int value) { - switch (value) { - case Feedback.FEEDBACK_FASTER: - return "Bitte schneller"; - case Feedback.FEEDBACK_OK: - return "Kann folgen"; - case Feedback.FEEDBACK_SLOWER: - return "Zu schnell"; - case Feedback.FEEDBACK_AWAY: - return "Nicht mehr dabei"; - default: - return null; - } - } - - private int feedbackValueFromString(final String value) { - if (value.equals("Bitte schneller")) { - return Feedback.FEEDBACK_FASTER; - } - if (value.equals("Kann folgen")) { - return Feedback.FEEDBACK_OK; - } - if (value.equals("Zu schnell")) { - return Feedback.FEEDBACK_AWAY; - } - if (value.equals("Nicht mehr dabei")) { - return Feedback.FEEDBACK_AWAY; - } - return Integer.MIN_VALUE; - } - @Override @Transactional(isolation = Isolation.READ_COMMITTED) public final boolean sessionKeyAvailable(final String keyword) { @@ -501,16 +272,6 @@ public class CouchDBDao implements IDatabaseDao { return !results.containsKey(keyword); } - private String getSessionId(final String keyword) { - View view = new View("session/by_keyword"); - view.setKey(URLEncoder.encode("\"" + keyword + "\"")); - ViewResults results = this.getDatabase().view(view); - if (results.getJSONArray("rows").optJSONObject(0) == null) { - return null; - } - return results.getJSONArray("rows").optJSONObject(0).optJSONObject("value").getString("_id"); - } - private String getSessionKeyword(final String internalSessionId) throws IOException { Document document = this.getDatabase().getDocument(internalSessionId); if (document.has("keyword")) { @@ -626,8 +387,8 @@ public class CouchDBDao implements IDatabaseDao { @Override public final Question getQuestion(final String id) { try { - View view = new View("skill_question/by_id"); - view.setKey(URLEncoder.encode("\"" + id + "\"", "UTF-8")); + NovaView view = new NovaView("skill_question/by_id"); + view.setKey(id); ViewResults results = this.getDatabase().view(view); if (results.getJSONArray("rows").optJSONObject(0) == null) { @@ -656,8 +417,8 @@ public class CouchDBDao implements IDatabaseDao { @Override public final LoggedIn registerAsOnlineUser(final User user, final Session session) { try { - View view = new View("logged_in/all"); - view.setKey(URLEncoder.encode("\"" + user.getUsername() + "\"", "UTF-8")); + NovaView view = new NovaView("logged_in/all"); + view.setKey(user.getUsername()); ViewResults results = this.getDatabase().view(view); LoggedIn loggedIn = new LoggedIn(); @@ -696,8 +457,6 @@ public class CouchDBDao implements IDatabaseDao { l.setVisitedSessions(new ArrayList<VisitedSession>(visitedSessions)); } return l; - } catch (UnsupportedEncodingException e) { - return null; } catch (IOException e) { return null; } @@ -715,61 +474,26 @@ public class CouchDBDao implements IDatabaseDao { } } - @Override - public final Integer getMyFeedback(final String keyword, final User user) { - try { - String sessionId = this.getSessionId(keyword); - if (sessionId == null) { - throw new NotFoundException(); - } - - View view = new View("understanding/by_user"); - view.setKey( - URLEncoder.encode( - "[\"" + sessionId + "\", \"" + user.getUsername() + "\"]", - "UTF-8" - ) - ); - ViewResults results = this.getDatabase().view(view); - JSONArray rows = results.getJSONArray("rows"); - - if (rows.size() == 0) { - return null; - } - - JSONObject json = rows.optJSONObject(0).optJSONObject("value"); - return this.feedbackValueFromString(json.getString("value")); - } catch (UnsupportedEncodingException e) { - return null; - } - } - @Override public final List<String> getQuestionIds(final Session session, final User user) { - View view; + NovaView view; if (user.getType().equals("thm")) { - view = new View("skill_question/by_session_only_id_for_thm"); + view = new NovaView("skill_question/by_session_only_id_for_thm"); } else { - view = new View("skill_question/by_session_only_id_for_all"); + view = new NovaView("skill_question/by_session_only_id_for_all"); } - try { - view.setKey(URLEncoder.encode("\"" + session.get_id() + "\"", "UTF-8")); - ViewResults results = this.getDatabase().view(view); - if (results.getResults().size() == 0) { - return new ArrayList<String>(); - } - - List<String> ids = new ArrayList<String>(); - for (Document d : results.getResults()) { - ids.add(d.getId()); - } - return ids; + view.setKey(session.get_id()); + ViewResults results = this.getDatabase().view(view); + if (results.getResults().size() == 0) { + return new ArrayList<String>(); + } - } catch (IOException e) { - LOGGER.error("Could not get list of question ids of session {}", session.getKeyword()); + List<String> ids = new ArrayList<String>(); + for (Document d : results.getResults()) { + ids.add(d.getId()); } - return new ArrayList<String>(); + return ids; } @Override @@ -781,6 +505,20 @@ public class CouchDBDao implements IDatabaseDao { LOGGER.error("IOException: Could not delete question {}", question.get_id()); } } + + @Override + public final void deleteAllQuestionsWithAnswers(Session session) { + NovaView view = new NovaView("skill_question/by_session"); + view.setStartKeyArray(session.get_id()); + view.setEndKey(session.get_id(), "{}"); + ViewResults results = this.getDatabase().view(view); + + for (Document d : results.getResults()) { + Question q = new Question(); + q.set_id(d.getId()); + this.deleteQuestionWithAnswers(q); + } + } private void deleteDocument(final String documentId) throws IOException { Document d = this.getDatabase().getDocument(documentId); @@ -790,8 +528,8 @@ public class CouchDBDao implements IDatabaseDao { @Override public final void deleteAnswers(final Question question) { try { - View view = new View("answer/cleanup"); - view.setKey(URLEncoder.encode("\"" + question.get_id() + "\"", "UTF-8")); + NovaView view = new NovaView("answer/cleanup"); + view.setKey(question.get_id()); ViewResults results = this.getDatabase().view(view); for (Document d : results.getResults()) { @@ -804,35 +542,23 @@ public class CouchDBDao implements IDatabaseDao { @Override public final List<String> getUnAnsweredQuestionIds(final Session session, final User user) { - try { - View view = new View("answer/by_user"); - view.setKey( - "[" + URLEncoder.encode( - "\"" + user.getUsername() + "\",\"" + session.get_id() + "\"", - "UTF-8" - ) - + "]" - ); - ViewResults anseweredQuestions = this.getDatabase().view(view); + NovaView view = new NovaView("answer/by_user"); + view.setKey(user.getUsername(), session.get_id()); + ViewResults anseweredQuestions = this.getDatabase().view(view); - List<String> answered = new ArrayList<String>(); - for (Document d : anseweredQuestions.getResults()) { - answered.add(d.getString("value")); - } + List<String> answered = new ArrayList<String>(); + for (Document d : anseweredQuestions.getResults()) { + answered.add(d.getString("value")); + } - List<String> questions = this.getQuestionIds(session, user); - List<String> unanswered = new ArrayList<String>(); - for (String questionId : questions) { - if (!answered.contains(questionId)) { - unanswered.add(questionId); - } + List<String> questions = this.getQuestionIds(session, user); + List<String> unanswered = new ArrayList<String>(); + for (String questionId : questions) { + if (!answered.contains(questionId)) { + unanswered.add(questionId); } - return unanswered; - } catch (UnsupportedEncodingException e) { - LOGGER.error("Error while retrieving unansweredquestions", e); } - - return null; + return unanswered; } @Override @@ -842,108 +568,73 @@ public class CouchDBDao implements IDatabaseDao { throw new UnauthorizedException(); } - try { - View view = new View("answer/by_question_and_user_and_piround"); - if (2 == piRound) { - view.setKey( - "[" + URLEncoder.encode( - "\"" + questionId + "\",\"" + user.getUsername() + "\",2", - "UTF-8" - ) - + "]" - ); - } else { - /* needed for legacy questions whose piRound property has not been set */ - view.setStartKey( - "[" + URLEncoder.encode( - "\"" + questionId + "\",\"" + user.getUsername() + "\"", - "UTF-8" - ) - + "]" - ); - view.setEndKey( - "[" + URLEncoder.encode( - "\"" + questionId + "\",\"" + user.getUsername() + "\",1", - "UTF-8" - ) - + "]" - ); - } - ViewResults results = this.getDatabase().view(view); - if (results.getResults().isEmpty()) { - return null; - } - 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 {}, {}", - new Object[] {user, questionId, e } - ); + NovaView view = new NovaView("answer/by_question_and_user_and_piround"); + if (2 == piRound) { + view.setKey(questionId, user.getUsername(), "2"); + } else { + /* needed for legacy questions whose piRound property has not been set */ + view.setStartKey(questionId, user.getUsername()); + view.setEndKey(questionId, user.getUsername(), "1"); } - - return null; + ViewResults results = this.getDatabase().view(view); + if (results.getResults().isEmpty()) { + return null; + } + return (Answer) JSONObject.toBean( + results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"), + Answer.class + ); } @Override public final List<Answer> getAnswers(final String questionId, int piRound) { - try { - View view = new View("skill_question/count_answers_by_question_and_piround"); - if (2 == piRound) { - view.setStartKey("[" + URLEncoder.encode( - "\"" + questionId + "\",2", - "UTF-8" - ) + "]"); - view.setEndKey("[" + URLEncoder.encode( - "\"" + questionId + "\",2,{}", - "UTF-8" - ) + "]"); - } else { - /* needed for legacy questions whose piRound property has not been set */ - view.setStartKey("[" + URLEncoder.encode( - "\"" + questionId + "\"", - "UTF-8" - ) + "]"); - view.setEndKey("[" + URLEncoder.encode( - "\"" + questionId + "\",1,{}", - "UTF-8" - ) + "]"); - } - view.setGroup(true); - ViewResults results = this.getDatabase().view(view); - List<Answer> answers = new ArrayList<Answer>(); - for (Document d : results.getResults()) { - Answer a = new Answer(); - a.setAnswerCount(d.getInt("value")); - a.setQuestionId(d.getJSONObject().getJSONArray("key").getString(0)); - a.setPiRound(piRound); - a.setAnswerText(d.getJSONObject().getJSONArray("key").getString(2)); - answers.add(a); - } - return answers; - } catch (UnsupportedEncodingException e) { - LOGGER.error("Error while retrieving answers", e); + NovaView view = new NovaView("skill_question/count_answers_by_question_and_piround"); + if (2 == piRound) { + view.setStartKey(questionId, "2"); + view.setEndKey(questionId, "2", "{}"); + } else { + /* needed for legacy questions whose piRound property has not been set */ + view.setStartKeyArray(questionId); + view.setEndKeyArray(questionId, "1", "{}"); } - return null; + view.setGroup(true); + ViewResults results = this.getDatabase().view(view); + int abstentionCount = this.getAbstentionAnswerCount(questionId); + List<Answer> answers = new ArrayList<Answer>(); + for (Document d : results.getResults()) { + Answer a = new Answer(); + a.setAnswerCount(d.getInt("value")); + a.setAbstentionCount(abstentionCount); + a.setQuestionId(d.getJSONObject().getJSONArray("key").getString(0)); + a.setPiRound(piRound); + String answerText = d.getJSONObject().getJSONArray("key").getString(2); + a.setAnswerText(answerText == "null" ? null : answerText); + answers.add(a); + } + return answers; + } + + private int getAbstentionAnswerCount(final String questionId) { + NovaView view = new NovaView("skill_question/count_abstention_answers_by_question"); + view.setKey(questionId); + view.setGroup(true); + ViewResults results = this.getDatabase().view(view); + if (results.getResults().size() == 0) { + return 0; + } + return results.getJSONArray("rows").optJSONObject(0).optInt("value"); } @Override public final int getAnswerCount(final String questionId) { - try { - View view = new View("skill_question/count_answers_by_question"); - view.setKey(URLEncoder.encode("\"" + questionId + "\"", "UTF-8")); - view.setGroup(true); - ViewResults results = this.getDatabase().view(view); - if (results.getResults().size() == 0) { - return 0; - } - return results.getJSONArray("rows").optJSONObject(0).optInt("value"); - } catch (UnsupportedEncodingException e) { - LOGGER.error("Error while retrieving answer count", e); + NovaView view = new NovaView("skill_question/count_answers_by_question"); + view.setKey(questionId); + view.setGroup(true); + ViewResults results = this.getDatabase().view(view); + if (results.getResults().size() == 0) { + return 0; } - return 0; + return results.getJSONArray("rows").optJSONObject(0).optInt("value"); } @Override @@ -965,20 +656,14 @@ public class CouchDBDao implements IDatabaseDao { @Override public final int countActiveUsers(Session session, long since) { if (session == null) throw new NotFoundException(); - try { - View view = new View("logged_in/count"); - view.setStartKey( - URLEncoder.encode("[\"" + session.get_id() + "\", " + String.valueOf(since) + "]", "UTF-8") - ); - view.setEndKey(URLEncoder.encode("[\"" + session.get_id() + "\", {}]", "UTF-8")); - ViewResults results = this.getDatabase().view(view); - if (isEmptyResults(results)) { - return 0; - } - return results.getJSONArray("rows").optJSONObject(0).getInt("value"); - } catch (UnsupportedEncodingException e) { + NovaView view = new NovaView("logged_in/count"); + view.setStartKey(session.get_id(), String.valueOf(since)); + view.setEndKey(session.get_id(), "{}"); + ViewResults results = this.getDatabase().view(view); + if (isEmptyResults(results)) { return 0; } + return results.getJSONArray("rows").optJSONObject(0).getInt("value"); } private boolean isEmptyResults(ViewResults results) { @@ -987,24 +672,19 @@ public class CouchDBDao implements IDatabaseDao { @Override public List<Answer> getFreetextAnswers(String questionId) { - try { - 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()) { - 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.setQuestionId(questionId); - answers.add(a); - } + List<Answer> answers = new ArrayList<Answer>(); + NovaView view = new NovaView("skill_question/freetext_answers_full"); + view.setKey(questionId); + ViewResults results = this.getDatabase().view(view); + if (results.getResults().isEmpty()) { return answers; - } catch (UnsupportedEncodingException e) { - LOGGER.error("Error while retrieving freetext answers", e); } - return null; + for (Document d : results.getResults()) { + Answer a = (Answer) JSONObject.toBean(d.getJSONObject().getJSONObject("value"), Answer.class); + a.setQuestionId(questionId); + answers.add(a); + } + return answers; } @Override @@ -1019,29 +699,22 @@ public class CouchDBDao implements IDatabaseDao { throw new UnauthorizedException(); } - try { - View view = new View("answer/by_user_and_session_full"); - view.setKey( - "[" + URLEncoder.encode("\"" + user.getUsername() + "\",\"" + s.get_id() + "\"", "UTF-8") + "]" - ); - ViewResults results = this.getDatabase().view(view); - List<Answer> answers = new ArrayList<Answer>(); - if (results == null || results.getResults() == null || results.getResults().isEmpty()) { - return answers; - } - for (Document d : results.getResults()) { - Answer a = (Answer) JSONObject.toBean(d.getJSONObject().getJSONObject("value"), Answer.class); - a.set_id(d.getId()); - a.set_rev(d.getRev()); - a.setUser(user.getUsername()); - a.setSessionId(s.get_id()); - answers.add(a); - } + NovaView view = new NovaView("answer/by_user_and_session_full"); + view.setKey(user.getUsername(), s.get_id()); + ViewResults results = this.getDatabase().view(view); + List<Answer> answers = new ArrayList<Answer>(); + if (results == null || results.getResults() == null || results.getResults().isEmpty()) { return answers; - } catch (UnsupportedEncodingException e) { - LOGGER.error("Error while retrieving user answers", e); } - return null; + for (Document d : results.getResults()) { + Answer a = (Answer) JSONObject.toBean(d.getJSONObject().getJSONObject("value"), Answer.class); + a.set_id(d.getId()); + a.set_rev(d.getRev()); + a.setUser(user.getUsername()); + a.setSessionId(s.get_id()); + answers.add(a); + } + return answers; } @Override @@ -1051,18 +724,13 @@ public class CouchDBDao implements IDatabaseDao { throw new NotFoundException(); } - try { - View view = new View("skill_question/count_answers_by_session"); - view.setKey(URLEncoder.encode("\"" + s.get_id() + "\"", "UTF-8")); - ViewResults results = this.getDatabase().view(view); - if (results.getResults().size() == 0) { - return 0; - } - return results.getJSONArray("rows").optJSONObject(0).optInt("value"); - } catch (UnsupportedEncodingException e) { - LOGGER.error("Error while retrieving total answer count", e); + NovaView view = new NovaView("skill_question/count_answers_by_session"); + view.setKey(s.get_id()); + ViewResults results = this.getDatabase().view(view); + if (results.getResults().size() == 0) { + return 0; } - return 0; + return results.getJSONArray("rows").optJSONObject(0).optInt("value"); } @Override @@ -1072,42 +740,32 @@ public class CouchDBDao implements IDatabaseDao { throw new NotFoundException(); } - try { - View view = new View("interposed_question/count_by_session"); - view.setKey(URLEncoder.encode("\"" + s.get_id() + "\"", "UTF-8")); - view.setGroup(true); - ViewResults results = this.getDatabase().view(view); - if (results.size() == 0 || results.getResults().size() == 0) { - return 0; - } - return results.getJSONArray("rows").optJSONObject(0).optInt("value"); - } catch (UnsupportedEncodingException e) { - LOGGER.error("Error while retrieving interposed question count", e); + NovaView view = new NovaView("interposed_question/count_by_session"); + view.setKey(s.get_id()); + view.setGroup(true); + ViewResults results = this.getDatabase().view(view); + if (results.size() == 0 || results.getResults().size() == 0) { + return 0; } - return 0; + return results.getJSONArray("rows").optJSONObject(0).optInt("value"); } @Override public InterposedReadingCount getInterposedReadingCount(Session session) { - try { - View view = new View("interposed_question/count_by_session_reading"); - view.setStartKey(URLEncoder.encode("[\"" + session.get_id() + "\"]", "UTF-8")); - view.setEndKey(URLEncoder.encode("[\"" + session.get_id() + "\", {}]", "UTF-8")); - view.setGroup(true); - ViewResults results = this.getDatabase().view(view); - if (results.size() == 0 || results.getResults().size() == 0) { - return new InterposedReadingCount(); - } - int read = results.getJSONArray("rows").optJSONObject(0).optInt("value"); - int unread = 0; - if (results.getJSONArray("rows").optJSONObject(1) != null) { - unread = results.getJSONArray("rows").optJSONObject(1).optInt("value"); - } - return new InterposedReadingCount(read, unread); - } catch (UnsupportedEncodingException e) { - LOGGER.error("Error while retrieving interposed question count", e); + NovaView view = new NovaView("interposed_question/count_by_session_reading"); + view.setStartKeyArray(session.get_id()); + view.setEndKeyArray(session.get_id(), "{}"); + view.setGroup(true); + ViewResults results = this.getDatabase().view(view); + if (results.size() == 0 || results.getResults().size() == 0) { + return new InterposedReadingCount(); } - return new InterposedReadingCount(); + int read = results.getJSONArray("rows").optJSONObject(0).optInt("value"); + int unread = 0; + if (results.getJSONArray("rows").optJSONObject(1) != null) { + unread = results.getJSONArray("rows").optJSONObject(1).optInt("value"); + } + return new InterposedReadingCount(read, unread); } @Override @@ -1117,29 +775,24 @@ public class CouchDBDao implements IDatabaseDao { throw new NotFoundException(); } - try { - View view = new View("interposed_question/by_session"); - view.setKey(URLEncoder.encode("\"" + s.get_id() + "\"", "UTF-8")); - ViewResults questions = this.getDatabase().view(view); - if (questions == null || questions.isEmpty()) { - return null; - } - List<InterposedQuestion> result = new ArrayList<InterposedQuestion>(); - LOGGER.debug("{}", questions.getResults()); - for (Document document : questions.getResults()) { - InterposedQuestion question = (InterposedQuestion) JSONObject.toBean( - document.getJSONObject().getJSONObject("value"), - InterposedQuestion.class - ); - question.setSessionId(sessionKey); - question.set_id(document.getId()); - result.add(question); - } - return result; - } catch (UnsupportedEncodingException e) { - LOGGER.error("Error while retrieving interposed questions", e); + NovaView view = new NovaView("interposed_question/by_session"); + view.setKey(s.get_id()); + ViewResults questions = this.getDatabase().view(view); + if (questions == null || questions.isEmpty()) { + return null; } - return null; + List<InterposedQuestion> result = new ArrayList<InterposedQuestion>(); + LOGGER.debug("{}", questions.getResults()); + for (Document document : questions.getResults()) { + InterposedQuestion question = (InterposedQuestion) JSONObject.toBean( + document.getJSONObject().getJSONObject("value"), + InterposedQuestion.class + ); + question.setSessionId(sessionKey); + question.set_id(document.getId()); + result.add(question); + } + return result; } public Question getInterposedQuestion(String sessionKey, String documentId) { @@ -1167,8 +820,8 @@ public class CouchDBDao implements IDatabaseDao { String date = new SimpleDateFormat("dd-mm-yyyyy").format(new Date()); try { - View view = new View("food_vote/get_user_vote"); - view.setKey("[" + URLEncoder.encode("\"" + date + "\",\"" + u.getUsername() + "\"", "UTF-8") + "]"); + NovaView view = new NovaView("food_vote/get_user_vote"); + view.setKey(date, u.getUsername()); ViewResults results = this.getDatabase().view(view); if (results.getResults().isEmpty()) { @@ -1183,8 +836,6 @@ public class CouchDBDao implements IDatabaseDao { vote.put("name", menu); this.database.saveDocument(vote); } - } catch (UnsupportedEncodingException e) { - LOGGER.error("Error while retrieving user food vote", e); } catch (IOException e) { LOGGER.error("Error while saving user food vote", e); } @@ -1194,23 +845,17 @@ public class CouchDBDao implements IDatabaseDao { public List<FoodVote> getFoodVote() { List<FoodVote> foodVotes = new ArrayList<FoodVote>(); String date = new SimpleDateFormat("dd-mm-yyyyy").format(new Date()); - try { - View view = new View("food_vote/count_by_day"); - view.setStartKey("[" + URLEncoder.encode("\"" + date + "\"", "UTF-8") + "]"); - view.setEndKey("[" + URLEncoder.encode("\"" + date + "\",{}", "UTF-8") + "]"); - view.setGroup(true); - ViewResults results = this.getDatabase().view(view); - for (Document d : results.getResults()) { - FoodVote vote = new FoodVote(); - vote.setCount(d.getJSONObject().optInt("value")); - vote.setDay(date); - vote.setName(d.getJSONObject().getJSONArray("key").getString(1)); - foodVotes.add(vote); - } - - return foodVotes; - } catch (UnsupportedEncodingException e) { - LOGGER.error("Error while retrieving food vote count", e); + NovaView view = new NovaView("food_vote/count_by_day"); + view.setStartKeyArray(date); + view.setEndKeyArray(date, "{}"); + view.setGroup(true); + ViewResults results = this.getDatabase().view(view); + for (Document d : results.getResults()) { + FoodVote vote = new FoodVote(); + vote.setCount(d.getJSONObject().optInt("value")); + vote.setDay(date); + vote.setName(d.getJSONObject().getJSONArray("key").getString(1)); + foodVotes.add(vote); } return foodVotes; } @@ -1218,20 +863,15 @@ public class CouchDBDao implements IDatabaseDao { @Override public int getFoodVoteCount() { String date = new SimpleDateFormat("dd-mm-yyyyy").format(new Date()); - try { - View view = new View("food_vote/count_by_day"); - view.setStartKey("[" + URLEncoder.encode("\"" + date + "\"", "UTF-8") + "]"); - view.setEndKey("[" + URLEncoder.encode("\"" + date + "\",{}", "UTF-8") + "]"); - view.setGroup(false); - ViewResults results = this.getDatabase().view(view); - if (results.size() == 0 || results.getResults().size() == 0) { - return 0; - } - return results.getJSONArray("rows").optJSONObject(0).optInt("value"); - } catch (UnsupportedEncodingException e) { - LOGGER.error("Error while retrieving food vote count", e); + NovaView view = new NovaView("food_vote/count_by_day"); + view.setStartKeyArray(date); + view.setEndKeyArray(date, "{}"); + view.setGroup(false); + ViewResults results = this.getDatabase().view(view); + if (results.size() == 0 || results.getResults().size() == 0) { + return 0; } - return 0; + return results.getJSONArray("rows").optJSONObject(0).optInt("value"); } @Override @@ -1316,34 +956,30 @@ public class CouchDBDao implements IDatabaseDao { @Override public List<Session> getMyVisitedSessions(User user) { - try { - View view = new View("logged_in/visited_sessions_by_user"); - view.setKey(URLEncoder.encode("\"" + user.getUsername() + "\"", "UTF-8")); - ViewResults sessions = this.getDatabase().view(view); - List<Session> allSessions = new ArrayList<Session>(); - for (Document d : sessions.getResults()) { - // Not all users have visited sessions - if (d.getJSONObject().optJSONArray("value") != null) { - @SuppressWarnings("unchecked") - Collection<Session> visitedSessions = JSONArray.toCollection( - d.getJSONObject().getJSONArray("value"), - Session.class - ); - allSessions.addAll(visitedSessions); - } + NovaView view = new NovaView("logged_in/visited_sessions_by_user"); + view.setKey(user.getUsername()); + ViewResults sessions = this.getDatabase().view(view); + List<Session> allSessions = new ArrayList<Session>(); + for (Document d : sessions.getResults()) { + // Not all users have visited sessions + if (d.getJSONObject().optJSONArray("value") != null) { + @SuppressWarnings("unchecked") + Collection<Session> visitedSessions = JSONArray.toCollection( + d.getJSONObject().getJSONArray("value"), + Session.class + ); + allSessions.addAll(visitedSessions); } - // Do these sessions still exist? - List<Session> result = new ArrayList<Session>(); - for (Session s : allSessions) { - Session session = this.getSessionFromKeyword(s.getKeyword()); - if (session != null) { - result.add(session); - } + } + // Do these sessions still exist? + List<Session> result = new ArrayList<Session>(); + for (Session s : allSessions) { + Session session = this.getSessionFromKeyword(s.getKeyword()); + if (session != null) { + result.add(session); } - return result; - } catch (UnsupportedEncodingException e) { - return null; } + return result; } @Override @@ -1424,24 +1060,17 @@ public class CouchDBDao implements IDatabaseDao { @Override public final List<String> getActiveUsers(int timeDifference) { - try { - long inactiveBeforeTimestamp = new Date().getTime() - timeDifference * 1000; - - View view = new View("logged_in/by_and_only_timestamp_and_username"); - view.setStartKey("[" + URLEncoder.encode(String.valueOf(inactiveBeforeTimestamp), "UTF-8") + "]"); - ViewResults results = this.getDatabase().view(view); - LOGGER.debug("getActiveUsers result count: {}", String.valueOf(results.size())); + long inactiveBeforeTimestamp = new Date().getTime() - timeDifference * 1000; - List<String> result = new ArrayList<String>(); - for (Document d : results.getResults()) { - result.add(d.getJSONObject().getJSONArray("key").getString(1)); - } + NovaView view = new NovaView("logged_in/by_and_only_timestamp_and_username"); + view.setStartKeyArray(String.valueOf(inactiveBeforeTimestamp)); + ViewResults results = this.getDatabase().view(view); - return result; - } catch (UnsupportedEncodingException e) { - LOGGER.error("Error while retrieving active users", e); + List<String> result = new ArrayList<String>(); + for (Document d : results.getResults()) { + result.add(d.getJSONObject().getJSONArray("key").getString(1)); } - return null; + return result; } private class ExtendedView extends View { diff --git a/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java b/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java index 4d9e600e5b0d9b3ce72fb586cf07b109bc9efebb..d6027c30c7a90916fbaf61aa17495890389405c4 100644 --- a/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java +++ b/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java @@ -23,7 +23,6 @@ import java.util.List; import de.thm.arsnova.connector.model.Course; import de.thm.arsnova.entities.Answer; -import de.thm.arsnova.entities.Feedback; import de.thm.arsnova.entities.FoodVote; import de.thm.arsnova.entities.InterposedQuestion; import de.thm.arsnova.entities.InterposedReadingCount; @@ -33,8 +32,6 @@ import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; public interface IDatabaseDao { - void cleanFeedbackVotes(int cleanupFeedbackDelay); - Session getSessionFromKeyword(String keyword); Session getSession(String keyword); @@ -43,10 +40,6 @@ public interface IDatabaseDao { Session saveSession(Session session); - Feedback getFeedback(String keyword); - - boolean saveFeedback(String keyword, int value, User user); - boolean sessionKeyAvailable(String keyword); Question saveQuestion(Session session, Question question); @@ -63,12 +56,12 @@ public interface IDatabaseDao { void updateSessionOwnerActivity(Session session); - Integer getMyFeedback(String keyword, User user); - List<String> getQuestionIds(Session session, User user); void deleteQuestionWithAnswers(Question question); + void deleteAllQuestionsWithAnswers(Session session); + List<String> getUnAnsweredQuestionIds(Session session, User user); Answer getMyAnswer(String questionId, int piRound); diff --git a/src/main/java/de/thm/arsnova/dao/NovaView.java b/src/main/java/de/thm/arsnova/dao/NovaView.java new file mode 100644 index 0000000000000000000000000000000000000000..b9325b598ea5208be3412643ed0c31999ef2dc04 --- /dev/null +++ b/src/main/java/de/thm/arsnova/dao/NovaView.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2012 THM webMedia + * + * This file is part of ARSnova. + * + * ARSnova 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 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.dao; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +import com.fourspaces.couchdb.View; + +public class NovaView extends View { + + public NovaView(String fullname) { + super(fullname); + } + + @Override + public void setStartKey(String key) { + this.startKey = quote(key); + } + + public void setStartKeyArray(String key) { + if (isNumber(key)) { + this.startKey = encode("[" + key + "]"); + } else { + this.startKey = encode("[\"" + key + "\"]"); + } + } + + public void setStartKeyArray(String... keys) { + this.setStartKey(keys); + } + + @Override + public void setEndKey(String key) { + this.endKey = quote(key); + } + + public void setEndKeyArray(String key) { + if (isNumber(key)) { + this.endKey = encode("[" + key + "]"); + } else { + this.endKey = encode("[\"" + key + "\"]"); + } + } + + public void setEndKeyArray(String... keys) { + this.setEndKey(keys); + } + + public void setStartKey(String... keys) { + this.startKey = toJsonArray(keys); + } + + public void setEndKey(String... keys) { + this.endKey = toJsonArray(keys); + } + + @Override + public void setKey(String key) { + this.key = quote(key); + } + + public void setKey(String... keys) { + this.key = toJsonArray(keys); + } + + private String toJsonArray(String[] strings) { + StringBuilder sb = new StringBuilder(); + for (String str : strings) { + if (isNumber(str)) { + sb.append(str + ","); + } else if (str.equals("{}")) { + sb.append(str + ","); + } else { + sb.append("\"" + str + "\"" + ","); + } + } + sb.replace(sb.length() - 1, sb.length(), ""); // remove final comma + sb.insert(0, "["); + sb.append("]"); + return encode(sb.toString()); + } + + private String quote(String string) { + return encode("\"" + string + "\""); + } + + private boolean isNumber(String string) { + return string.matches("^[0-9]+$"); + } + + private String encode(String string) { + try { + return URLEncoder.encode(string, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // Since we're using 'UTF-8', this should Exception should never occur. + } + return ""; + } +} diff --git a/src/main/java/de/thm/arsnova/entities/Answer.java b/src/main/java/de/thm/arsnova/entities/Answer.java index 5aded99a5762f924b2ae42c370c0bc91520ac1ba..278ac92d50b61363859b23a4133e836993b331a9 100644 --- a/src/main/java/de/thm/arsnova/entities/Answer.java +++ b/src/main/java/de/thm/arsnova/entities/Answer.java @@ -13,7 +13,8 @@ public class Answer { private String user; private long timestamp; private int answerCount = 1; - private boolean abstention; // Currently available only for freetext answers! + private boolean abstention; // Currently available only for freetext and mc answers! + private int abstentionCount; // Currently available only for freetext and mc answers! public Answer() { this.type = "skill_question_answer"; @@ -115,6 +116,14 @@ public class Answer { this.abstention = abstention; } + public int getAbstentionCount() { + return abstentionCount; + } + + public void setAbstentionCount(int abstentionCount) { + this.abstentionCount = abstentionCount; + } + @Override public final String toString() { return "Answer type:'" + type + "'" diff --git a/src/main/java/de/thm/arsnova/entities/User.java b/src/main/java/de/thm/arsnova/entities/User.java index 13ae4a9cba5b4f8f2535694ce4132974c0b2414c..2f9c290bf2af3a41d6eac5b16d329d413e0ee4ff 100644 --- a/src/main/java/de/thm/arsnova/entities/User.java +++ b/src/main/java/de/thm/arsnova/entities/User.java @@ -9,6 +9,8 @@ import org.scribe.up.profile.twitter.TwitterProfile; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import de.thm.arsnova.services.UserSessionService; + public class User implements Serializable { public static final String GOOGLE = "google"; public static final String TWITTER = "twitter"; @@ -20,6 +22,7 @@ public class User implements Serializable { private static final long serialVersionUID = 1L; private String username; private String type; + private UserSessionService.Role role; public User(Google2Profile profile) { setUsername(profile.getEmail()); @@ -67,6 +70,18 @@ public class User implements Serializable { this.type = type; } + public UserSessionService.Role getRole() { + return role; + } + + public void setRole(UserSessionService.Role role) { + this.role = role; + } + + public boolean hasRole(UserSessionService.Role role) { + return this.role == role; + } + @Override public String toString() { return "User [username=" + username + ", type=" + type + "]"; diff --git a/src/main/java/de/thm/arsnova/services/IQuestionService.java b/src/main/java/de/thm/arsnova/services/IQuestionService.java index 389e1442a4726e0e9dc696db7426f76377ae0908..4f04a0c73506ff5b42baaf5c87cf24e8321047a8 100644 --- a/src/main/java/de/thm/arsnova/services/IQuestionService.java +++ b/src/main/java/de/thm/arsnova/services/IQuestionService.java @@ -41,6 +41,8 @@ public interface IQuestionService { void deleteQuestion(String questionId); + void deleteAllQuestions(String sessionKeyword); + List<String> getUnAnsweredQuestionIds(String sessionKey); Answer getMyAnswer(String questionId); diff --git a/src/main/java/de/thm/arsnova/services/QuestionService.java b/src/main/java/de/thm/arsnova/services/QuestionService.java index d41a9173f9bec3083b2d0b443be63b896e012941..1f9d70e87685d57dccc93eab2c8afa80612a0c20 100644 --- a/src/main/java/de/thm/arsnova/services/QuestionService.java +++ b/src/main/java/de/thm/arsnova/services/QuestionService.java @@ -148,6 +148,17 @@ public class QuestionService implements IQuestionService { databaseDao.deleteQuestionWithAnswers(question); } + @Override + @Authenticated + public void deleteAllQuestions(String sessionKeyword) { + User user = userService.getCurrentUser(); + Session session = databaseDao.getSession(sessionKeyword); + if (user == null || session == null || !session.isCreator(user)) { + throw new UnauthorizedException(); + } + databaseDao.deleteAllQuestionsWithAnswers(session); + } + @Override @Authenticated public void deleteInterposedQuestion(String questionId) { @@ -229,7 +240,11 @@ public class QuestionService implements IQuestionService { @Override @Authenticated public List<Answer> getFreetextAnswers(String questionId) { - return databaseDao.getFreetextAnswers(questionId); + List<Answer> answers = databaseDao.getFreetextAnswers(questionId); + if (answers == null) { + throw new NotFoundException(); + } + return answers; } @Override diff --git a/src/main/java/de/thm/arsnova/services/SessionService.java b/src/main/java/de/thm/arsnova/services/SessionService.java index 34b2b3d20954ee8082eacdd1249650b467c0c47e..2843b36e72bfef48660f5b8fa2d861c536bc3dd1 100644 --- a/src/main/java/de/thm/arsnova/services/SessionService.java +++ b/src/main/java/de/thm/arsnova/services/SessionService.java @@ -64,7 +64,6 @@ public class SessionService implements ISessionService { } @Override - @Authenticated public final Session joinSession(final String keyword, final UUID socketId) { /* Socket.IO solution */ @@ -96,7 +95,18 @@ public class SessionService implements ISessionService { public final Session joinSession(final String keyword) { /* HTTP polling solution (legacy) */ - Session session = databaseDao.getSession(keyword); + User user = userService.getCurrentUser(); + Session session = databaseDao.getSessionFromKeyword(keyword); + if (session == null) { + throw new NotFoundException(); + } + if (!session.isActive()) { + if (user.hasRole(UserSessionService.Role.STUDENT)) { + throw new ForbiddenException(); + } else if (user.hasRole(UserSessionService.Role.SPEAKER) && !session.isCreator(user)) { + throw new ForbiddenException(); + } + } userService.addCurrentUserToSessionMap(keyword); socketIoServer.reportActiveUserCountForSession(keyword); diff --git a/src/main/java/de/thm/arsnova/services/UserService.java b/src/main/java/de/thm/arsnova/services/UserService.java index 23494da815e2bff274bdc3c52fb22b8bffbebbf0..99521ec7530b50d628bd9b9555633967da72b3f4 100644 --- a/src/main/java/de/thm/arsnova/services/UserService.java +++ b/src/main/java/de/thm/arsnova/services/UserService.java @@ -142,8 +142,12 @@ public class UserService implements IUserService { } String session = user2sessionLegacy.get(user); if (session == null) { - return false; + session = user2session.get(user); + if (session == null) { + return false; + } } + return keyword.equals(session); } diff --git a/src/main/java/de/thm/arsnova/services/UserSessionService.java b/src/main/java/de/thm/arsnova/services/UserSessionService.java index 84f2885033b88772db4c74d34c4f831b6530982d..df3110cd50221cba01a1dc79b7ddb1951271091a 100644 --- a/src/main/java/de/thm/arsnova/services/UserSessionService.java +++ b/src/main/java/de/thm/arsnova/services/UserSessionService.java @@ -8,6 +8,11 @@ import de.thm.arsnova.events.ARSnovaEvent; import de.thm.arsnova.socket.ARSnovaSocketIOServer; public interface UserSessionService { + + enum Role { + STUDENT, + SPEAKER + } void setUser(User user); User getUser(); @@ -18,5 +23,11 @@ public interface UserSessionService { void setSocketId(UUID socketId); UUID getSocketId(); + void setRole(Role role); + Role getRole(); + + boolean inSession(); + boolean isAuthenticated(); + void sendEventViaWebSocket(ARSnovaSocketIOServer server, ARSnovaEvent event); } \ No newline at end of file diff --git a/src/main/java/de/thm/arsnova/services/UserSessionServiceImpl.java b/src/main/java/de/thm/arsnova/services/UserSessionServiceImpl.java index e10a7b6c662d102775b72b729c23009cc72f7fd1..1a2c22874303c9cc95f9a6716bf89dd7443baa6e 100644 --- a/src/main/java/de/thm/arsnova/services/UserSessionServiceImpl.java +++ b/src/main/java/de/thm/arsnova/services/UserSessionServiceImpl.java @@ -25,10 +25,12 @@ public class UserSessionServiceImpl implements UserSessionService, Serializable private User user; private Session session; private UUID socketId; + private Role role; @Override public void setUser(User u) { this.user = u; + this.user.setRole(this.role); } @Override @@ -59,6 +61,22 @@ public class UserSessionServiceImpl implements UserSessionService, Serializable private boolean hasConnectedWebSocket() { return getSocketId() != null; } + + @Override + public boolean inSession() { + return ( + this.isAuthenticated() + && this.getSession() != null + ); + } + + @Override + public boolean isAuthenticated() { + return ( + this.getUser() != null + && this.getRole() != null + ); + } @Override public void sendEventViaWebSocket(ARSnovaSocketIOServer server, ARSnovaEvent event) { @@ -85,4 +103,17 @@ public class UserSessionServiceImpl implements UserSessionService, Serializable server.sendToClient(getSocketId(), event); } } + + @Override + public void setRole(Role r) { + role = r; + if (user != null) { + user.setRole(role); + } + } + + @Override + public Role getRole() { + return role; + } } diff --git a/src/site/apt/installation.apt b/src/site/apt/installation.apt index 824ec56bcaab052d5f97bdfbd961d1c1ce4e14e5..e30cf7011119154090972cc365713a56e487b5e9 100644 --- a/src/site/apt/installation.apt +++ b/src/site/apt/installation.apt @@ -73,4 +73,39 @@ Make environment usable for productive usage To protect requests and responses you should use HTTPS and configure your Apache Webserver installation to redirect all traffic according to this {{{http://wiki.apache.org/httpd/RedirectSSL}example}}. - Finally you should (re)start all services. ARSnova2 is now listening on HTTP port 80 and 443. \ No newline at end of file + Finally you should (re)start all services. ARSnova2 is now listening on HTTP port 80 and 443. + +Securing your websocket connection + + To provide SSL websocket encryption, you have to provide the servers ssl key and certificate in a Java keystore. The following steps will guide you threw this process. + + Use your webserver certificate, private key and certificate chain to create a PKCS12 keystore + ++---------------------------+ +openssl pkcs12 -export -in <servercert>.crt -inkey <serverkey>.key \ + -out keystore.p12 -name 1 \ + -certfile <your_cert_chain_file> ++---------------------------+ + + You will be asked for a password for your PKCS12 keystore. This password must be used for importing this keystore into your java keystore. + The import can be done by using this command: + ++---------------------------+ +keytool -importkeystore \ + -deststorepass <your_java_keystore_password> -destkeypass <your_java_keystore_password> -destkeystore arsnova.jks \ + -srckeystore keystore.p12 -srcstoretype PKCS12 -srcstorepass <your_pkcs12_keystore_password> \ + -alias 1 ++---------------------------+ + + Be sure to provide the correct certificate and key file names and to use the correct passwords for your keystore. + + The last step is to find your ARSnova configuration file, setup the location of your Java keystore and its password. + ++---------------------------+ + +security.ssl=true +security.keystore=<your keystore location> +security.storepass=<your keystore password> + ++---------------------------+ + \ No newline at end of file diff --git a/src/site/resources/showcase.png b/src/site/resources/showcase.png new file mode 100644 index 0000000000000000000000000000000000000000..6832c11cb0666acf7b262769adf8ca51ea428ffe Binary files /dev/null and b/src/site/resources/showcase.png differ diff --git a/src/test/java/de/thm/arsnova/controller/LoginControllerTest.java b/src/test/java/de/thm/arsnova/controller/LoginControllerTest.java index f10539f3e7fb1fd8c0a72ede265db8c7e4da7a62..65b8ffa4af117a2a211af79108bd93fbc928193d 100644 --- a/src/test/java/de/thm/arsnova/controller/LoginControllerTest.java +++ b/src/test/java/de/thm/arsnova/controller/LoginControllerTest.java @@ -126,7 +126,7 @@ public class LoginControllerTest { handlerAdapter.handle(request, response, loginController); assertNotNull(response); - assertEquals("{\"username\":\"ptsr00\",\"type\":\"ldap\"}", response.getContentAsString()); + assertEquals("{\"username\":\"ptsr00\",\"type\":\"ldap\",\"role\":null}", response.getContentAsString()); } @Test diff --git a/src/test/java/de/thm/arsnova/controller/SessionControllerTest.java b/src/test/java/de/thm/arsnova/controller/SessionControllerTest.java index f8fe71eb0d39304eaf7baf4e9abfc2386f15319b..cdb927ae3b831584dc1f6f78db5decd11327273a 100644 --- a/src/test/java/de/thm/arsnova/controller/SessionControllerTest.java +++ b/src/test/java/de/thm/arsnova/controller/SessionControllerTest.java @@ -20,10 +20,12 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter; import de.thm.arsnova.dao.StubDatabaseDao; +import de.thm.arsnova.entities.Session; import de.thm.arsnova.exceptions.ForbiddenException; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.exceptions.UnauthorizedException; import de.thm.arsnova.services.StubUserService; +import de.thm.arsnova.services.UserSessionService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { @@ -76,12 +78,17 @@ public class SessionControllerTest { @Test(expected = ForbiddenException.class) public void testShouldNotGetForbiddenSession() throws Exception { + Session session = new Session(); + session.setKeyword("08154711"); + session.setCreator("some other user"); + session.setActive(false); + databaseDao.saveSession(session); userService.setUserAuthenticated(true); + userService.setRole(UserSessionService.Role.STUDENT); request.setMethod("GET"); - request.setRequestURI("/session/99999999"); - final ModelAndView mav = handlerAdapter.handle(request, response, - sessionController); + request.setRequestURI("/session/08154711"); + final ModelAndView mav = handlerAdapter.handle(request, response, sessionController); assertNull(mav); assertTrue(response.getStatus() == 403); diff --git a/src/test/java/de/thm/arsnova/dao/NovaViewTest.java b/src/test/java/de/thm/arsnova/dao/NovaViewTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2ef0f87694f6e8e24807ac5dcd757f608a727bd6 --- /dev/null +++ b/src/test/java/de/thm/arsnova/dao/NovaViewTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2012 THM webMedia + * + * This file is part of ARSnova. + * + * ARSnova 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 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.dao; + +import static org.junit.Assert.*; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +import org.junit.Test; + +public class NovaViewTest { + + @Test + public void setKeyShouldAcceptSingleArgument() { + NovaView v = new NovaView(null); + v.setKey("foo"); + assertEncodedEquals("key", "\"foo\"", v.getQueryString()); + } + + @Test + public void setKeyShouldAcceptMultipleArgument() { + NovaView v = new NovaView(null); + v.setKey("foo", "bar", "baz"); + assertEncodedEquals("key", "[\"foo\",\"bar\",\"baz\"]", v.getQueryString()); + } + + @Test + public void setStartKeyShouldAcceptSingleArgument() { + NovaView v = new NovaView(null); + v.setStartKey("foo"); + assertEncodedEquals("startkey", "\"foo\"", v.getQueryString()); + } + + @Test + public void setStartKeyShouldAcceptSingleArgumentArray() { + NovaView v = new NovaView(null); + v.setStartKeyArray("foo"); + assertEncodedEquals("startkey", "[\"foo\"]", v.getQueryString()); + } + + @Test + public void setEndKeyShouldAcceptSingleArgumentArray() { + NovaView v = new NovaView(null); + v.setEndKeyArray("foo"); + assertEncodedEquals("endkey", "[\"foo\"]", v.getQueryString()); + } + + @Test + public void setEndKeyShouldAcceptSingleArgument() { + NovaView v = new NovaView(null); + v.setEndKey("foo"); + assertEncodedEquals("endkey", "\"foo\"", v.getQueryString()); + } + + @Test + public void setStartKeyShouldAcceptMultipleArgument() { + NovaView v = new NovaView(null); + v.setStartKey("foo", "bar", "baz"); + assertEncodedEquals("startkey", "[\"foo\",\"bar\",\"baz\"]", v.getQueryString()); + } + + @Test + public void setEndKeyShouldAcceptMultipleArgument() { + NovaView v = new NovaView(null); + v.setEndKey("foo", "bar", "baz"); + assertEncodedEquals("endkey", "[\"foo\",\"bar\",\"baz\"]", v.getQueryString()); + } + + @Test + public void keysShouldSupportEmptyObject() { + NovaView v = new NovaView(null); + v.setKey("foo", "bar", "{}"); + assertEncodedEquals("key", "[\"foo\",\"bar\",{}]", v.getQueryString()); + } + + @Test + public void arrayKeysShouldNotEnquoteNumbers() { + NovaView v = new NovaView(null); + v.setKey("foo", "bar", "2"); + assertEncodedEquals("key", "[\"foo\",\"bar\",2]", v.getQueryString()); + } + + @Test + public void singleArrayKeysShouldNotEnquoteNumbers() { + NovaView v1 = new NovaView(null); + NovaView v2 = new NovaView(null); + v1.setStartKeyArray("2"); + v2.setEndKeyArray("2"); + assertEncodedEquals("startkey", "[2]", v1.getQueryString()); + assertEncodedEquals("endkey", "[2]", v2.getQueryString()); + } + + private void assertEncodedEquals(String key, String expected, String actual) { + try { + assertEquals(key + "=" + URLEncoder.encode(expected, "UTF-8"), actual); + } catch (UnsupportedEncodingException e) { + fail(e.getLocalizedMessage()); + } + } +} diff --git a/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java b/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java index ffc536497e58a35dc181f301609838c55ee2eb46..5f83db6dbe0650196b6c90c5b202437bc019c53b 100644 --- a/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java +++ b/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java @@ -104,11 +104,6 @@ public class StubDatabaseDao implements IDatabaseDao { stubQuestions.put("12345678", questions); } - @Override - public void cleanFeedbackVotes(int cleanupFeedbackDelay) { - stubSessions.clear(); - } - @Override public Session getSession(String keyword) { // Magic keyword for forbidden session @@ -150,37 +145,6 @@ public class StubDatabaseDao implements IDatabaseDao { } return result; } - - @Override - public Feedback getFeedback(String keyword) { - // Magic keyword for forbidden session - if (keyword.equals("99999999")) - throw new ForbiddenException(); - - Feedback feedback = stubFeedbacks.get(keyword); - if (feedback == null) - throw new NotFoundException(); - - return feedback; - } - - @Override - public boolean saveFeedback(String keyword, int value, User user) { - if (stubFeedbacks.get(keyword) == null) { - stubFeedbacks.put(keyword, new Feedback(0, 0, 0, 0)); - } - - Feedback sessionFeedback = stubFeedbacks.get(keyword); - - List<Integer> values = sessionFeedback.getValues(); - values.set(value, values.get(value) + 1); - - sessionFeedback = new Feedback(values.get(0), values.get(1), values.get(2), values.get(3)); - - stubFeedbacks.put(keyword, sessionFeedback); - - return true; - } @Override public boolean sessionKeyAvailable(String keyword) { @@ -240,12 +204,6 @@ public class StubDatabaseDao implements IDatabaseDao { } - @Override - public Integer getMyFeedback(String keyword, User user) { - // TODO Auto-generated method stub - return null; - } - @Override public Answer getMyAnswer(String questionId, int piRound) { // TODO Auto-generated method stub @@ -439,4 +397,10 @@ public class StubDatabaseDao implements IDatabaseDao { public void deleteSession(Session session) { // TODO Auto-generated method stub } + + @Override + public void deleteAllQuestionsWithAnswers(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 5700c0eaef926567b2d4cabc5a2df4d032f0c7d6..c6ede4f8dfd05792f6996ab9a81aed41f0cb3a35 100644 --- a/src/test/java/de/thm/arsnova/services/SessionServiceTest.java +++ b/src/test/java/de/thm/arsnova/services/SessionServiceTest.java @@ -103,8 +103,6 @@ public class SessionServiceTest { @Test public void testShouldSaveSession() { userService.setUserAuthenticated(true); - // Prevent "NotFoundExceptions" inside StubDatabase while saving the session - databaseDao.saveFeedback("11111111", 1, userService.getCurrentUser()); Session session = new Session(); session.setActive(true); diff --git a/src/test/java/de/thm/arsnova/services/StubUserService.java b/src/test/java/de/thm/arsnova/services/StubUserService.java index 94f67fa33f13ed8a6269a8592bda98bbe49b11f2..c7667d2e8739f277df6473eb740a7306a7bd0515 100644 --- a/src/test/java/de/thm/arsnova/services/StubUserService.java +++ b/src/test/java/de/thm/arsnova/services/StubUserService.java @@ -46,4 +46,8 @@ public class StubUserService extends UserService { public User getCurrentUser() { return stubUser; } + + public void setRole(UserSessionService.Role role) { + stubUser.setRole(role); + } }