diff --git a/ARSnova-checkstyle-checker.xml b/ARSnova-checkstyle-checker.xml index 04ce514502474617ae9f7fbcc01877d8adb5d433..c12208180ed04e6301b4b2412083028563d36ac8 100644 --- a/ARSnova-checkstyle-checker.xml +++ b/ARSnova-checkstyle-checker.xml @@ -1,194 +1,194 @@ -<?xml version="1.0"?> -<!DOCTYPE module PUBLIC - "-//Puppy Crawl//DTD Check Configuration 1.2//EN" - "http://www.puppycrawl.com/dtds/configuration_1_2.dtd"> - -<!-- - - Checkstyle configuration that checks the sun coding conventions from: - - - the Java Language Specification at - http://java.sun.com/docs/books/jls/second_edition/html/index.html - - - the Sun Code Conventions at http://java.sun.com/docs/codeconv/ - - - the Javadoc guidelines at - http://java.sun.com/j2se/javadoc/writingdoccomments/index.html - - - the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html - - - some best practices - - Checkstyle is very configurable. Be sure to read the documentation at - http://checkstyle.sf.net (or in your downloaded distribution). - - Most Checks are configurable, be sure to consult the documentation. - - To completely disable a check, just comment it out or delete it from the file. - - Finally, it is worth reading the documentation. - ---> - -<module name="Checker"> - <!-- - If you set the basedir property below, then all reported file - names will be relative to the specified directory. See - http://checkstyle.sourceforge.net/5.x/config.html#Checker - - <property name="basedir" value="${basedir}"/> - --> - - <!-- Checks that each Java package has a Javadoc file used for commenting. --> - <!-- See http://checkstyle.sf.net/config_javadoc.html#JavadocPackage --> - <module name="JavadocPackage"> - <property name="allowLegacy" value="true"/> - </module> - - <!-- Checks whether files end with a new line. --> - <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile --> - <module name="NewlineAtEndOfFile"/> - - <!-- Checks that property files contain the same keys. --> - <!-- See http://checkstyle.sf.net/config_misc.html#Translation --> - <module name="Translation"/> - - <module name="FileLength"/> - - <!-- Following interprets the header file as regular expressions. --> - <!-- <module name="RegexpHeader"/> --> - - <!--module name="FileTabCharacter"> - <property name="eachLine" value="true"/> - </module--> - - <module name="RegexpSingleline"> - <!-- \s matches whitespace character, $ matches end of line. --> - <property name="format" value="\s+$"/> - <property name="message" value="Line has trailing spaces."/> - </module> - - <module name="TreeWalker"> - - <property name="cacheFile" value="${checkstyle.cache.file}"/> - <property name="tabWidth" value="4"/> - - <!-- Checks for Javadoc comments. --> - <!-- See http://checkstyle.sf.net/config_javadoc.html --> - <!-- module name="JavadocMethod"> - <property name="allowMissingJavadoc" value="true"/> - </module> - <module name="JavadocType"/> - <module name="JavadocVariable"/> - <module name="JavadocStyle"/--> - - - <!-- Checks for Naming Conventions. --> - <!-- See http://checkstyle.sf.net/config_naming.html --> - <module name="ConstantName"/> - <module name="LocalFinalVariableName"/> - <module name="LocalVariableName"/> - <module name="MemberName"/> - <module name="MethodName"/> - <module name="PackageName"/> - <module name="ParameterName"/> - <module name="StaticVariableName"/> - <module name="TypeName"/> - - - <!-- Checks for Headers --> - <!-- See http://checkstyle.sf.net/config_header.html --> - <!-- <module name="Header"> --> - <!-- The follow property value demonstrates the ability --> - <!-- to have access to ANT properties. In this case it uses --> - <!-- the ${basedir} property to allow Checkstyle to be run --> - <!-- from any directory within a project. See property --> - <!-- expansion, --> - <!-- http://checkstyle.sf.net/config.html#properties --> - <!-- <property --> - <!-- name="headerFile" --> - <!-- value="${basedir}/java.header"/> --> - <!-- </module> --> - - - <!-- Checks for imports --> - <!-- See http://checkstyle.sf.net/config_import.html --> - <module name="AvoidStarImport"/> - <module name="IllegalImport"/> <!-- defaults to sun.* packages --> - <module name="RedundantImport"/> - <module name="UnusedImports"/> - - - <!-- Checks for Size Violations. --> - <!-- See http://checkstyle.sf.net/config_sizes.html --> - <module name="LineLength"> - <property name="max" value="120" /> - </module> - <module name="MethodLength"/> - <module name="ParameterNumber"/> - - - <!-- Checks for whitespace --> - <!-- See http://checkstyle.sf.net/config_whitespace.html --> - <module name="EmptyForIteratorPad"/> - <module name="MethodParamPad"/> - <module name="NoWhitespaceAfter"/> - <module name="NoWhitespaceBefore"/> - <module name="OperatorWrap"/> - <module name="ParenPad"/> - <module name="TypecastParenPad"/> - <module name="WhitespaceAfter"/> - <module name="WhitespaceAround"/> - - - <!-- Modifier Checks --> - <!-- See http://checkstyle.sf.net/config_modifiers.html --> - <module name="ModifierOrder"/> - <module name="RedundantModifier"/> - - - <!-- Checks for blocks. You know, those {}'s --> - <!-- See http://checkstyle.sf.net/config_blocks.html --> - <module name="AvoidNestedBlocks"/> - <module name="EmptyBlock"/> - <module name="LeftCurly"/> - <module name="NeedBraces"/> - <module name="RightCurly"/> - - - <!-- Checks for common coding problems --> - <!-- See http://checkstyle.sf.net/config_coding.html --> - <!-- module name="AvoidInlineConditionals" /--> - <!-- module name="DoubleCheckedLocking"/--> <!-- MY FAVOURITE --> - <module name="EmptyStatement"/> - <module name="EqualsHashCode"/> - <module name="HiddenField"> - <property name="ignoreSetter" value="true"/> - </module> - <module name="IllegalInstantiation"/> - <module name="InnerAssignment"/> - <module name="MagicNumber"/> - <module name="MissingSwitchDefault"/> - <module name="RedundantThrows"/> - <module name="SimplifyBooleanExpression"/> - <module name="SimplifyBooleanReturn"/> - - <!-- Checks for class design --> - <!-- See http://checkstyle.sf.net/config_design.html --> - <!-- module name="DesignForExtension"/ --> - <!-- module name="FinalClass"/ --> - <module name="HideUtilityClassConstructor"/> - <module name="InterfaceIsType"/> - <module name="VisibilityModifier"/> - - - <!-- Miscellaneous other checks. --> - <!-- See http://checkstyle.sf.net/config_misc.html --> - <module name="ArrayTypeStyle"/> - <!-- module name="FinalParameters"/ --> - <module name="TodoComment"/> - <module name="UpperEll"/> - - </module> - -</module> +<?xml version="1.0"?> +<!DOCTYPE module PUBLIC + "-//Puppy Crawl//DTD Check Configuration 1.2//EN" + "http://www.puppycrawl.com/dtds/configuration_1_2.dtd"> + +<!-- + + Checkstyle configuration that checks the sun coding conventions from: + + - the Java Language Specification at + http://java.sun.com/docs/books/jls/second_edition/html/index.html + + - the Sun Code Conventions at http://java.sun.com/docs/codeconv/ + + - the Javadoc guidelines at + http://java.sun.com/j2se/javadoc/writingdoccomments/index.html + + - the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html + + - some best practices + + Checkstyle is very configurable. Be sure to read the documentation at + http://checkstyle.sf.net (or in your downloaded distribution). + + Most Checks are configurable, be sure to consult the documentation. + + To completely disable a check, just comment it out or delete it from the file. + + Finally, it is worth reading the documentation. + +--> + +<module name="Checker"> + <!-- + If you set the basedir property below, then all reported file + names will be relative to the specified directory. See + http://checkstyle.sourceforge.net/5.x/config.html#Checker + + <property name="basedir" value="${basedir}"/> + --> + + <!-- Checks that each Java package has a Javadoc file used for commenting. --> + <!-- See http://checkstyle.sf.net/config_javadoc.html#JavadocPackage --> + <module name="JavadocPackage"> + <property name="allowLegacy" value="true"/> + </module> + + <!-- Checks whether files end with a new line. --> + <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile --> + <module name="NewlineAtEndOfFile"/> + + <!-- Checks that property files contain the same keys. --> + <!-- See http://checkstyle.sf.net/config_misc.html#Translation --> + <module name="Translation"/> + + <module name="FileLength"/> + + <!-- Following interprets the header file as regular expressions. --> + <!-- <module name="RegexpHeader"/> --> + + <!--module name="FileTabCharacter"> + <property name="eachLine" value="true"/> + </module--> + + <module name="RegexpSingleline"> + <!-- \s matches whitespace character, $ matches end of line. --> + <property name="format" value="\s+$"/> + <property name="message" value="Line has trailing spaces."/> + </module> + + <module name="TreeWalker"> + + <property name="cacheFile" value="${checkstyle.cache.file}"/> + <property name="tabWidth" value="4"/> + + <!-- Checks for Javadoc comments. --> + <!-- See http://checkstyle.sf.net/config_javadoc.html --> + <!-- module name="JavadocMethod"> + <property name="allowMissingJavadoc" value="true"/> + </module> + <module name="JavadocType"/> + <module name="JavadocVariable"/> + <module name="JavadocStyle"/--> + + + <!-- Checks for Naming Conventions. --> + <!-- See http://checkstyle.sf.net/config_naming.html --> + <module name="ConstantName"/> + <module name="LocalFinalVariableName"/> + <module name="LocalVariableName"/> + <module name="MemberName"/> + <module name="MethodName"/> + <module name="PackageName"/> + <module name="ParameterName"/> + <module name="StaticVariableName"/> + <module name="TypeName"/> + + + <!-- Checks for Headers --> + <!-- See http://checkstyle.sf.net/config_header.html --> + <!-- <module name="Header"> --> + <!-- The follow property value demonstrates the ability --> + <!-- to have access to ANT properties. In this case it uses --> + <!-- the ${basedir} property to allow Checkstyle to be run --> + <!-- from any directory within a project. See property --> + <!-- expansion, --> + <!-- http://checkstyle.sf.net/config.html#properties --> + <!-- <property --> + <!-- name="headerFile" --> + <!-- value="${basedir}/java.header"/> --> + <!-- </module> --> + + + <!-- Checks for imports --> + <!-- See http://checkstyle.sf.net/config_import.html --> + <module name="AvoidStarImport"/> + <module name="IllegalImport"/> <!-- defaults to sun.* packages --> + <module name="RedundantImport"/> + <module name="UnusedImports"/> + + + <!-- Checks for Size Violations. --> + <!-- See http://checkstyle.sf.net/config_sizes.html --> + <module name="LineLength"> + <property name="max" value="120" /> + </module> + <module name="MethodLength"/> + <module name="ParameterNumber"/> + + + <!-- Checks for whitespace --> + <!-- See http://checkstyle.sf.net/config_whitespace.html --> + <module name="EmptyForIteratorPad"/> + <module name="MethodParamPad"/> + <module name="NoWhitespaceAfter"/> + <module name="NoWhitespaceBefore"/> + <module name="OperatorWrap"/> + <module name="ParenPad"/> + <module name="TypecastParenPad"/> + <module name="WhitespaceAfter"/> + <module name="WhitespaceAround"/> + + + <!-- Modifier Checks --> + <!-- See http://checkstyle.sf.net/config_modifiers.html --> + <module name="ModifierOrder"/> + <module name="RedundantModifier"/> + + + <!-- Checks for blocks. You know, those {}'s --> + <!-- See http://checkstyle.sf.net/config_blocks.html --> + <module name="AvoidNestedBlocks"/> + <module name="EmptyBlock"/> + <module name="LeftCurly"/> + <module name="NeedBraces"/> + <module name="RightCurly"/> + + + <!-- Checks for common coding problems --> + <!-- See http://checkstyle.sf.net/config_coding.html --> + <!-- module name="AvoidInlineConditionals" /--> + <!-- module name="DoubleCheckedLocking"/--> <!-- MY FAVOURITE --> + <module name="EmptyStatement"/> + <module name="EqualsHashCode"/> + <module name="HiddenField"> + <property name="ignoreSetter" value="true"/> + </module> + <module name="IllegalInstantiation"/> + <module name="InnerAssignment"/> + <module name="MagicNumber"/> + <module name="MissingSwitchDefault"/> + <module name="RedundantThrows"/> + <module name="SimplifyBooleanExpression"/> + <module name="SimplifyBooleanReturn"/> + + <!-- Checks for class design --> + <!-- See http://checkstyle.sf.net/config_design.html --> + <!-- module name="DesignForExtension"/ --> + <!-- module name="FinalClass"/ --> + <module name="HideUtilityClassConstructor"/> + <module name="InterfaceIsType"/> + <module name="VisibilityModifier"/> + + + <!-- Miscellaneous other checks. --> + <!-- See http://checkstyle.sf.net/config_misc.html --> + <module name="ArrayTypeStyle"/> + <!-- module name="FinalParameters"/ --> + <module name="TodoComment"/> + <module name="UpperEll"/> + + </module> + +</module> diff --git a/src/main/java/de/thm/arsnova/FeedbackStorage.java b/src/main/java/de/thm/arsnova/FeedbackStorage.java index 41df30bc2ee716ae38eaee6afb4a44b5d2145e14..4ebeb6b6e71339584efb6ba1af16fe2a720279af 100644 --- a/src/main/java/de/thm/arsnova/FeedbackStorage.java +++ b/src/main/java/de/thm/arsnova/FeedbackStorage.java @@ -1,16 +1,19 @@ package de.thm.arsnova; +import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; -import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.Feedback; +import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; -import de.thm.arsnova.exceptions.NotFoundException; public class FeedbackStorage { private static class FeedbackStorageObject { @@ -31,34 +34,24 @@ public class FeedbackStorage { return timestamp; } public boolean fromUser(final User u) { - return u.getUsername().equals(user.getUsername()); + return user.equals(u); } } - private final IDatabaseDao dao; + private final Map<Session, Map<User, FeedbackStorageObject>> data = + new ConcurrentHashMap<Session, Map<User, FeedbackStorageObject>>(); - private final Map<String, Map<String, FeedbackStorageObject>> data; - - public FeedbackStorage(final IDatabaseDao newDao) { - data = new ConcurrentHashMap<String, Map<String, FeedbackStorageObject>>(); - dao = newDao; - } - - public Feedback getFeedback(final String keyword) { + public Feedback getFeedback(final Session session) { int a = 0; int b = 0; int c = 0; int d = 0; - if (dao.getSession(keyword) == null) { - throw new NotFoundException(); - } - - if (data.get(keyword) == null) { + if (data.get(session) == null) { return new Feedback(0, 0, 0, 0); } - for (final FeedbackStorageObject fso : data.get(keyword).values()) { + for (final FeedbackStorageObject fso : data.get(session).values()) { switch (fso.getValue()) { case Feedback.FEEDBACK_FASTER: a++; @@ -79,12 +72,12 @@ public class FeedbackStorage { return new Feedback(a, b, c, d); } - public Integer getMyFeedback(final String keyword, final User u) { - if (data.get(keyword) == null) { + public Integer getMyFeedback(final Session session, final User u) { + if (data.get(session) == null) { return null; } - for (final FeedbackStorageObject fso : data.get(keyword).values()) { + for (final FeedbackStorageObject fso : data.get(session).values()) { if (fso.fromUser(u)) { return fso.getValue(); } @@ -94,38 +87,42 @@ public class FeedbackStorage { } @Transactional(isolation = Isolation.READ_COMMITTED) - public boolean saveFeedback(final String keyword, final int value, final User user) { - if (dao.getSession(keyword) == null) { - throw new NotFoundException(); - } - - if (data.get(keyword) == null) { - data.put(keyword, new ConcurrentHashMap<String, FeedbackStorageObject>()); + public void saveFeedback(final Session session, final int value, final User user) { + if (data.get(session) == null) { + data.put(session, new ConcurrentHashMap<User, FeedbackStorageObject>()); } - data.get(keyword).put(user.getUsername(), new FeedbackStorageObject(value, user)); - return true; + data.get(session).put(user, new FeedbackStorageObject(value, user)); } @Transactional(isolation = Isolation.READ_COMMITTED) - public void cleanFeedbackVotes(final int cleanupFeedbackDelay) { - for (final String keyword : data.keySet()) { - cleanSessionFeedbackVotes(keyword, cleanupFeedbackDelay); + public Map<Session, List<User>> cleanFeedbackVotes(final int cleanupFeedbackDelay) { + final Map<Session, List<User>> removedFeedbackOfUsersInSession = new HashMap<Session, List<User>>(); + for (final Session session : data.keySet()) { + List<User> affectedUsers = cleanFeedbackVotesInSession(session, cleanupFeedbackDelay); + if (!affectedUsers.isEmpty()) { + removedFeedbackOfUsersInSession.put(session, affectedUsers); + } } + return removedFeedbackOfUsersInSession; } - private void cleanSessionFeedbackVotes(final String keyword, final int cleanupFeedbackDelay) { - final long timelimitInMillis = 60000 * (long) cleanupFeedbackDelay; - final long maxAllowedTimeInMillis = System.currentTimeMillis() - timelimitInMillis; + private List<User> cleanFeedbackVotesInSession(final Session session, final int cleanupFeedbackDelayInMins) { + final long timelimitInMillis = TimeUnit.MILLISECONDS.convert(cleanupFeedbackDelayInMins, TimeUnit.MINUTES); + final Date maxAllowedTime = new Date(System.currentTimeMillis() - timelimitInMillis); - final Map<String, FeedbackStorageObject> sessionFeedbacks = data.get(keyword); + final Map<User, FeedbackStorageObject> sessionFeedbacks = data.get(session); + final List<User> affectedUsers = new ArrayList<User>(); - for (final Map.Entry<String, FeedbackStorageObject> entry : sessionFeedbacks.entrySet()) { - if ( - entry.getValue().getTimestamp().getTime() < maxAllowedTimeInMillis - ) { - sessionFeedbacks.remove(entry.getKey()); + for (final Map.Entry<User, FeedbackStorageObject> entry : sessionFeedbacks.entrySet()) { + final User user = entry.getKey(); + final FeedbackStorageObject feedback = entry.getValue(); + final boolean timeIsUp = feedback.getTimestamp().before(maxAllowedTime); + if (timeIsUp) { + sessionFeedbacks.remove(user); + affectedUsers.add(user); } } + return affectedUsers; } } diff --git a/src/main/java/de/thm/arsnova/ImageUtils.java b/src/main/java/de/thm/arsnova/ImageUtils.java index 2bd23ebf1f5974ac97475b28760fc61c36e5cd60..d8d1f72eb67fb28440ddc631525306650d01d830 100644 --- a/src/main/java/de/thm/arsnova/ImageUtils.java +++ b/src/main/java/de/thm/arsnova/ImageUtils.java @@ -41,7 +41,7 @@ import org.slf4j.LoggerFactory; public final class ImageUtils { // Or whatever size you want to read in at a time. - private final static int CHUNK_SIZE = 4096; + private static final int CHUNK_SIZE = 4096; private ImageUtils() { } @@ -120,7 +120,7 @@ public final class ImageUtils { final byte[] byteChunk = new byte[CHUNK_SIZE]; int n; - while ((n = is.read(byteChunk)) > 0 ) { + while ((n = is.read(byteChunk)) > 0) { baos.write(byteChunk, 0, n); } diff --git a/src/main/java/de/thm/arsnova/aop/UserSessionAspect.java b/src/main/java/de/thm/arsnova/aop/UserSessionAspect.java index 690fdd241bf238dfcb89f3009e9af7d4976f1c11..7ee29d1fd99432ca206002491c553b58bffbcf5f 100644 --- a/src/main/java/de/thm/arsnova/aop/UserSessionAspect.java +++ b/src/main/java/de/thm/arsnova/aop/UserSessionAspect.java @@ -21,8 +21,8 @@ public class UserSessionAspect { * @param session */ @AfterReturning( - pointcut="execution(public * de.thm.arsnova.services.SessionService.joinSession(..)) && args(keyword)", - returning="session" + pointcut = "execution(public * de.thm.arsnova.services.SessionService.joinSession(..)) && args(keyword)", + returning = "session" ) public final void joinSessionAdvice(final JoinPoint jp, final String keyword, final Session session) { userSessionService.setSession(session); diff --git a/src/main/java/de/thm/arsnova/config/ExtraConfig.java b/src/main/java/de/thm/arsnova/config/ExtraConfig.java index 41de2ede58e8052a9464a7236d164027a5073a81..62086912a31fd2478918434f5e4c1acdf3bc4f1a 100644 --- a/src/main/java/de/thm/arsnova/config/ExtraConfig.java +++ b/src/main/java/de/thm/arsnova/config/ExtraConfig.java @@ -72,9 +72,10 @@ public class ExtraConfig { @Profile("test") @Bean(name = "socketServer", initMethod = "startServer", destroyMethod = "stopServer") public ARSnovaSocketIOServer socketTestServer() { + final int testSocketPort = 1234; final ARSnovaSocketIOServer socketServer = new ARSnovaSocketIOServer(); socketServer.setHostIp(socketIp); - socketServer.setPortNumber(socketPort + 1234); + socketServer.setPortNumber(socketPort + testSocketPort); socketServer.setUseSSL(socketUseSll); socketServer.setKeystore(socketKeystore); socketServer.setStorepass(socketStorepass); diff --git a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java index 9ce717e5dc2a33148b3b4c192ce5fe7161786943..e4c7e2d526ec421507a3204656cf91788f935941 100644 --- a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java +++ b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java @@ -38,7 +38,6 @@ import org.springframework.web.bind.annotation.RestController; import de.thm.arsnova.entities.Answer; import de.thm.arsnova.entities.Question; import de.thm.arsnova.exceptions.BadRequestException; -import de.thm.arsnova.exceptions.ForbiddenException; import de.thm.arsnova.exceptions.NoContentException; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.services.IQuestionService; diff --git a/src/main/java/de/thm/arsnova/controller/LoginController.java b/src/main/java/de/thm/arsnova/controller/LoginController.java index c0cd572c60027e037b0ddcbee477561e861d1865..95483dbfd78cc42393420a0e1de14109ec1a97cc 100644 --- a/src/main/java/de/thm/arsnova/controller/LoginController.java +++ b/src/main/java/de/thm/arsnova/controller/LoginController.java @@ -159,7 +159,8 @@ public class LoginController extends AbstractController { Authentication auth = daoProvider.authenticate(authRequest); if (auth.isAuthenticated()) { SecurityContextHolder.getContext().setAuthentication(auth); - request.getSession(true).setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, + request.getSession(true).setAttribute( + HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()); return; @@ -182,14 +183,14 @@ public class LoginController extends AbstractController { Authentication auth = ldapAuthenticationProvider.authenticate(token); if (auth.isAuthenticated()) { SecurityContextHolder.getContext().setAuthentication(token); - request.getSession(true).setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, + request.getSession(true).setAttribute( + HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()); return; } LOGGER.info("LDAPLOGIN: {}", auth.isAuthenticated()); - } - catch (AuthenticationException e) { + } catch (AuthenticationException e) { LOGGER.info("No LDAP login: {}", e); } @@ -272,7 +273,7 @@ public class LoginController extends AbstractController { return userService.getCurrentUser(); } - @RequestMapping(value = { "/auth/logout", "/logout" }, method = { RequestMethod.POST, RequestMethod.GET } ) + @RequestMapping(value = { "/auth/logout", "/logout" }, method = { RequestMethod.POST, RequestMethod.GET }) public final View doLogout(final HttpServletRequest request) { final Authentication auth = SecurityContextHolder.getContext().getAuthentication(); userService.removeUserFromMaps(userService.getCurrentUser()); diff --git a/src/main/java/de/thm/arsnova/controller/SessionController.java b/src/main/java/de/thm/arsnova/controller/SessionController.java index b54df6d521021fbedeae70ec5365538e732246bf..600d51d416709ddbe1f5f56526cb1481a76b904e 100644 --- a/src/main/java/de/thm/arsnova/controller/SessionController.java +++ b/src/main/java/de/thm/arsnova/controller/SessionController.java @@ -43,11 +43,14 @@ import org.springframework.web.bind.annotation.RestController; import de.thm.arsnova.connector.model.Course; import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.SessionInfo; import de.thm.arsnova.exceptions.UnauthorizedException; import de.thm.arsnova.services.ISessionService; import de.thm.arsnova.services.IUserService; -import de.thm.arsnova.services.SessionService.SessionNameComperator; -import de.thm.arsnova.services.SessionService.SessionShortNameComperator; +import de.thm.arsnova.services.SessionService.SessionNameComparator; +import de.thm.arsnova.services.SessionService.SessionInfoNameComparator; +import de.thm.arsnova.services.SessionService.SessionShortNameComparator; +import de.thm.arsnova.services.SessionService.SessionInfoShortNameComparator; import de.thm.arsnova.web.DeprecatedApi; @RestController @@ -65,7 +68,7 @@ public class SessionController extends AbstractController { @RequestMapping(value = "/{sessionkey}", method = RequestMethod.GET) public final Session joinSession(@PathVariable final String sessionkey) { final Session session = sessionService.getSession(sessionkey); - if (! session.getCreator().equals(userService.getCurrentUser().getUsername())) { + if (!session.isCreator(userService.getCurrentUser())) { session.setCreator("NOT VISIBLE TO YOU"); } else { session.setCreator(Sha512DigestUtils.shaHex(session.getCreator())); @@ -146,14 +149,46 @@ public class SessionController extends AbstractController { } if (sortby != null && sortby.equals("shortname")) { - Collections.sort(sessions, new SessionShortNameComperator()); + Collections.sort(sessions, new SessionShortNameComparator()); } else { - Collections.sort(sessions, new SessionNameComperator()); + Collections.sort(sessions, new SessionNameComparator()); } return sessions; } + /** + * Returns a list of my own sessions with only the necessary information like name, keyword, or counters. + * @param statusOnly The flag that has to be set in order to get this shortened list. + * @param response + * @return + */ + @RequestMapping(value = "/", method = RequestMethod.GET, params = "statusonly=true") + public final List<SessionInfo> getMySessions( + @RequestParam(value = "visitedonly", defaultValue = "false") final boolean visitedOnly, + @RequestParam(value = "sortby", defaultValue = "name") final String sortby, + final HttpServletResponse response + ) { + List<SessionInfo> sessions; + if (!visitedOnly) { + sessions = sessionService.getMySessionsInfo(); + } else { + sessions = sessionService.getMyVisitedSessionsInfo(); + } + + if (sessions == null || sessions.isEmpty()) { + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + return null; + } + + if (sortby != null && sortby.equals("shortname")) { + Collections.sort(sessions, new SessionInfoShortNameComparator()); + } else { + Collections.sort(sessions, new SessionInfoNameComparator()); + } + return sessions; + } + @RequestMapping(value = "/{sessionkey}/lock", method = RequestMethod.POST) public final Session lockSession( @PathVariable final String sessionkey, diff --git a/src/main/java/de/thm/arsnova/controller/SocketController.java b/src/main/java/de/thm/arsnova/controller/SocketController.java index dd874dd03e5635cdcc2c5170d5c4b9783a042fd9..6dc3ef68187124958595cd99d5dc3e571006d368 100644 --- a/src/main/java/de/thm/arsnova/controller/SocketController.java +++ b/src/main/java/de/thm/arsnova/controller/SocketController.java @@ -51,22 +51,20 @@ public class SocketController extends AbstractController { @Autowired private ARSnovaSocketIOServer server; - private static final Logger logger = LoggerFactory.getLogger(SocketController.class); + private static final Logger LOGGER = LoggerFactory.getLogger(SocketController.class); @RequestMapping(method = RequestMethod.POST, value = "/assign") public final void authorize(@RequestBody final Map<String, String> sessionMap, final HttpServletResponse response) { String socketid = sessionMap.get("session"); if (null == socketid) { - logger.debug("Expected property 'session' missing", socketid); + LOGGER.debug("Expected property 'session' missing", socketid); response.setStatus(HttpStatus.BAD_REQUEST.value()); - return; } User u = userService.getCurrentUser(); if (null == u) { - logger.debug("Client {} requested to assign Websocket session but has not authenticated", socketid); + LOGGER.debug("Client {} requested to assign Websocket session but has not authenticated", socketid); response.setStatus(HttpStatus.FORBIDDEN.value()); - return; } userService.putUser2SocketId(UUID.fromString(socketid), u); diff --git a/src/main/java/de/thm/arsnova/controller/WelcomeController.java b/src/main/java/de/thm/arsnova/controller/WelcomeController.java index f54afa314f2ec301eab3fb672eddf3c3ddd30c1d..626cb92495b900c1f210f1e55cf89824c4a64163 100644 --- a/src/main/java/de/thm/arsnova/controller/WelcomeController.java +++ b/src/main/java/de/thm/arsnova/controller/WelcomeController.java @@ -34,7 +34,7 @@ import org.springframework.web.servlet.view.RedirectView; public class WelcomeController extends AbstractController { @Value("${mobile.path}") - String mobileContextPath; + private String mobileContextPath; @RequestMapping(value = "/", method = RequestMethod.GET) public final View home(final HttpServletRequest request) { diff --git a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java index b11ce969d65b6d7d77b8a35c80514af8ce60f67d..ccbd2f0d6ab52b2338726a046952eeb6b3226d87 100644 --- a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java +++ b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java @@ -20,15 +20,15 @@ package de.thm.arsnova.dao; import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.text.SimpleDateFormat; import java.util.AbstractMap; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Collection; -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; @@ -59,6 +59,7 @@ import de.thm.arsnova.entities.LoggedIn; import de.thm.arsnova.entities.PossibleAnswer; import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.SessionInfo; import de.thm.arsnova.entities.User; import de.thm.arsnova.entities.VisitedSession; import de.thm.arsnova.exceptions.NotFoundException; @@ -128,6 +129,145 @@ public class CouchDBDao implements IDatabaseDao { return result; } + @Override + public final List<SessionInfo> getMySessionsInfo(final User user) { + final List<Session> sessions = this.getMySessions(user); + return getInfosForSessions(sessions); + } + + private List<SessionInfo> getInfosForSessions(final List<Session> sessions) { + final ExtendedView questionCountView = new ExtendedView("skill_question/count_by_session"); + final ExtendedView answerCountView = new ExtendedView("skill_question/count_answers_by_session"); + final ExtendedView interposedCountView = new ExtendedView("interposed_question/count_by_session_reading"); + questionCountView.setSessionIdKeys(sessions); + questionCountView.setGroup(true); + answerCountView.setSessionIdKeys(sessions); + answerCountView.setGroup(true); + List<String> interposedQueryKeys = new ArrayList<String>(); + for (Session s : sessions) { + interposedQueryKeys.add("[\"" + s.get_id() + "\",\"unread\"]"); + } + interposedCountView.setKeys(interposedQueryKeys); + interposedCountView.setGroup(true); + return getSessionInfoData(sessions, questionCountView, answerCountView, interposedCountView); + } + + private List<SessionInfo> getInfosForVisitedSessions(final List<Session> sessions, final User user) { + final ExtendedView answeredQuestionsView = new ExtendedView("answer/by_user"); + final ExtendedView questionIdsView = new ExtendedView("skill_question/by_session_only_id_for_all"); + questionIdsView.setSessionIdKeys(sessions); + List<String> answeredQuestionQueryKeys = new ArrayList<String>(); + for (Session s : sessions) { + answeredQuestionQueryKeys.add("[\"" + user.getUsername() + "\",\"" + s.get_id() + "\"]"); + } + answeredQuestionsView.setKeys(answeredQuestionQueryKeys); + return getVisitedSessionInfoData(sessions, answeredQuestionsView, questionIdsView); + } + + private List<SessionInfo> getVisitedSessionInfoData(List<Session> sessions, + ExtendedView answeredQuestionsView, ExtendedView questionIdsView) { + final Map<String, Set<String>> answeredQuestionsMap = new HashMap<String, Set<String>>(); + final Map<String, Set<String>> questionIdMap = new HashMap<String, Set<String>>(); + final ViewResults answeredQuestionsViewResults = getDatabase().view(answeredQuestionsView); + final ViewResults questionIdsViewResults = getDatabase().view(questionIdsView); + + // Maps a session ID to a set of question IDs of answered questions of that session + for (final Document d : answeredQuestionsViewResults.getResults()) { + final String sessionId = d.getJSONArray("key").getString(1); + final String questionId = d.getString("value"); + Set<String> questionIdsInSession = answeredQuestionsMap.get(sessionId); + if (questionIdsInSession == null) { + questionIdsInSession = new HashSet<String>(); + } + questionIdsInSession.add(questionId); + answeredQuestionsMap.put(sessionId, questionIdsInSession); + } + + // Maps a session ID to a set of question IDs of that session + for (final Document d : questionIdsViewResults.getResults()) { + final String sessionId = d.getString("key"); + final String questionId = d.getId(); + Set<String> questionIdsInSession = questionIdMap.get(sessionId); + if (questionIdsInSession == null) { + questionIdsInSession = new HashSet<String>(); + } + questionIdsInSession.add(questionId); + questionIdMap.put(sessionId, questionIdsInSession); + } + + // For each session, count the question IDs that are not yet answered + Map<String, Integer> unansweredQuestionsCountMap = new HashMap<String, Integer>(); + for (final Session s : sessions) { + if (!questionIdMap.containsKey(s.get_id())) { + continue; + } + // Note: create a copy of the first set so that we don't modify the contents in the original set + Set<String> questionIdsInSession = new HashSet<String>(questionIdMap.get(s.get_id())); + Set<String> answeredQuestionIdsInSession = answeredQuestionsMap.get(s.get_id()); + if (answeredQuestionIdsInSession == null) { + answeredQuestionIdsInSession = new HashSet<String>(); + } + questionIdsInSession.removeAll(answeredQuestionIdsInSession); + unansweredQuestionsCountMap.put(s.get_id(), questionIdsInSession.size()); + } + + List<SessionInfo> sessionInfos = new ArrayList<SessionInfo>(); + for (Session session : sessions) { + int numUnanswered = 0; + + if (unansweredQuestionsCountMap.containsKey(session.get_id())) { + numUnanswered = unansweredQuestionsCountMap.get(session.get_id()); + } + SessionInfo info = new SessionInfo(session); + info.setNumUnanswered(numUnanswered); + sessionInfos.add(info); + } + return sessionInfos; + } + + private List<SessionInfo> getSessionInfoData(final List<Session> sessions, + final ExtendedView questionCountView, + final ExtendedView answerCountView, + final ExtendedView interposedCountView) { + final ViewResults questionCountViewResults = getDatabase().view(questionCountView); + final ViewResults answerCountViewResults = getDatabase().view(answerCountView); + final ViewResults interposedCountViewResults = getDatabase().view(interposedCountView); + + Map<String, Integer> questionCountMap = new HashMap<String, Integer>(); + for (final Document d : questionCountViewResults.getResults()) { + questionCountMap.put(d.getString("key"), d.getInt("value")); + } + Map<String, Integer> answerCountMap = new HashMap<String, Integer>(); + for (final Document d : answerCountViewResults.getResults()) { + answerCountMap.put(d.getString("key"), d.getInt("value")); + } + Map<String, Integer> interposedCountMap = new HashMap<String, Integer>(); + for (final Document d : interposedCountViewResults.getResults()) { + interposedCountMap.put(d.getJSONArray("key").getString(0), d.getInt("value")); + } + List<SessionInfo> sessionInfos = new ArrayList<SessionInfo>(); + for (Session session : sessions) { + int numQuestions = 0; + int numAnswers = 0; + int numInterposed = 0; + if (questionCountMap.containsKey(session.get_id())) { + numQuestions = questionCountMap.get(session.get_id()); + } + if (answerCountMap.containsKey(session.get_id())) { + numAnswers = answerCountMap.get(session.get_id()); + } + if (interposedCountMap.containsKey(session.get_id())) { + numInterposed = interposedCountMap.get(session.get_id()); + } + SessionInfo info = new SessionInfo(session); + info.setNumQuestions(numQuestions); + info.setNumAnswers(numAnswers); + info.setNumInterposed(numInterposed); + sessionInfos.add(info); + } + return sessionInfos; + } + @Override public final List<Question> getSkillQuestions(final User user, final Session session) { String viewName; @@ -370,10 +510,8 @@ public class CouchDBDao implements IDatabaseDao { ); final JSONArray possibleAnswers = results.getJSONArray("rows").optJSONObject(0).optJSONObject("value") .getJSONArray("possibleAnswers"); - final Collection<PossibleAnswer> answers = JSONArray.toCollection( - possibleAnswers, - PossibleAnswer.class - ); + @SuppressWarnings("unchecked") + final Collection<PossibleAnswer> answers = JSONArray.toCollection(possibleAnswers, PossibleAnswer.class); q.setPossibleAnswers(new ArrayList<PossibleAnswer>(answers)); q.setSessionKeyword(getSessionKeyword(q.getSessionId())); return q; @@ -396,6 +534,7 @@ public class CouchDBDao implements IDatabaseDao { loggedIn = (LoggedIn) JSONObject.toBean(json, LoggedIn.class); final JSONArray vs = json.optJSONArray("visitedSessions"); if (vs != null) { + @SuppressWarnings("unchecked") final Collection<VisitedSession> visitedSessions = JSONArray.toCollection(vs, VisitedSession.class); loggedIn.setVisitedSessions(new ArrayList<VisitedSession>(visitedSessions)); } @@ -427,6 +566,7 @@ public class CouchDBDao implements IDatabaseDao { final LoggedIn l = (LoggedIn) JSONObject.toBean(doc.getJSONObject(), LoggedIn.class); final JSONArray vs = doc.getJSONObject().optJSONArray("visitedSessions"); if (vs != null) { + @SuppressWarnings("unchecked") final Collection<VisitedSession> visitedSessions = JSONArray.toCollection(vs, VisitedSession.class); l.setVisitedSessions(new ArrayList<VisitedSession>(visitedSessions)); } @@ -588,22 +728,6 @@ public class CouchDBDao implements IDatabaseDao { return results.getJSONArray("rows").optJSONObject(0).optInt("value"); } - @Override - public final int countActiveUsers(final long since) { - try { - final View view = new View("statistic/count_active_users"); - view.setStartKey(String.valueOf(since)); - final ViewResults results = getDatabase().view(view); - if (isEmptyResults(results)) { - return 0; - } - return results.getJSONArray("rows").optJSONObject(0).getInt("value"); - } catch (final Exception e) { - LOGGER.error("Error while retrieving active users count", e); - } - return 0; - } - private boolean isEmptyResults(final ViewResults results) { return results == null || results.getResults().isEmpty() || results.getJSONArray("rows").size() == 0; } @@ -796,31 +920,6 @@ public class CouchDBDao implements IDatabaseDao { return null; } - @Override - public void vote(final User me, final String menu) { - final String date = new SimpleDateFormat("dd-mm-yyyyy").format(new Date()); - try { - final NovaView view = new NovaView("food_vote/get_user_vote"); - view.setKey(date, me.getUsername()); - final ViewResults results = getDatabase().view(view); - - if (results.getResults().isEmpty()) { - final Document vote = new Document(); - vote.put("type", "food_vote"); - vote.put("name", menu); - vote.put("user", me.getUsername()); - vote.put("day", date); - database.saveDocument(vote); - } else { - final Document vote = results.getResults().get(0); - vote.put("name", menu); - database.saveDocument(vote); - } - } catch (final IOException e) { - LOGGER.error("Error while saving user food vote", e); - } - } - @Override public int countSessions() { return sessionsCountValue("openSessions") @@ -911,29 +1010,62 @@ public class CouchDBDao implements IDatabaseDao { // Not all users have visited sessions if (d.getJSONObject().optJSONArray("value") != null) { @SuppressWarnings("unchecked") - final - Collection<Session> visitedSessions = JSONArray.toCollection( - d.getJSONObject().getJSONArray("value"), - Session.class - ); + final Collection<Session> visitedSessions = JSONArray.toCollection( + d.getJSONObject().getJSONArray("value"), + Session.class + ); allSessions.addAll(visitedSessions); } } - // Do these sessions still exist? + // Filter sessions that don't exist anymore, also filter my own sessions final List<Session> result = new ArrayList<Session>(); + final List<Session> filteredSessions = new ArrayList<Session>(); for (final Session s : allSessions) { try { final Session session = getSessionFromKeyword(s.getKeyword()); - if (session != null) { + if (session != null && !session.isCreator(user)) { result.add(session); + } else { + filteredSessions.add(s); } } catch (final NotFoundException e) { - // TODO Remove non existant session + filteredSessions.add(s); } } + if (filteredSessions.isEmpty()) { + return result; + } + // Update document to remove sessions that don't exist anymore + try { + List<VisitedSession> visitedSessions = new ArrayList<VisitedSession>(); + for (final Session s : result) { + visitedSessions.add(new VisitedSession(s)); + } + final LoggedIn loggedIn = new LoggedIn(); + final Document loggedInDocument = getDatabase().getDocument(sessions.getResults().get(0).getString("id")); + loggedIn.setSessionId(loggedInDocument.getString("sessionId")); + loggedIn.setUser(user.getUsername()); + loggedIn.setTimestamp(loggedInDocument.getLong("timestamp")); + loggedIn.setType(loggedInDocument.getString("type")); + loggedIn.setVisitedSessions(visitedSessions); + loggedIn.set_id(loggedInDocument.getId()); + loggedIn.set_rev(loggedInDocument.getRev()); + + final JSONObject json = JSONObject.fromObject(loggedIn); + final Document doc = new Document(json); + getDatabase().saveDocument(doc); + } catch (IOException e) { + LOGGER.error("Could not clean up logged_in document of {}", user.getUsername()); + } return result; } + @Override + public List<SessionInfo> getMyVisitedSessionsInfo(final User user) { + List<Session> sessions = this.getMyVisitedSessions(user); + return this.getInfosForVisitedSessions(sessions, user); + } + @Override public Answer saveAnswer(final Answer answer, final User user) { try { @@ -1013,71 +1145,26 @@ public class CouchDBDao implements IDatabaseDao { return result; } - @Override - public final List<String> getActiveUsers(final int timeDifference) { - final long inactiveBeforeTimestamp = new Date().getTime() - timeDifference * 1000; - - final NovaView view = new NovaView("logged_in/by_and_only_timestamp_and_username"); - view.setStartKeyArray(String.valueOf(inactiveBeforeTimestamp)); - final ViewResults results = getDatabase().view(view); - - final List<String> result = new ArrayList<String>(); - for (final Document d : results.getResults()) { - result.add(d.getJSONObject().getJSONArray("key").getString(1)); - } - return result; - } - - private static class ExtendedView extends View { - - private String keys; + private static class ExtendedView extends NovaView { public ExtendedView(final String fullname) { super(fullname); } - public void setKeys(final String newKeys) { - keys = newKeys; - } - public void setCourseIdKeys(final List<Course> courses) { - if (courses.isEmpty()) { - keys = "[]"; - return; - } - - final StringBuilder sb = new StringBuilder(); - sb.append("["); - for (int i = 0; i < courses.size() - 1; i++) { - sb.append("\"" + courses.get(i).getId() + "\","); - } - sb.append("\"" + courses.get(courses.size() - 1).getId() + "\""); - sb.append("]"); - try { - setKeys(URLEncoder.encode(sb.toString(), "UTF-8")); - } catch (final UnsupportedEncodingException e) { - LOGGER.error("Error while encoding course ID keys", e); + List<String> courseIds = new ArrayList<String>(); + for (Course c : courses) { + courseIds.add(c.getId()); } + setKeys(courseIds); } - @Override - public String getQueryString() { - final StringBuilder query = new StringBuilder(); - if (super.getQueryString() != null) { - query.append(super.getQueryString()); - } - if (keys != null) { - if (query.toString().isEmpty()) { - query.append("&"); - } - - query.append("keys=" + keys); - } - - if (query.toString().isEmpty()) { - return null; + public void setSessionIdKeys(final List<Session> sessions) { + List<String> sessionIds = new ArrayList<String>(); + for (Session s : sessions) { + sessionIds.add(s.get_id()); } - return query.toString(); + setKeys(sessionIds); } } @@ -1260,7 +1347,7 @@ public class CouchDBDao implements IDatabaseDao { } private List<String> getLectureQuestionIds(final Session session, final User user) { - NovaView view = new NovaView("skill_question/lecture_question_by_session_for_all"); + NovaView view = new NovaView("skill_question/lecture_question_ids_by_session_for_all"); view.setStartKeyArray(session.get_id()); view.setEndKeyArray(session.get_id(), "{}"); return collectQuestionIds(view); @@ -1274,7 +1361,7 @@ public class CouchDBDao implements IDatabaseDao { } private List<String> getPreparationQuestionIds(final Session session, final User user) { - NovaView view = new NovaView("skill_question/preparation_question_by_session_for_all"); + NovaView view = new NovaView("skill_question/preparation_question_ids_by_session_for_all"); view.setStartKeyArray(session.get_id()); view.setEndKeyArray(session.get_id(), "{}"); return collectQuestionIds(view); diff --git a/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java b/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java index 45926de753f09d1f2f4ce8d558093e8541eab753..37e1a2115e5701672fb6504d616c963723786fe0 100644 --- a/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java +++ b/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java @@ -30,6 +30,7 @@ import de.thm.arsnova.entities.InterposedReadingCount; import de.thm.arsnova.entities.LoggedIn; import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.SessionInfo; import de.thm.arsnova.entities.User; public interface IDatabaseDao { @@ -73,8 +74,6 @@ public interface IDatabaseDao { List<Answer> getFreetextAnswers(String questionId); - int countActiveUsers(long since); - List<Answer> getMyAnswers(User me, String sessionKey); int getTotalAnswerCount(String sessionKey); @@ -89,8 +88,6 @@ public interface IDatabaseDao { List<InterposedQuestion> getInterposedQuestions(Session session, User user); - void vote(User me, String menu); - int countSessions(); int countOpenSessions(); @@ -125,8 +122,6 @@ public interface IDatabaseDao { Session lockSession(Session session, Boolean lock); - List<String> getActiveUsers(int timeDifference); - Session updateSession(Session session); void deleteSession(Session session); @@ -174,4 +169,8 @@ public interface IDatabaseDao { int getLearningProgress(Session session); SimpleEntry<Integer, Integer> getMyLearningProgress(Session session, User user); + + List<SessionInfo> getMySessionsInfo(User user); + + List<SessionInfo> getMyVisitedSessionsInfo(User currentUser); } diff --git a/src/main/java/de/thm/arsnova/dao/NovaView.java b/src/main/java/de/thm/arsnova/dao/NovaView.java index 7649b68698fb4f26c9a14d3823515c9f8a74bda5..dbcbb8f55731e04efe7ee7e150ae613de78afd4f 100644 --- a/src/main/java/de/thm/arsnova/dao/NovaView.java +++ b/src/main/java/de/thm/arsnova/dao/NovaView.java @@ -20,11 +20,17 @@ package de.thm.arsnova.dao; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.StringUtils; import com.fourspaces.couchdb.View; public class NovaView extends View { + protected String keys; + public NovaView(final String fullname) { super(fullname); } @@ -80,22 +86,40 @@ public class NovaView extends View { key = toJsonArray(keys); } - private String toJsonArray(final String[] strings) { - final StringBuilder sb = new StringBuilder(); - for (final String str : strings) { - if (isNumber(str)) { - sb.append(str + ","); - } else if (str.equals("{}")) { - sb.append(str + ","); + public void setKeys(List<String> keys) { + this.keys = toJsonArray(keys.toArray(new String[keys.size()])); + } + + @Override + public String getQueryString() { + final String tempQuery = super.getQueryString(); + final StringBuilder query = new StringBuilder(); + if (tempQuery != null) { + query.append(tempQuery); + } + if (keys != null) { + if (query.length() > 0) { + query.append("&"); + } + query.append("keys=" + keys); + } + + if (query.length() == 0) { + return null; + } + return query.toString(); + } + + private String toJsonArray(final String[] strs) { + final List<String> strings = new ArrayList<String>(); + for (final String string : strs) { + if (isNumber(string) || isPlaceholder(string) || isArray(string)) { + strings.add(string); } else { - sb.append("\"" + str + "\"" + ","); + strings.add("\"" + string + "\""); } } - // remove final comma - sb.replace(sb.length() - 1, sb.length(), ""); - sb.insert(0, "["); - sb.append("]"); - return encode(sb.toString()); + return encode("[" + StringUtils.join(strings, ",") + "]"); } private String quote(final String string) { @@ -106,6 +130,14 @@ public class NovaView extends View { return string.matches("^[0-9]+$"); } + private boolean isPlaceholder(final String string) { + return string.equals("{}"); + } + + private boolean isArray(final String string) { + return string.startsWith("[") && string.endsWith("]"); + } + private String encode(final String string) { try { return URLEncoder.encode(string, "UTF-8"); diff --git a/src/main/java/de/thm/arsnova/entities/Session.java b/src/main/java/de/thm/arsnova/entities/Session.java index ff44e39928e05b99b796ccaf67be350961ba8fa4..7c7b2d684f5f455482df80b1abc21f0f05551c61 100644 --- a/src/main/java/de/thm/arsnova/entities/Session.java +++ b/src/main/java/de/thm/arsnova/entities/Session.java @@ -148,6 +148,24 @@ public class Session implements Serializable { @Override public String toString() { - return "Session [keyword=" + keyword+ ", type=" + type + "]"; + return "Session [keyword=" + keyword+ ", type=" + type + ", creator=" + creator + "]"; + } + + @Override + public int hashCode() { + // See http://stackoverflow.com/a/113600 + final int theAnswer = 42; + final int theOthers = 37; + + return theOthers * theAnswer + this.keyword.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !obj.getClass().equals(this.getClass())) { + return false; + } + Session other = (Session) obj; + return this.keyword.equals(other.keyword); } } diff --git a/src/main/java/de/thm/arsnova/entities/SessionInfo.java b/src/main/java/de/thm/arsnova/entities/SessionInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..54577aed729aa7522192b55f69a2786622168ae5 --- /dev/null +++ b/src/main/java/de/thm/arsnova/entities/SessionInfo.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2014 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.entities; + +import java.util.ArrayList; +import java.util.List; + +public class SessionInfo { + + private String name; + private String shortName; + private String keyword; + private boolean active; + private String courseType; + + private int numQuestions; + private int numAnswers; + private int numInterposed; + private int numUnanswered; + + public SessionInfo(Session session) { + this.name = session.getName(); + this.shortName = session.getShortName(); + this.keyword = session.getKeyword(); + this.active = session.isActive(); + this.courseType = session.getCourseType(); + } + + public static List<SessionInfo> fromSessionList(List<Session> sessions) { + List<SessionInfo> infos = new ArrayList<SessionInfo>(); + for (Session s : sessions) { + infos.add(new SessionInfo(s)); + } + return infos; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getShortName() { + return shortName; + } + + public void setShortName(String shortName) { + this.shortName = shortName; + } + + public String getKeyword() { + return keyword; + } + + public void setKeyword(String keyword) { + this.keyword = keyword; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public String getCourseType() { + return courseType; + } + + public void setCourseType(String courseType) { + this.courseType = courseType; + } + + public int getNumQuestions() { + return numQuestions; + } + + public void setNumQuestions(int numQuestions) { + this.numQuestions = numQuestions; + } + + public int getNumAnswers() { + return numAnswers; + } + + public void setNumAnswers(int numAnswers) { + this.numAnswers = numAnswers; + } + + public int getNumInterposed() { + return numInterposed; + } + + public void setNumInterposed(int numInterposed) { + this.numInterposed = numInterposed; + } + + public int getNumUnanswered() { + return numUnanswered; + } + + public void setNumUnanswered(int numUnanswered) { + this.numUnanswered = numUnanswered; + } +} diff --git a/src/main/java/de/thm/arsnova/entities/VisitedSession.java b/src/main/java/de/thm/arsnova/entities/VisitedSession.java index 06569a43cf50e546866a3b23bc452ff1a8dfca8e..bc40b8117e142800618113c65814d855f4fd807a 100644 --- a/src/main/java/de/thm/arsnova/entities/VisitedSession.java +++ b/src/main/java/de/thm/arsnova/entities/VisitedSession.java @@ -18,6 +18,7 @@ */ package de.thm.arsnova.entities; + public class VisitedSession { private String _id; private String name; diff --git a/src/main/java/de/thm/arsnova/events/NewInterposedQuestionEvent.java b/src/main/java/de/thm/arsnova/events/NewInterposedQuestionEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..d66d136254473c538cfcd1e005accea2aa0c7996 --- /dev/null +++ b/src/main/java/de/thm/arsnova/events/NewInterposedQuestionEvent.java @@ -0,0 +1,32 @@ +package de.thm.arsnova.events; + +import de.thm.arsnova.entities.InterposedQuestion; +import de.thm.arsnova.entities.Session; + +public class NewInterposedQuestionEvent extends NovaEvent { + + private static final long serialVersionUID = 1L; + + private final Session session; + private final InterposedQuestion question; + + public NewInterposedQuestionEvent(Object source, InterposedQuestion question, Session session) { + super(source); + this.question = question; + this.session = session; + } + + public Session getSession() { + return session; + } + + public InterposedQuestion getQuestion() { + return question; + } + + @Override + public void accept(NovaEventVisitor visitor) { + visitor.visit(this); + } + +} diff --git a/src/main/java/de/thm/arsnova/events/NewQuestionEvent.java b/src/main/java/de/thm/arsnova/events/NewQuestionEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..aa40f1f6bc90aec96a0efc4f30e62a4dc34637b5 --- /dev/null +++ b/src/main/java/de/thm/arsnova/events/NewQuestionEvent.java @@ -0,0 +1,32 @@ +package de.thm.arsnova.events; + +import de.thm.arsnova.entities.Question; +import de.thm.arsnova.entities.Session; + +public class NewQuestionEvent extends NovaEvent { + + private static final long serialVersionUID = 1L; + + private final Question question; + + private final Session session; + + public NewQuestionEvent(Object source, Question question, Session session) { + super(source); + this.question = question; + this.session = session; + } + + public Question getQuestion() { + return question; + } + + public Session getSession() { + return session; + } + + @Override + public void accept(NovaEventVisitor visitor) { + visitor.visit(this); + } +} diff --git a/src/main/java/de/thm/arsnova/events/NovaEvent.java b/src/main/java/de/thm/arsnova/events/NovaEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..81afe7c138b8750ecc95394098aba5e8f35aae90 --- /dev/null +++ b/src/main/java/de/thm/arsnova/events/NovaEvent.java @@ -0,0 +1,15 @@ +package de.thm.arsnova.events; + +import org.springframework.context.ApplicationEvent; + +public abstract class NovaEvent extends ApplicationEvent { + + private static final long serialVersionUID = 1L; + + public NovaEvent(Object source) { + super(source); + } + + public abstract void accept(NovaEventVisitor visitor); + +} diff --git a/src/main/java/de/thm/arsnova/events/NovaEventVisitor.java b/src/main/java/de/thm/arsnova/events/NovaEventVisitor.java new file mode 100644 index 0000000000000000000000000000000000000000..4664d21d26c63cf0ecd5e0dc0ddcb04f5e0c5fe8 --- /dev/null +++ b/src/main/java/de/thm/arsnova/events/NovaEventVisitor.java @@ -0,0 +1,9 @@ +package de.thm.arsnova.events; + +public interface NovaEventVisitor { + + void visit(NewInterposedQuestionEvent newInterposedQuestionEvent); + + void visit(NewQuestionEvent newQuestionEvent); + +} diff --git a/src/main/java/de/thm/arsnova/events/package-info.java b/src/main/java/de/thm/arsnova/events/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..acecfa4e5ef8424c1dd8e1c30ee9f49d06cba7e1 --- /dev/null +++ b/src/main/java/de/thm/arsnova/events/package-info.java @@ -0,0 +1,5 @@ +/** + * @author Christoph Thelen + * + */ +package de.thm.arsnova.events; diff --git a/src/main/java/de/thm/arsnova/security/package-info.java b/src/main/java/de/thm/arsnova/security/package-info.java index 71f273effe762f0e0364c6e6dfca6e39c1090dbf..193d89746abe08c2e005ac92e9f27419f0ebf0ba 100644 --- a/src/main/java/de/thm/arsnova/security/package-info.java +++ b/src/main/java/de/thm/arsnova/security/package-info.java @@ -2,4 +2,4 @@ * @author Paul-Christian Volkmer * */ -package de.thm.arsnova.security; \ No newline at end of file +package de.thm.arsnova.security; diff --git a/src/main/java/de/thm/arsnova/services/FeedbackService.java b/src/main/java/de/thm/arsnova/services/FeedbackService.java index 116578bd586c10be634305471cb012253cf75881..3011fb64e95ab6bbd060f83a33e7c9055fc96509 100644 --- a/src/main/java/de/thm/arsnova/services/FeedbackService.java +++ b/src/main/java/de/thm/arsnova/services/FeedbackService.java @@ -19,7 +19,11 @@ package de.thm.arsnova.services; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import javax.annotation.PostConstruct; @@ -31,8 +35,10 @@ import org.springframework.stereotype.Service; import de.thm.arsnova.FeedbackStorage; import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.Feedback; +import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; import de.thm.arsnova.exceptions.NoContentException; +import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.socket.ARSnovaSocketIOServer; @Service @@ -60,23 +66,58 @@ public class FeedbackService implements IFeedbackService { @PostConstruct public void init() { - feedbackStorage = new FeedbackStorage(databaseDao); + feedbackStorage = new FeedbackStorage(); } @Override @Scheduled(fixedDelay = DEFAULT_SCHEDULER_DELAY) public final void cleanFeedbackVotes() { - feedbackStorage.cleanFeedbackVotes(cleanupFeedbackDelay); + Map<Session, List<User>> deletedFeedbackOfUsersInSession = feedbackStorage.cleanFeedbackVotes(cleanupFeedbackDelay); + /* + * mapping (Session -> Users) is not suitable for web sockets, because we want to sent all affected + * sessions to a single user in one go instead of sending multiple messages for each session. Hence, + * we need the mapping (User -> Sessions) + */ + final Map<User, Set<Session>> affectedSessionsOfUsers = new HashMap<User, Set<Session>>(); + + for (Map.Entry<Session, List<User>> entry : deletedFeedbackOfUsersInSession.entrySet()) { + final Session session = entry.getKey(); + final List<User> users = entry.getValue(); + for (User user : users) { + Set<Session> affectedSessions; + if (affectedSessionsOfUsers.containsKey(user)) { + affectedSessions = affectedSessionsOfUsers.get(user); + } else { + affectedSessions = new HashSet<Session>(); + } + affectedSessions.add(session); + affectedSessionsOfUsers.put(user, affectedSessions); + } + } + // Send feedback reset event to all affected users + for (Map.Entry<User, Set<Session>> entry : affectedSessionsOfUsers.entrySet()) { + final User user = entry.getKey(); + final Set<Session> arsSessions = entry.getValue(); + server.reportDeletedFeedback(user, arsSessions); + } + // For each session that has deleted feedback, send the new feedback to all clients + for (Session session : deletedFeedbackOfUsersInSession.keySet()) { + server.reportUpdatedFeedbackForSession(session); + } } @Override public final Feedback getFeedback(final String keyword) { - return feedbackStorage.getFeedback(keyword); + final Session session = databaseDao.getSessionFromKeyword(keyword); + if (session == null) { + throw new NotFoundException(); + } + return feedbackStorage.getFeedback(session); } @Override public final int getFeedbackCount(final String keyword) { - final Feedback feedback = feedbackStorage.getFeedback(keyword); + final Feedback feedback = this.getFeedback(keyword); final List<Integer> values = feedback.getValues(); return values.get(Feedback.FEEDBACK_FASTER) + values.get(Feedback.FEEDBACK_OK) + values.get(Feedback.FEEDBACK_SLOWER) + values.get(Feedback.FEEDBACK_AWAY); @@ -84,7 +125,11 @@ public class FeedbackService implements IFeedbackService { @Override public final double getAverageFeedback(final String sessionkey) { - final Feedback feedback = feedbackStorage.getFeedback(sessionkey); + final Session session = databaseDao.getSessionFromKeyword(sessionkey); + if (session == null) { + throw new NotFoundException(); + } + final Feedback feedback = feedbackStorage.getFeedback(session); final List<Integer> values = feedback.getValues(); final double count = values.get(Feedback.FEEDBACK_FASTER) + values.get(Feedback.FEEDBACK_OK) + values.get(Feedback.FEEDBACK_SLOWER) + values.get(Feedback.FEEDBACK_AWAY); @@ -104,15 +149,21 @@ public class FeedbackService implements IFeedbackService { @Override public final boolean saveFeedback(final String keyword, final int value, final User user) { - final boolean result = feedbackStorage.saveFeedback(keyword, value, user); - if (result) { - server.reportUpdatedFeedbackForSession(keyword); + final Session session = databaseDao.getSessionFromKeyword(keyword); + if (session == null) { + throw new NotFoundException(); } - return result; + feedbackStorage.saveFeedback(session, value, user); + server.reportUpdatedFeedbackForSession(session); + return true; } @Override public final Integer getMyFeedback(final String keyword, final User user) { - return feedbackStorage.getMyFeedback(keyword, user); + final Session session = databaseDao.getSessionFromKeyword(keyword); + if (session == null) { + throw new NotFoundException(); + } + return feedbackStorage.getMyFeedback(session, user); } } diff --git a/src/main/java/de/thm/arsnova/services/ISessionService.java b/src/main/java/de/thm/arsnova/services/ISessionService.java index 86f823734f4dbf77556a7f6b43dd9efa589bf843..a215b57a24020c7c989db22782ed0171e9923871 100644 --- a/src/main/java/de/thm/arsnova/services/ISessionService.java +++ b/src/main/java/de/thm/arsnova/services/ISessionService.java @@ -25,6 +25,7 @@ import java.util.UUID; import de.thm.arsnova.connector.model.Course; import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.SessionInfo; public interface ISessionService { Session getSession(String keyword); @@ -54,4 +55,8 @@ public interface ISessionService { int getLearningProgress(String sessionkey); SimpleEntry<Integer, Integer> getMyLearningProgress(String sessionkey); + + List<SessionInfo> getMySessionsInfo(); + + List<SessionInfo> getMyVisitedSessionsInfo(); } diff --git a/src/main/java/de/thm/arsnova/services/QuestionService.java b/src/main/java/de/thm/arsnova/services/QuestionService.java index b19217cdaaa12b96d3231048673b4c96d849387c..ee1d5e827fd8f72582341e482cdee32401182358 100644 --- a/src/main/java/de/thm/arsnova/services/QuestionService.java +++ b/src/main/java/de/thm/arsnova/services/QuestionService.java @@ -28,6 +28,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @@ -39,13 +41,15 @@ import de.thm.arsnova.entities.InterposedReadingCount; import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.Session; import de.thm.arsnova.entities.User; +import de.thm.arsnova.events.NewInterposedQuestionEvent; +import de.thm.arsnova.events.NewQuestionEvent; import de.thm.arsnova.exceptions.BadRequestException; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.exceptions.UnauthorizedException; import de.thm.arsnova.socket.ARSnovaSocketIOServer; @Service -public class QuestionService implements IQuestionService { +public class QuestionService implements IQuestionService, ApplicationEventPublisherAware { @Autowired private IDatabaseDao databaseDao; @@ -59,6 +63,8 @@ public class QuestionService implements IQuestionService { @Value("${upload.filesize_b}") private int uploadFileSizeByte; + private ApplicationEventPublisher publisher; + public static final Logger LOGGER = LoggerFactory.getLogger(QuestionService.class); public void setDatabaseDao(final IDatabaseDao databaseDao) { @@ -101,15 +107,16 @@ public class QuestionService implements IQuestionService { } // base64 adds offset to filesize, formular taken from: http://en.wikipedia.org/wiki/Base64#MIME - final int fileSize = (int)((question.getImage().length()-814)/1.37); - if ( fileSize > uploadFileSizeByte ) { - LOGGER.error("Could not save file. File is too large with "+ fileSize + " Byte."); + final int fileSize = (int) ((question.getImage().length()-814)/1.37); + if (fileSize > uploadFileSizeByte) { + LOGGER.error("Could not save file. File is too large with " + fileSize + " Byte."); throw new BadRequestException(); } } final Question result = databaseDao.saveQuestion(session, question); - socketIoServer.reportLecturerQuestionAvailable(result.getSessionKeyword(), result.get_id()); + final NewQuestionEvent event = new NewQuestionEvent(this, result, session); + this.publisher.publishEvent(event); return result; } @@ -121,11 +128,10 @@ public class QuestionService implements IQuestionService { final InterposedQuestion result = databaseDao.saveQuestion(session, question, userService.getCurrentUser()); if (null != result) { - socketIoServer.reportAudienceQuestionAvailable(result.getSessionId(), result.get_id()); - + final NewInterposedQuestionEvent event = new NewInterposedQuestionEvent(this, result, session); + this.publisher.publishEvent(event); return true; } - return false; } @@ -169,7 +175,7 @@ public class QuestionService implements IQuestionService { private Session getSessionWithAuthCheck(final String sessionKeyword) { final User user = userService.getCurrentUser(); final Session session = databaseDao.getSession(sessionKeyword); - if (user == null || session == null || ! session.isCreator(user)) { + if (user == null || session == null || !session.isCreator(user)) { throw new UnauthorizedException(); } return session; @@ -210,7 +216,7 @@ public class QuestionService implements IQuestionService { final User user = userService.getCurrentUser(); final Session session = databaseDao.getSession(question.getSessionKeyword()); - if (user == null || session == null || ! session.isCreator(user)) { + if (user == null || session == null || !session.isCreator(user)) { throw new UnauthorizedException(); } databaseDao.deleteAnswers(question); @@ -551,4 +557,9 @@ public class QuestionService implements IQuestionService { } databaseDao.deleteAllQuestionsAnswers(session); } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { + this.publisher = publisher; + } } diff --git a/src/main/java/de/thm/arsnova/services/SessionService.java b/src/main/java/de/thm/arsnova/services/SessionService.java index a00c4e448ca1776c9e297cb312630e50ee1aab5c..f51a8016e58741e4fe8d6e3606af2877abe1acae 100644 --- a/src/main/java/de/thm/arsnova/services/SessionService.java +++ b/src/main/java/de/thm/arsnova/services/SessionService.java @@ -21,11 +21,8 @@ package de.thm.arsnova.services; import java.io.Serializable; import java.util.AbstractMap.SimpleEntry; -import java.util.ArrayList; import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; @@ -37,6 +34,7 @@ import de.thm.arsnova.connector.model.Course; import de.thm.arsnova.dao.IDatabaseDao; import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.SessionInfo; import de.thm.arsnova.entities.User; import de.thm.arsnova.exceptions.ForbiddenException; import de.thm.arsnova.exceptions.NotFoundException; @@ -45,7 +43,7 @@ import de.thm.arsnova.socket.ARSnovaSocketIOServer; @Service public class SessionService implements ISessionService { - public static class SessionNameComperator implements Comparator<Session>, Serializable { + public static class SessionNameComparator implements Comparator<Session>, Serializable { private static final long serialVersionUID = 1L; @Override @@ -54,7 +52,16 @@ public class SessionService implements ISessionService { } } - public static class SessionShortNameComperator implements Comparator<Session>, Serializable { + public static class SessionInfoNameComparator implements Comparator<SessionInfo>, Serializable { + private static final long serialVersionUID = 1L; + + @Override + public int compare(final SessionInfo session1, final SessionInfo session2) { + return session1.getName().compareToIgnoreCase(session2.getName()); + } + } + + public static class SessionShortNameComparator implements Comparator<Session>, Serializable { private static final long serialVersionUID = 1L; @Override @@ -63,6 +70,15 @@ public class SessionService implements ISessionService { } } + public static class SessionInfoShortNameComparator implements Comparator<SessionInfo>, Serializable { + private static final long serialVersionUID = 1L; + + @Override + public int compare(final SessionInfo session1, final SessionInfo session2) { + return session1.getShortName().compareToIgnoreCase(session2.getShortName()); + } + } + @Autowired private IDatabaseDao databaseDao; @@ -83,15 +99,10 @@ public class SessionService implements ISessionService { public final Session joinSession(final String keyword, final UUID socketId) { /* Socket.IO solution */ - Session session = null; - try { - session = databaseDao.getSession(keyword); - } catch (NotFoundException e) { + Session session = databaseDao.getSessionFromKeyword(keyword); - } if (null == session) { userService.removeUserFromSessionBySocketId(socketId); - return null; } final User user = userService.getUser2SocketId(socketId); @@ -128,38 +139,26 @@ public class SessionService implements ISessionService { throw new ForbiddenException(); } } - if (connectorClient != null && session.isCourseSession()) { final String courseid = session.getCourseId(); if (!connectorClient.getMembership(userService.getCurrentUser().getUsername(), courseid).isMember()) { throw new ForbiddenException(); } } - return session; } @Override @PreAuthorize("isAuthenticated()") public final List<Session> getMySessions() { - final List<Session> mySessions = databaseDao.getMySessions(userService.getCurrentUser()); - if (connectorClient == null) { - return mySessions; - } - - final List<Session> courseSessions = databaseDao.getCourseSessions( - connectorClient.getCourses(userService.getCurrentUser().getUsername()).getCourse() - ); - - final Map<String, Session> allAvailableSessions = new HashMap<String, Session>(); + return databaseDao.getMySessions(userService.getCurrentUser()); + } - for (final Session session : mySessions) { - allAvailableSessions.put(session.get_id(), session); - } - for (final Session session : courseSessions) { - allAvailableSessions.put(session.get_id(), session); - } - return new ArrayList<Session>(allAvailableSessions.values()); + @Override + @PreAuthorize("isAuthenticated()") + public final List<SessionInfo> getMySessionsInfo() { + final User user = userService.getCurrentUser(); + return databaseDao.getMySessionsInfo(user); } @Override @@ -168,6 +167,12 @@ public class SessionService implements ISessionService { return databaseDao.getMyVisitedSessions(userService.getCurrentUser()); } + @Override + @PreAuthorize("isAuthenticated()") + public final List<SessionInfo> getMyVisitedSessionsInfo() { + return databaseDao.getMyVisitedSessionsInfo(userService.getCurrentUser()); + } + @Override @PreAuthorize("isAuthenticated()") public final Session saveSession(final Session session) { @@ -220,6 +225,7 @@ public class SessionService implements ISessionService { if (!session.isCreator(user)) { throw new ForbiddenException(); } + socketIoServer.reportSessionStatus(sessionkey, lock); return databaseDao.lockSession(session, lock); } @@ -248,7 +254,7 @@ public class SessionService implements ISessionService { @Override @PreAuthorize("isAuthenticated()") - public SimpleEntry<Integer,Integer> getMyLearningProgress(final String sessionkey) { + public SimpleEntry<Integer, Integer> getMyLearningProgress(final String sessionkey) { final Session session = databaseDao.getSession(sessionkey); final User user = userService.getCurrentUser(); return databaseDao.getMyLearningProgress(session, user); diff --git a/src/main/java/de/thm/arsnova/services/StatisticsService.java b/src/main/java/de/thm/arsnova/services/StatisticsService.java index 5446fd0d8cc1cfad28c6258ecf355402fb9ca715..19990272a533f97db74d084f02087158e41df9ec 100644 --- a/src/main/java/de/thm/arsnova/services/StatisticsService.java +++ b/src/main/java/de/thm/arsnova/services/StatisticsService.java @@ -10,8 +10,6 @@ import de.thm.arsnova.entities.Statistics; @Service public class StatisticsService implements IStatisticsService { - private static final int DURATION_IN_MILLIS = 3 * 60 * 1000; - @Autowired private IDatabaseDao databaseDao; @@ -23,8 +21,6 @@ public class StatisticsService implements IStatisticsService { @Override public final Statistics getStatistics() { - final long since = System.currentTimeMillis() - DURATION_IN_MILLIS; - final Statistics statistics = new Statistics(); statistics.setOpenSessions(databaseDao.countOpenSessions()); statistics.setClosedSessions(databaseDao.countClosedSessions()); diff --git a/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java b/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java index 262a3ab9ce61e598ca39e0b43cb8359fbc7b401d..c663b35137a257d03463374989034ecb5fc706f0 100644 --- a/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java +++ b/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java @@ -15,6 +15,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; +import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; import com.corundumstudio.socketio.AckRequest; @@ -28,7 +29,13 @@ import com.corundumstudio.socketio.listener.DisconnectListener; import com.corundumstudio.socketio.protocol.Packet; import com.corundumstudio.socketio.protocol.PacketType; +import de.thm.arsnova.entities.InterposedQuestion; +import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.User; +import de.thm.arsnova.events.NewInterposedQuestionEvent; +import de.thm.arsnova.events.NewQuestionEvent; +import de.thm.arsnova.events.NovaEvent; +import de.thm.arsnova.events.NovaEventVisitor; import de.thm.arsnova.exceptions.NoContentException; import de.thm.arsnova.services.IFeedbackService; import de.thm.arsnova.services.ISessionService; @@ -37,7 +44,7 @@ import de.thm.arsnova.socket.message.Feedback; import de.thm.arsnova.socket.message.Session; @Component -public class ARSnovaSocketIOServer { +public class ARSnovaSocketIOServer implements ApplicationListener<NovaEvent>, NovaEventVisitor { @Autowired private IFeedbackService feedbackService; @@ -225,26 +232,32 @@ public class ARSnovaSocketIOServer { this.useSSL = useSSL; } - public void reportDeletedFeedback(final String username, final Set<String> arsSessions) { - final List<UUID> connectionIds = findConnectionIdForUser(username); + public void reportDeletedFeedback(final User user, final Set<de.thm.arsnova.entities.Session> arsSessions) { + final List<UUID> connectionIds = findConnectionIdForUser(user); if (connectionIds.isEmpty()) { return; } + final List<String> keywords = new ArrayList<String>(); + for (final de.thm.arsnova.entities.Session session : arsSessions) { + keywords.add(session.getKeyword()); + } for (final SocketIOClient client : server.getAllClients()) { // Find the client whose feedback has been deleted and send a // message. if (connectionIds.contains(client.getSessionId())) { - client.sendEvent("feedbackReset", arsSessions); + client.sendEvent("feedbackReset", keywords); } } } - private List<UUID> findConnectionIdForUser(final String username) { + private List<UUID> findConnectionIdForUser(final User user) { final List<UUID> result = new ArrayList<UUID>(); for (final Entry<UUID, User> e : userService.socketId2User()) { - if (e.getValue().getUsername().equals(username)) { - result.add(e.getKey()); + final UUID someUsersConnectionId = e.getKey(); + final User someUser = e.getValue(); + if (someUser.equals(user)) { + result.add(someUsersConnectionId); } } return result; @@ -261,16 +274,23 @@ public class ARSnovaSocketIOServer { client.sendEvent("activeUserCountData", sessionService.activeUsers(sessionKey)); final de.thm.arsnova.entities.Feedback fb = feedbackService.getFeedback(sessionKey); client.sendEvent("feedbackData", fb.getValues()); + try { + final long averageFeedback = feedbackService.getAverageFeedbackRounded(sessionKey); + client.sendEvent("feedbackDataRoundedAverage", averageFeedback); + } catch (final NoContentException e) { + final Object object = null; // can't directly use "null". + client.sendEvent("feedbackDataRoundedAverage", object); + } } - public void reportUpdatedFeedbackForSession(final String sessionKey) { - final de.thm.arsnova.entities.Feedback fb = feedbackService.getFeedback(sessionKey); - broadcastInSession(sessionKey, "feedbackData", fb.getValues()); + public void reportUpdatedFeedbackForSession(final de.thm.arsnova.entities.Session session) { + final de.thm.arsnova.entities.Feedback fb = feedbackService.getFeedback(session.getKeyword()); + broadcastInSession(session.getKeyword(), "feedbackData", fb.getValues()); try { - final long averageFeedback = feedbackService.getAverageFeedbackRounded(sessionKey); - broadcastInSession(sessionKey, "feedbackDataRoundedAverage", averageFeedback); + final long averageFeedback = feedbackService.getAverageFeedbackRounded(session.getKeyword()); + broadcastInSession(session.getKeyword(), "feedbackDataRoundedAverage", averageFeedback); } catch (final NoContentException e) { - broadcastInSession(sessionKey, "feedbackDataRoundedAverage", null); + broadcastInSession(session.getKeyword(), "feedbackDataRoundedAverage", null); } } @@ -282,7 +302,7 @@ public class ARSnovaSocketIOServer { } catch (final NoContentException e) { averageFeedback = null; } - final List<UUID> connectionIds = findConnectionIdForUser(user.getUsername()); + final List<UUID> connectionIds = findConnectionIdForUser(user); if (connectionIds.isEmpty()) { return; } @@ -306,14 +326,18 @@ public class ARSnovaSocketIOServer { broadcastInSession(sessionKey, "answersToLecQuestionAvail", lecturerQuestionId); } - public void reportAudienceQuestionAvailable(final String sessionKey, final String audienceQuestionId) { + public void reportAudienceQuestionAvailable(final de.thm.arsnova.entities.Session session, final InterposedQuestion audienceQuestion) { /* TODO role handling implementation, send this only to users with role lecturer */ - broadcastInSession(sessionKey, "audQuestionAvail", audienceQuestionId); + broadcastInSession(session.getKeyword(), "audQuestionAvail", audienceQuestion.get_id()); } - public void reportLecturerQuestionAvailable(final String sessionKey, final String lecturerQuestionId) { + public void reportLecturerQuestionAvailable(final de.thm.arsnova.entities.Session session, final Question lecturerQuestion) { /* TODO role handling implementation, send this only to users with role audience */ - broadcastInSession(sessionKey, "lecQuestionAvail", lecturerQuestionId); + broadcastInSession(session.getKeyword(), "lecQuestionAvail", lecturerQuestion.get_id()); + } + + public void reportSessionStatus(final String sessionKey, final boolean active) { + broadcastInSession(sessionKey, "setSessionActive", active); } public void broadcastInSession(final String sessionKey, final String eventName, final Object data) { @@ -331,4 +355,19 @@ public class ARSnovaSocketIOServer { } } } + + @Override + public void visit(NewQuestionEvent event) { + this.reportLecturerQuestionAvailable(event.getSession(), event.getQuestion()); + } + + @Override + public void visit(NewInterposedQuestionEvent event) { + this.reportAudienceQuestionAvailable(event.getSession(), event.getQuestion()); + } + + @Override + public void onApplicationEvent(NovaEvent event) { + event.accept(this); + } } diff --git a/src/main/java/de/thm/arsnova/web/CacheControlInterceptorHandler.java b/src/main/java/de/thm/arsnova/web/CacheControlInterceptorHandler.java index 45f60100a4ad37d8ae752fbd96841f8f0aced55e..843f63f288fd548bf3496c8bfed996e7a32d3ad6 100644 --- a/src/main/java/de/thm/arsnova/web/CacheControlInterceptorHandler.java +++ b/src/main/java/de/thm/arsnova/web/CacheControlInterceptorHandler.java @@ -32,7 +32,7 @@ public class CacheControlInterceptorHandler extends HandlerInterceptorAdapter { final StringBuilder headerValue = new StringBuilder(); - if(cacheControl.policy().length > 0) { + if (cacheControl.policy().length > 0) { for (final CacheControl.Policy policy : cacheControl.policy()) { if (headerValue.length() > 0) { headerValue.append(", "); @@ -49,7 +49,7 @@ public class CacheControlInterceptorHandler extends HandlerInterceptorAdapter { response.setHeader("cache-control", headerValue.toString()); } - if(cacheControl.maxAge() >= 0) { + if (cacheControl.maxAge() >= 0) { if (headerValue.length() > 0) { headerValue.append(", "); } diff --git a/src/main/resources/log4j-dev.properties b/src/main/resources/log4j-dev.properties index 633b03b68e1e5de2deb37cc5fd5a68656c68bd4b..c8a5268ac4d8bda8d9917488730f0e48ee71fb90 100644 --- a/src/main/resources/log4j-dev.properties +++ b/src/main/resources/log4j-dev.properties @@ -4,7 +4,9 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n -log4j.category.de.thm.arsnova=TRACE +log4j.category.de.thm.arsnova=INFO log4j.category.org.springframework=INFO -log4j.category.com.corundumstudio.socketio=TRACE -log4j.category.com.fourspaces.couchdb=INFO +log4j.category.com.corundumstudio.socketio=INFO +log4j.category.com.corundumstudio.socketio.handler.AuthorizeHandler=ERROR +log4j.category.com.fourspaces.couchdb=OFF +log4j.category.net.sf.json=WARN \ No newline at end of file diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties index 0808e98baa610ea7fb80aa0acd83225bb81f173e..f947a78d49176f34698ce9af626cfe7e31d1fa43 100644 --- a/src/main/resources/log4j.properties +++ b/src/main/resources/log4j.properties @@ -8,5 +8,5 @@ log4j.category.de.thm.arsnova=INFO log4j.category.org.springframework=INFO log4j.category.com.corundumstudio.socketio=INFO log4j.category.com.corundumstudio.socketio.handler.AuthorizeHandler=ERROR -log4j.category.com.fourspaces.couchdb=WARN +log4j.category.com.fourspaces.couchdb=OFF log4j.category.net.sf.json=WARN diff --git a/src/test/java/de/thm/arsnova/dao/NovaViewTest.java b/src/test/java/de/thm/arsnova/dao/NovaViewTest.java index 4b6b28c9fd593709e8b13c35c734f82cf01e3726..2d8d14e6a9de99f38e4e5dc645df7aa1b7b4cf7f 100644 --- a/src/test/java/de/thm/arsnova/dao/NovaViewTest.java +++ b/src/test/java/de/thm/arsnova/dao/NovaViewTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.fail; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.util.Arrays; import org.junit.Test; @@ -108,6 +109,30 @@ public class NovaViewTest { assertEncodedEquals("endkey", "[2]", v2.getQueryString()); } + @Test + public void shouldSupportAddingKeysParameter() { + String[] stringKeys = new String[] { "foo", "bar" }; + String[] numberKeys = new String[] { "123", "456" }; + String[] mixedKeys = new String[] { "foo", "123" }; + String[] arrayKeys = new String[] { "[\"foo\",123]", "[456,\"bar\"]" }; + String[] emptyKeys = new String[0]; + final NovaView v1 = new NovaView(null); + final NovaView v2 = new NovaView(null); + final NovaView v3 = new NovaView(null); + final NovaView v4 = new NovaView(null); + final NovaView v5 = new NovaView(null); + v1.setKeys(Arrays.asList(stringKeys)); + v2.setKeys(Arrays.asList(numberKeys)); + v3.setKeys(Arrays.asList(mixedKeys)); + v4.setKeys(Arrays.asList(arrayKeys)); + v5.setKeys(Arrays.asList(emptyKeys)); + assertEncodedEquals("keys", "[\"foo\",\"bar\"]", v1.getQueryString()); + assertEncodedEquals("keys", "[123,456]", v2.getQueryString()); + assertEncodedEquals("keys", "[\"foo\",123]", v3.getQueryString()); + assertEncodedEquals("keys", "[[\"foo\",123],[456,\"bar\"]]", v4.getQueryString()); + assertEncodedEquals("keys", "[]", v5.getQueryString()); + } + private void assertEncodedEquals(final String key, final String expected, final String actual) { try { assertEquals(key + "=" + URLEncoder.encode(expected, "UTF-8"), actual); diff --git a/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java b/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java index 699ab3a3df9a27e6a7bf54aceedbbd420393f76c..4a48df93f774a2c1f0515f77465962f603eea5ca 100644 --- a/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java +++ b/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java @@ -33,6 +33,7 @@ import de.thm.arsnova.entities.InterposedReadingCount; import de.thm.arsnova.entities.LoggedIn; import de.thm.arsnova.entities.Question; import de.thm.arsnova.entities.Session; +import de.thm.arsnova.entities.SessionInfo; import de.thm.arsnova.entities.User; import de.thm.arsnova.exceptions.ForbiddenException; import de.thm.arsnova.exceptions.NoContentException; @@ -229,11 +230,6 @@ public class StubDatabaseDao implements IDatabaseDao { return null; } - @Override - public int countActiveUsers(long since) { - return stubUsers.size(); - } - @Override public List<Answer> getMyAnswers(User user, String sessionKey) { return new ArrayList<Answer>(); @@ -257,12 +253,6 @@ public class StubDatabaseDao implements IDatabaseDao { return null; } - @Override - public void vote(User user, String menu) { - // TODO Auto-generated method stub - - } - @Override public int countAnswers() { // TODO Auto-generated method stub @@ -370,12 +360,6 @@ public class StubDatabaseDao implements IDatabaseDao { return null; } - @Override - public List<String> getActiveUsers(int timeDifference) { - // TODO Auto-generated method stub - return null; - } - @Override public Session updateSession(Session session) { // TODO Auto-generated method stub @@ -535,4 +519,16 @@ public class StubDatabaseDao implements IDatabaseDao { // TODO Auto-generated method stub return null; } + + @Override + public List<SessionInfo> getMySessionsInfo(User user) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List<SessionInfo> getMyVisitedSessionsInfo(User currentUser) { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/test/java/de/thm/arsnova/services/SessionServiceTest.java b/src/test/java/de/thm/arsnova/services/SessionServiceTest.java index d3a7211408197d4f070393ba5464ea86538d369b..8fe9b5725240bc63321145b90e97d62faff0f5db 100644 --- a/src/test/java/de/thm/arsnova/services/SessionServiceTest.java +++ b/src/test/java/de/thm/arsnova/services/SessionServiceTest.java @@ -208,7 +208,7 @@ public class SessionServiceTest { sessionB.setName("TestSessionB"); sessionB.setShortName("TSB"); - final Comparator<Session> comp = new SessionService.SessionNameComperator(); + final Comparator<Session> comp = new SessionService.SessionNameComparator(); assertTrue(comp.compare(sessionA, sessionB) < 0); } @@ -222,7 +222,7 @@ public class SessionServiceTest { sessionB.setName("TestSessionB"); sessionB.setShortName("TSB"); - final Comparator<Session> comp = new SessionService.SessionShortNameComperator(); + final Comparator<Session> comp = new SessionService.SessionShortNameComparator(); assertTrue(comp.compare(sessionA, sessionB) < 0); }