diff --git a/src/main/java/de/thm/arsnova/PaginationListDecorator.java b/src/main/java/de/thm/arsnova/PaginationListDecorator.java new file mode 100644 index 0000000000000000000000000000000000000000..9dd4f092b5f89632f2eaafa01e126b42aec26d98 --- /dev/null +++ b/src/main/java/de/thm/arsnova/PaginationListDecorator.java @@ -0,0 +1,214 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2015 The ARSnova Team + * + * ARSnova Backend 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 Backend 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; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * Extends {@link List}s with the ability to paginate. + * + * @param <T> type of the List items + */ +public class PaginationListDecorator<T> implements List<T> { + private final List<T> list; + private List<T> subList; + private int offset; + private int limit; + + public PaginationListDecorator(final List<T> list, final int offset, final int limit) { + this.list = list; + this.offset = offset; + this.limit = limit; + checkRange(); + subList = list.subList(this.offset, this.offset + this.limit); + } + + private void checkRange() { + if (offset < 0) { + offset = 0; + } + if (limit <= 0 || limit > list.size() - offset) { + limit = list.size(); + } + } + + /** + * @return the original (not paginated) List + */ + public List<T> getList() { + return list; + } + + /** + * @return the number of skipped items + */ + public int getOffset() { + return offset; + } + + /** + * @param offset the number of items to be skipped + */ + public void setOffset(final int offset) { + this.offset = offset; + checkRange(); + subList = list.subList(this.offset, this.offset + this.limit); + } + + /** + * @return the size limit of the paginated list + */ + public int getLimit() { + return limit; + } + + /** + * @param limit the size limit for the resulting list + */ + public void setLimit(final int limit) { + this.limit = limit; + checkRange(); + subList = list.subList(this.offset, this.offset + this.limit); + } + + /** + * @return the size of the original (not paginated) List + */ + public int getTotalSize() { + return list.size(); + } + + @Override + public Iterator<T> iterator() { + return subList.iterator(); + } + + @Override + public boolean add(final T e) { + return subList.add(e); + } + + @Override + public void add(final int index, final T element) { + subList.add(index, element); + } + + @Override + public boolean addAll(final Collection<? extends T> c) { + return subList.addAll(c); + } + + @Override + public boolean addAll(final int index, final Collection<? extends T> c) { + return subList.addAll(index, c); + } + + @Override + public void clear() { + subList.clear(); + } + + @Override + public boolean contains(final Object o) { + return subList.contains(o); + } + + @Override + public boolean containsAll(final Collection<?> c) { + return subList.containsAll(c); + } + + @Override + public T get(final int index) { + return subList.get(index); + } + + @Override + public int indexOf(final Object o) { + return subList.indexOf(o); + } + + @Override + public boolean isEmpty() { + return subList.isEmpty(); + } + + @Override + public int lastIndexOf(final Object o) { + return subList.lastIndexOf(o); + } + + @Override + public ListIterator<T> listIterator() { + return subList.listIterator(); + } + + @Override + public ListIterator<T> listIterator(final int index) { + return subList.listIterator(index); + } + + @Override + public boolean remove(final Object o) { + return subList.remove(o); + } + + @Override + public T remove(final int index) { + return subList.remove(index); + } + + @Override + public boolean removeAll(final Collection<?> c) { + return subList.removeAll(c); + } + + @Override + public boolean retainAll(final Collection<?> c) { + return subList.retainAll(c); + } + + @Override + public T set(final int index, final T element) { + return subList.set(index, element); + } + + @Override + public int size() { + return subList.size(); + } + + @Override + public List<T> subList(final int fromIndex, final int toIndex) { + return subList.subList(fromIndex, toIndex); + } + + @Override + public Object[] toArray() { + return subList.toArray(); + } + + @Override + public <A> A[] toArray(final A[] a) { + return subList.toArray(a); + } + +} diff --git a/src/main/java/de/thm/arsnova/aop/RangeAspect.java b/src/main/java/de/thm/arsnova/aop/RangeAspect.java new file mode 100644 index 0000000000000000000000000000000000000000..03bd80fb085317e73b49fdfa431fa2d05106756c --- /dev/null +++ b/src/main/java/de/thm/arsnova/aop/RangeAspect.java @@ -0,0 +1,105 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2015 The ARSnova Team + * + * ARSnova Backend 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 Backend 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.aop; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import de.thm.arsnova.PaginationListDecorator; +import de.thm.arsnova.controller.PaginationController; +import de.thm.arsnova.services.ResponseProviderService; + +/** + * An aspect which parses requests for pagination parameters in a "Range" header and adds a "Content-Range" header to + * the response. It only applies to methods of {@link PaginationController}s annotated with + * {@link de.thm.arsnova.web.Pagination} which return a {@link List}. + */ +@Component +@Aspect +@Profile("!test") +public class RangeAspect { + + @Autowired + private HttpServletRequest request; + + @Autowired + private ResponseProviderService responseProviderService; + + private final Pattern rangePattern = Pattern.compile("^items=([0-9]+)-([0-9]+)?$"); + + private static final Logger logger = LoggerFactory.getLogger(RangeAspect.class); + + /** Sets start and end parameters based on request's range header and sets content range header for the response. + * + * @param controller + * @throws Throwable + */ + @Around("execution(java.util.List+ de.thm.arsnova.controller.*.*(..)) && this(controller) && @annotation(de.thm.arsnova.web.Pagination)") + public Object handlePaginationRange(ProceedingJoinPoint pjp, final PaginationController controller) throws Throwable { + logger.debug("handlePaginationRange"); + String rangeHeader = request.getHeader("Range"); + Matcher matcher = null; + int start = -1; + int end = -1; + + if (rangeHeader != null) { + matcher = rangePattern.matcher(rangeHeader); + } + + if (matcher != null && matcher.matches()) { + start = matcher.group(1) != null ? Integer.valueOf(matcher.group(1)) : -1; + end = matcher.group(2) != null ? Integer.valueOf(matcher.group(2)) : -1; + logger.debug("Pagination: {}-{}", start, end); + } + controller.setRange(start, end); + + List<?> list = (List<?>) pjp.proceed(); + + if (matcher != null && matcher.matches()) { + int totalSize = -1; + if (list instanceof PaginationListDecorator) { + PaginationListDecorator<?> pl = (PaginationListDecorator<?>) list; + totalSize = pl.getTotalSize(); + } + + /* Header format: "items <start>-<end>/<total>" + * + * The value for end is calculated since the result list + * could be shorter than requested. + */ + String rangeStr = String.format("items %d-%d/%d", start, start + list.size() - 1, totalSize); + HttpServletResponse response = responseProviderService.getResponse(); + response.addHeader("Content-Range", rangeStr); + } + + return list; + } +} diff --git a/src/main/java/de/thm/arsnova/controller/AudienceQuestionController.java b/src/main/java/de/thm/arsnova/controller/AudienceQuestionController.java index 2202338ca018ba719237f5251c2eba8b44f6c07c..660c4538134e08bb8686e97853a48d64eae4ac5f 100644 --- a/src/main/java/de/thm/arsnova/controller/AudienceQuestionController.java +++ b/src/main/java/de/thm/arsnova/controller/AudienceQuestionController.java @@ -36,13 +36,14 @@ import de.thm.arsnova.entities.transport.InterposedQuestion; import de.thm.arsnova.exceptions.BadRequestException; import de.thm.arsnova.services.IQuestionService; import de.thm.arsnova.web.DeprecatedApi; +import de.thm.arsnova.web.Pagination; /** * Handles requests related to audience questions, which are also called interposed or feedback questions. */ @RestController @RequestMapping("/audiencequestion") -public class AudienceQuestionController extends AbstractController { +public class AudienceQuestionController extends PaginationController { public static final Logger LOGGER = LoggerFactory.getLogger(AudienceQuestionController.class); @@ -62,8 +63,9 @@ public class AudienceQuestionController extends AbstractController { } @RequestMapping(value = "/", method = RequestMethod.GET) + @Pagination public List<InterposedQuestion> getInterposedQuestions(@RequestParam final String sessionkey) { - return InterposedQuestion.fromList(questionService.getInterposedQuestions(sessionkey)); + return InterposedQuestion.fromList(questionService.getInterposedQuestions(sessionkey, offset, limit)); } @RequestMapping(value = "/{questionId}", method = RequestMethod.GET) diff --git a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java index 59df5aaa872815340e4f142213e4d2a13ea52009..6b7ad834ed5874d45574d176afdec42a3003766e 100644 --- a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java +++ b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java @@ -35,6 +35,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import de.thm.arsnova.PaginationListDecorator; import de.thm.arsnova.entities.Answer; import de.thm.arsnova.entities.Question; import de.thm.arsnova.exceptions.BadRequestException; @@ -43,13 +44,14 @@ import de.thm.arsnova.exceptions.NoContentException; import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.services.IQuestionService; import de.thm.arsnova.web.DeprecatedApi; +import de.thm.arsnova.web.Pagination; /** * Handles requests related to questions teachers are asking their students. */ @RestController @RequestMapping("/lecturerquestion") -public class LecturerQuestionController extends AbstractController { +public class LecturerQuestionController extends PaginationController { public static final Logger LOGGER = LoggerFactory.getLogger(LecturerQuestionController.class); @@ -215,6 +217,7 @@ public class LecturerQuestionController extends AbstractController { } @RequestMapping(value = "/", method = RequestMethod.GET) + @Pagination public List<Question> getSkillQuestions( @RequestParam final String sessionkey, @RequestParam(value = "lecturequestionsonly", defaultValue = "false") final boolean lectureQuestionsOnly, @@ -236,7 +239,8 @@ public class LecturerQuestionController extends AbstractController { response.setStatus(HttpStatus.NO_CONTENT.value()); return null; } - return questions; + + return new PaginationListDecorator<Question>(questions, offset, limit); } @RequestMapping(value = { "/" }, method = RequestMethod.DELETE) @@ -362,16 +366,16 @@ public class LecturerQuestionController extends AbstractController { ) { List<Answer> answers = null; if (allAnswers) { - answers = questionService.getAllAnswers(questionId); + answers = questionService.getAllAnswers(questionId, -1, -1); } else if (null == piRound) { - answers = questionService.getAnswers(questionId); + answers = questionService.getAnswers(questionId, offset, limit); } else { if (piRound < 1 || piRound > 2) { response.setStatus(HttpStatus.BAD_REQUEST.value()); return null; } - answers = questionService.getAnswers(questionId, piRound); + answers = questionService.getAnswers(questionId, piRound, offset, limit); } if (answers == null) { return new ArrayList<Answer>(); @@ -484,8 +488,9 @@ public class LecturerQuestionController extends AbstractController { } @RequestMapping(value = "/{questionId}/freetextanswer/", method = RequestMethod.GET) + @Pagination public List<Answer> getFreetextAnswers(@PathVariable final String questionId) { - return questionService.getFreetextAnswers(questionId); + return questionService.getFreetextAnswers(questionId, offset, limit); } @DeprecatedApi diff --git a/src/main/java/de/thm/arsnova/controller/PaginationController.java b/src/main/java/de/thm/arsnova/controller/PaginationController.java new file mode 100644 index 0000000000000000000000000000000000000000..62d4021d8cd68bab53f107698de2f26deb13f5af --- /dev/null +++ b/src/main/java/de/thm/arsnova/controller/PaginationController.java @@ -0,0 +1,31 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2015 The ARSnova Team + * + * ARSnova Backend 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 Backend 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.controller; + +/** + * Adds pagination properties to controllers. + */ +public abstract class PaginationController extends AbstractController { + protected int offset = -1; + protected int limit = -1; + + public void setRange(int start, int end) { + this.offset = start; + this.limit = end != -1 && start <= end ? end - start + 1 : -1; + } +} diff --git a/src/main/java/de/thm/arsnova/controller/SessionController.java b/src/main/java/de/thm/arsnova/controller/SessionController.java index 4a75665e843c9962e15737b00c51f32cf875ad93..3ef4c0363e61bd653509ead3ed881bcd18fe08dd 100644 --- a/src/main/java/de/thm/arsnova/controller/SessionController.java +++ b/src/main/java/de/thm/arsnova/controller/SessionController.java @@ -50,13 +50,14 @@ import de.thm.arsnova.services.SessionService.SessionInfoShortNameComparator; import de.thm.arsnova.services.SessionService.SessionNameComparator; import de.thm.arsnova.services.SessionService.SessionShortNameComparator; import de.thm.arsnova.web.DeprecatedApi; +import de.thm.arsnova.web.Pagination; /** * Handles requests related to ARSnova sessions. */ @RestController @RequestMapping("/session") -public class SessionController extends AbstractController { +public class SessionController extends PaginationController { public static final Logger LOGGER = LoggerFactory.getLogger(SessionController.class); @@ -117,6 +118,7 @@ public class SessionController extends AbstractController { } @RequestMapping(value = "/", method = RequestMethod.GET) + @Pagination public List<Session> getSessions( @RequestParam(value = "ownedonly", defaultValue = "false") final boolean ownedOnly, @RequestParam(value = "visitedonly", defaultValue = "false") final boolean visitedOnly, @@ -128,9 +130,9 @@ public class SessionController extends AbstractController { /* TODO implement all parameter combinations, implement use of user parameter */ try { if (ownedOnly && !visitedOnly) { - sessions = sessionService.getMySessions(); + sessions = sessionService.getMySessions(offset, limit); } else if (visitedOnly && !ownedOnly) { - sessions = sessionService.getMyVisitedSessions(); + sessions = sessionService.getMyVisitedSessions(offset, limit); } else { response.setStatus(HttpStatus.NOT_IMPLEMENTED.value()); return null; @@ -160,6 +162,7 @@ public class SessionController extends AbstractController { * @return */ @RequestMapping(value = "/", method = RequestMethod.GET, params = "statusonly=true") + @Pagination public List<SessionInfo> getMySessions( @RequestParam(value = "visitedonly", defaultValue = "false") final boolean visitedOnly, @RequestParam(value = "sortby", defaultValue = "name") final String sortby, @@ -167,9 +170,9 @@ public class SessionController extends AbstractController { ) { List<SessionInfo> sessions; if (!visitedOnly) { - sessions = sessionService.getMySessionsInfo(); + sessions = sessionService.getMySessionsInfo(offset, limit); } else { - sessions = sessionService.getMyVisitedSessionsInfo(); + sessions = sessionService.getMyVisitedSessionsInfo(offset, limit); } if (sessions == null || sessions.isEmpty()) { diff --git a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java index fe956fbcf21e1c2150e149e2e44f85b1d42171fe..8afc459c389fe5446433c5950c1d37f3625d0f48 100644 --- a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java +++ b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java @@ -152,8 +152,14 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware } @Override - public List<Session> getMySessions(final User user) { + public List<Session> getMySessions(final User user, final int start, final int limit) { final NovaView view = new NovaView("session/by_creator"); + if (start > 0) { + view.setSkip(start); + } + if (limit > 0) { + view.setLimit(limit); + } view.setStartKeyArray(user.getUsername()); view.setEndKeyArray(user.getUsername(), "{}"); @@ -227,8 +233,8 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware } @Override - public List<SessionInfo> getMySessionsInfo(final User user) { - final List<Session> sessions = this.getMySessions(user); + public List<SessionInfo> getMySessionsInfo(final User user, final int start, final int limit) { + final List<Session> sessions = this.getMySessions(user, start, limit); if (sessions.isEmpty()) { return new ArrayList<SessionInfo>(); } @@ -406,14 +412,18 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware @Override public List<Question> getSkillQuestionsForUsers(final Session session) { String viewName = "skill_question/by_session_for_all_full"; - return getQuestions(new NovaView(viewName), session); + NovaView view = new NovaView(viewName); + + return getQuestions(view, session); } @Cacheable("skillquestions") @Override public List<Question> getSkillQuestionsForTeachers(final Session session) { String viewName = "skill_question/by_session_sorted_by_subject_and_text"; - return getQuestions(new NovaView(viewName), session); + NovaView view = new NovaView(viewName); + + return getQuestions(view, session); } @Override @@ -1010,9 +1020,15 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware } @Override - public List<Answer> getFreetextAnswers(final String questionId) { + public List<Answer> getFreetextAnswers(final String questionId, final int start, final int limit) { final List<Answer> answers = new ArrayList<Answer>(); final NovaView view = new NovaView("skill_question/freetext_answers_full"); + if (start > 0) { + view.setSkip(start); + } + if (limit > 0) { + view.setLimit(limit); + } view.setKey(questionId); final ViewResults results = getDatabase().view(view); if (results.getResults().isEmpty()) { @@ -1142,8 +1158,14 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware } @Override - public List<InterposedQuestion> getInterposedQuestions(final Session session) { + public List<InterposedQuestion> getInterposedQuestions(final Session session, final int start, final int limit) { final NovaView view = new NovaView("interposed_question/by_session_full"); + if (start > 0) { + view.setSkip(start); + } + if (limit > 0) { + view.setLimit(limit); + } view.setKey(session.get_id()); final ViewResults questions = getDatabase().view(view); if (questions == null || questions.isEmpty()) { @@ -1153,8 +1175,14 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware } @Override - public List<InterposedQuestion> getInterposedQuestions(final Session session, final User user) { + public List<InterposedQuestion> getInterposedQuestions(final Session session, final User user, final int start, final int limit) { final NovaView view = new NovaView("interposed_question/by_session_and_creator"); + if (start > 0) { + view.setSkip(start); + } + if (limit > 0) { + view.setLimit(limit); + } view.setKey(session.get_id(), user.getUsername()); final ViewResults questions = getDatabase().view(view); if (questions == null || questions.isEmpty()) { @@ -1276,8 +1304,14 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware } @Override - public List<Session> getMyVisitedSessions(final User user) { + public List<Session> getMyVisitedSessions(final User user, final int start, final int limit) { final NovaView view = new NovaView("logged_in/visited_sessions_by_user"); + if (start > 0) { + view.setSkip(start); + } + if (limit > 0) { + view.setLimit(limit); + } view.setKey(user.getUsername()); final ViewResults sessions = getDatabase().view(view); final List<Session> allSessions = new ArrayList<Session>(); @@ -1336,8 +1370,8 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware } @Override - public List<SessionInfo> getMyVisitedSessionsInfo(final User user) { - List<Session> sessions = this.getMyVisitedSessions(user); + public List<SessionInfo> getMyVisitedSessionsInfo(final User user, final int start, final int limit) { + List<Session> sessions = this.getMyVisitedSessions(user, start, limit); if (sessions.isEmpty()) { return new ArrayList<SessionInfo>(); } @@ -1521,39 +1555,51 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware @Override public List<Question> getLectureQuestionsForUsers(final Session session) { String viewName = "skill_question/lecture_question_by_session_for_all"; - return getQuestions(new NovaView(viewName), session); + NovaView view = new NovaView(viewName); + + return getQuestions(view, session); } @Override public List<Question> getLectureQuestionsForTeachers(final Session session) { String viewName = "skill_question/lecture_question_by_session"; - return getQuestions(new NovaView(viewName), session); + NovaView view = new NovaView(viewName); + + return getQuestions(view, session); } @Cacheable("flashcardquestions") @Override public List<Question> getFlashcardsForUsers(final Session session) { String viewName = "skill_question/flashcard_by_session_for_all"; - return getQuestions(new NovaView(viewName), session); + NovaView view = new NovaView(viewName); + + return getQuestions(view, session); } @Override public List<Question> getFlashcardsForTeachers(final Session session) { String viewName = "skill_question/flashcard_by_session"; - return getQuestions(new NovaView(viewName), session); + NovaView view = new NovaView(viewName); + + return getQuestions(view, session); } @Cacheable("preparationquestions") @Override public List<Question> getPreparationQuestionsForUsers(final Session session) { String viewName = "skill_question/preparation_question_by_session_for_all"; - return getQuestions(new NovaView(viewName), session); + NovaView view = new NovaView(viewName); + + return getQuestions(view, session); } @Override public List<Question> getPreparationQuestionsForTeachers(final Session session) { String viewName = "skill_question/preparation_question_by_session"; - return getQuestions(new NovaView(viewName), session); + NovaView view = new NovaView(viewName); + + return getQuestions(view, session); } private List<Question> getQuestions(final NovaView view, final Session session) { diff --git a/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java b/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java index f910ecbbabf98f169a05432ded496f06ca0be296..3fbfe4231d4cdf6f2a7efcb3c18521d4f6ce133b 100644 --- a/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java +++ b/src/main/java/de/thm/arsnova/dao/IDatabaseDao.java @@ -40,7 +40,7 @@ import de.thm.arsnova.entities.transport.ImportExportSession; public interface IDatabaseDao { Session getSessionFromKeyword(String keyword); - List<Session> getMySessions(User user); + List<Session> getMySessions(User user, final int start, final int limit); List<Session> getPublicPoolSessions(); @@ -97,7 +97,7 @@ public interface IDatabaseDao { int getAbstentionAnswerCount(String questionId); - List<Answer> getFreetextAnswers(String questionId); + List<Answer> getFreetextAnswers(String questionId, final int start, final int limit); List<Answer> getMyAnswers(User me, Session session); @@ -109,15 +109,15 @@ public interface IDatabaseDao { InterposedReadingCount getInterposedReadingCount(Session session, User user); - List<InterposedQuestion> getInterposedQuestions(Session session); + List<InterposedQuestion> getInterposedQuestions(Session session, final int start, final int limit); - List<InterposedQuestion> getInterposedQuestions(Session session, User user); + List<InterposedQuestion> getInterposedQuestions(Session session, User user, final int start, final int limit); InterposedQuestion getInterposedQuestion(String questionId); void markInterposedQuestionAsRead(InterposedQuestion question); - List<Session> getMyVisitedSessions(User user); + List<Session> getMyVisitedSessions(User user, final int start, final int limit); Question updateQuestion(Question question); @@ -189,13 +189,13 @@ public interface IDatabaseDao { CourseScore getLearningProgress(Session session); - List<SessionInfo> getMySessionsInfo(User user); + List<SessionInfo> getMySessionsInfo(User user, final int start, final int limit); List<SessionInfo> getPublicPoolSessionsInfo(); List<SessionInfo> getMyPublicPoolSessionsInfo(final User user); - List<SessionInfo> getMyVisitedSessionsInfo(User currentUser); + List<SessionInfo> getMyVisitedSessionsInfo(User currentUser, final int start, final int limit); void deleteAllPreparationAnswers(Session session); diff --git a/src/main/java/de/thm/arsnova/services/IQuestionService.java b/src/main/java/de/thm/arsnova/services/IQuestionService.java index a21d8424473ec5db4dc2932ee40c86f7908f539b..f1b74157e4a16d47d7a97d44368c761719166421 100644 --- a/src/main/java/de/thm/arsnova/services/IQuestionService.java +++ b/src/main/java/de/thm/arsnova/services/IQuestionService.java @@ -61,17 +61,17 @@ public interface IQuestionService { void readFreetextAnswer(String answerId, User user); - List<Answer> getAnswers(String questionId, int piRound); + List<Answer> getAnswers(String questionId, int piRound, int offset, int limit); - List<Answer> getAnswers(String questionId); + List<Answer> getAnswers(String questionId, int offset, int limit); - List<Answer> getAllAnswers(String questionId); + List<Answer> getAllAnswers(String questionId, int offset, int limit); int getAnswerCount(String questionId); int getAnswerCount(String questionId, int piRound); - List<Answer> getFreetextAnswers(String questionId); + List<Answer> getFreetextAnswers(String questionId, int offset, int limit); List<Answer> getMyAnswers(String sessionKey); @@ -83,7 +83,7 @@ public interface IQuestionService { InterposedReadingCount getInterposedReadingCount(String sessionKey, String username); - List<InterposedQuestion> getInterposedQuestions(String sessionKey); + List<InterposedQuestion> getInterposedQuestions(String sessionKey, int offset, int limit); InterposedQuestion readInterposedQuestion(String questionId); diff --git a/src/main/java/de/thm/arsnova/services/ISessionService.java b/src/main/java/de/thm/arsnova/services/ISessionService.java index ab4a395af089c95b1b10df0dec667112b4601b34..22ccbf90bbe1ad60ffaaa685852c5b0645135986 100644 --- a/src/main/java/de/thm/arsnova/services/ISessionService.java +++ b/src/main/java/de/thm/arsnova/services/ISessionService.java @@ -42,9 +42,9 @@ public interface ISessionService { String generateKeyword(); - List<Session> getMySessions(); + List<Session> getMySessions(int offset, int limit); - List<Session> getMyVisitedSessions(); + List<Session> getMyVisitedSessions(int offset, int limit); int countSessions(List<Course> courses); @@ -64,13 +64,13 @@ public interface ISessionService { LearningProgressValues getMyLearningProgress(String sessionkey, String progressType, String questionVariant); - List<SessionInfo> getMySessionsInfo(); + List<SessionInfo> getMySessionsInfo(int offset, int limit); List<SessionInfo> getPublicPoolSessionsInfo(); List<SessionInfo> getMyPublicPoolSessionsInfo(); - List<SessionInfo> getMyVisitedSessionsInfo(); + List<SessionInfo> getMyVisitedSessionsInfo(int offset, int limit); SessionInfo importSession(ImportExportSession session); diff --git a/src/main/java/de/thm/arsnova/services/QuestionService.java b/src/main/java/de/thm/arsnova/services/QuestionService.java index 5edb106f564d08693781c800055ab96c0015b83a..d11db191076d38d3344d50d48f703b6d881e5228 100644 --- a/src/main/java/de/thm/arsnova/services/QuestionService.java +++ b/src/main/java/de/thm/arsnova/services/QuestionService.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -480,25 +479,25 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override @PreAuthorize("isAuthenticated()") - public List<Answer> getAnswers(final String questionId, final int piRound) { + public List<Answer> getAnswers(final String questionId, final int piRound, final int offset, final int limit) { final Question question = databaseDao.getQuestion(questionId); if (question == null) { throw new NotFoundException(); } return "freetext".equals(question.getQuestionType()) - ? getFreetextAnswers(questionId) + ? getFreetextAnswers(questionId, offset, limit) : databaseDao.getAnswers(question, piRound); } @Override @PreAuthorize("isAuthenticated()") - public List<Answer> getAnswers(final String questionId) { + public List<Answer> getAnswers(final String questionId, final int offset, final int limit) { final Question question = getQuestion(questionId); if (question == null) { throw new NotFoundException(); } if ("freetext".equals(question.getQuestionType())) { - return getFreetextAnswers(questionId); + return getFreetextAnswers(questionId, offset, limit); } else { return databaseDao.getAnswers(question); } @@ -506,13 +505,13 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override @PreAuthorize("isAuthenticated()") - public List<Answer> getAllAnswers(final String questionId) { + public List<Answer> getAllAnswers(final String questionId, final int offset, final int limit) { final Question question = getQuestion(questionId); if (question == null) { throw new NotFoundException(); } if ("freetext".equals(question.getQuestionType())) { - return getFreetextAnswers(questionId); + return getFreetextAnswers(questionId, offset, limit); } else { return databaseDao.getAllAnswers(question); } @@ -564,8 +563,8 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override @PreAuthorize("isAuthenticated()") - public List<Answer> getFreetextAnswers(final String questionId) { - final List<Answer> answers = databaseDao.getFreetextAnswers(questionId); + public List<Answer> getFreetextAnswers(final String questionId, final int offset, final int limit) { + final List<Answer> answers = databaseDao.getFreetextAnswers(questionId, offset, limit); if (answers == null) { throw new NotFoundException(); } @@ -644,13 +643,13 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override @PreAuthorize("isAuthenticated()") - public List<InterposedQuestion> getInterposedQuestions(final String sessionKey) { + public List<InterposedQuestion> getInterposedQuestions(final String sessionKey, final int offset, final int limit) { final Session session = this.getSession(sessionKey); final User user = getCurrentUser(); if (session.isCreator(user)) { - return databaseDao.getInterposedQuestions(session); + return databaseDao.getInterposedQuestions(session, offset, limit); } else { - return databaseDao.getInterposedQuestions(session, user); + return databaseDao.getInterposedQuestions(session, user, offset, limit); } } @@ -1043,7 +1042,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis @Override public String getImage(String questionId, String answerId) { - final List<Answer> answers = getAnswers(questionId); + final List<Answer> answers = getAnswers(questionId, -1, -1); Answer answer = null; for (Answer a : answers) { diff --git a/src/main/java/de/thm/arsnova/services/ResponseProviderService.java b/src/main/java/de/thm/arsnova/services/ResponseProviderService.java new file mode 100644 index 0000000000000000000000000000000000000000..f8cd8a9b05823d1b30cfce211d3192e714f5f6e3 --- /dev/null +++ b/src/main/java/de/thm/arsnova/services/ResponseProviderService.java @@ -0,0 +1,29 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2015 The ARSnova Team + * + * ARSnova Backend 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 Backend 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.services; + +import javax.servlet.http.HttpServletResponse; + +/** + * Allows access to the {@link HttpServletResponse} outside of Controllers. + */ +public interface ResponseProviderService { + public void setResponse(HttpServletResponse response); + + public HttpServletResponse getResponse(); +} diff --git a/src/main/java/de/thm/arsnova/services/ResponseProviderServiceImpl.java b/src/main/java/de/thm/arsnova/services/ResponseProviderServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..d4791011599b9793ae82b8f32417690a49cc4ee8 --- /dev/null +++ b/src/main/java/de/thm/arsnova/services/ResponseProviderServiceImpl.java @@ -0,0 +1,42 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2015 The ARSnova Team + * + * ARSnova Backend 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 Backend 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.services; + +import javax.servlet.http.HttpServletResponse; + +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.stereotype.Service; + +@Service +@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) +public class ResponseProviderServiceImpl implements ResponseProviderService { + + HttpServletResponse response; + + @Override + public void setResponse(HttpServletResponse response) { + this.response = response; + } + + @Override + public HttpServletResponse getResponse() { + return response; + } + +} diff --git a/src/main/java/de/thm/arsnova/services/SessionService.java b/src/main/java/de/thm/arsnova/services/SessionService.java index f9a49bf2fb55819968ed35692de6b853f46d6929..b404910b3fb1efd7fae1b3f0cb8756f3adac7e1e 100644 --- a/src/main/java/de/thm/arsnova/services/SessionService.java +++ b/src/main/java/de/thm/arsnova/services/SessionService.java @@ -187,8 +187,8 @@ public class SessionService implements ISessionService, ApplicationEventPublishe @Override @PreAuthorize("isAuthenticated()") - public List<Session> getMySessions() { - return databaseDao.getMySessions(userService.getCurrentUser()); + public List<Session> getMySessions(final int offset, final int limit) { + return databaseDao.getMySessions(userService.getCurrentUser(), offset, limit); } @Override @@ -205,21 +205,21 @@ public class SessionService implements ISessionService, ApplicationEventPublishe @Override @PreAuthorize("isAuthenticated()") - public List<SessionInfo> getMySessionsInfo() { + public List<SessionInfo> getMySessionsInfo(final int offset, final int limit) { final User user = userService.getCurrentUser(); - return databaseDao.getMySessionsInfo(user); + return databaseDao.getMySessionsInfo(user, offset, limit); } @Override @PreAuthorize("isAuthenticated()") - public List<Session> getMyVisitedSessions() { - return databaseDao.getMyVisitedSessions(userService.getCurrentUser()); + public List<Session> getMyVisitedSessions(final int offset, final int limit) { + return databaseDao.getMyVisitedSessions(userService.getCurrentUser(), offset, limit); } @Override @PreAuthorize("isAuthenticated()") - public List<SessionInfo> getMyVisitedSessionsInfo() { - return databaseDao.getMyVisitedSessionsInfo(userService.getCurrentUser()); + public List<SessionInfo> getMyVisitedSessionsInfo(final int offset, final int limit) { + return databaseDao.getMyVisitedSessionsInfo(userService.getCurrentUser(), offset, limit); } @Override diff --git a/src/main/java/de/thm/arsnova/web/Pagination.java b/src/main/java/de/thm/arsnova/web/Pagination.java new file mode 100644 index 0000000000000000000000000000000000000000..f8f8a98242e8a0d7beba182962fdaa77eac7b934 --- /dev/null +++ b/src/main/java/de/thm/arsnova/web/Pagination.java @@ -0,0 +1,35 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2015 The ARSnova Team + * + * ARSnova Backend 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 Backend 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.web; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks controller methods for pagination. For pagination to work the controller class has to specialize + * {@link de.thm.arsnova.controller.PaginationController} and the return value has to be a List. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +@Documented +public @interface Pagination { + +} diff --git a/src/main/java/de/thm/arsnova/web/ResponseInterceptorHandler.java b/src/main/java/de/thm/arsnova/web/ResponseInterceptorHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..15e157c01a3b2b68814d23c06fbc55c1be4eb977 --- /dev/null +++ b/src/main/java/de/thm/arsnova/web/ResponseInterceptorHandler.java @@ -0,0 +1,50 @@ +/* + * This file is part of ARSnova Backend. + * Copyright (C) 2012-2015 The ARSnova Team + * + * ARSnova Backend 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 Backend 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.web; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import de.thm.arsnova.services.ResponseProviderService; + +/** + * Injects a {@link HttpServletResponse} into {@link ResponseProviderService} to allow access to it outside of + * Controllers. + */ +@Component +public class ResponseInterceptorHandler extends HandlerInterceptorAdapter { + + @Autowired + ResponseProviderService responseProviderService; + + @Override + public boolean preHandle( + HttpServletRequest request, + HttpServletResponse response, + Object handler + ) throws Exception { + responseProviderService.setResponse(response); + + return true; + }; + +} diff --git a/src/main/webapp/WEB-INF/spring/arsnova-servlet.xml b/src/main/webapp/WEB-INF/spring/arsnova-servlet.xml index 2375d536eed0269efab83ea0d46d11e859a13bd9..c9c760e7c93c970c2c33bc6e15566d1762bae87e 100644 --- a/src/main/webapp/WEB-INF/spring/arsnova-servlet.xml +++ b/src/main/webapp/WEB-INF/spring/arsnova-servlet.xml @@ -1,17 +1,19 @@ <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation=" + http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd"> <!-- ARSnova Servlet Context --> - <context:component-scan base-package="de.thm.arsnova.controller,de.thm.arsnova.web" /> + <context:component-scan base-package="de.thm.arsnova.aop,de.thm.arsnova.controller,de.thm.arsnova.web" /> <mvc:annotation-driven content-negotiation-manager="mvcContentNegotiationManager" /> @@ -19,8 +21,11 @@ <mvc:interceptors> <bean class="de.thm.arsnova.web.CacheControlInterceptorHandler" /> <bean class="de.thm.arsnova.web.DeprecatedApiInterceptorHandler" /> + <bean class="de.thm.arsnova.web.ResponseInterceptorHandler" /> </mvc:interceptors> + <aop:aspectj-autoproxy /> + <bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" p:ignoreUnresolvablePlaceholders="false" p:ignoreResourceNotFound="true"> diff --git a/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java b/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java index 32ec1204a3c10734e2ff5d0672b73340161aeac8..7e3972d1660b282be700dee9592016fe6fa4bfce 100644 --- a/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java +++ b/src/test/java/de/thm/arsnova/dao/StubDatabaseDao.java @@ -155,7 +155,7 @@ public class StubDatabaseDao implements IDatabaseDao { } @Override - public List<Session> getMySessions(User user) { + public List<Session> getMySessions(User user, final int start, final int limit) { // TODO Auto-generated method stub return null; } @@ -209,7 +209,7 @@ public class StubDatabaseDao implements IDatabaseDao { } @Override - public List<Answer> getFreetextAnswers(String questionId) { + public List<Answer> getFreetextAnswers(String questionId, final int start, final int limit) { // TODO Auto-generated method stub return null; } @@ -232,7 +232,7 @@ public class StubDatabaseDao implements IDatabaseDao { } @Override - public List<InterposedQuestion> getInterposedQuestions(Session session) { + public List<InterposedQuestion> getInterposedQuestions(Session session, final int start, final int limit) { // TODO Auto-generated method stub return null; } @@ -254,7 +254,7 @@ public class StubDatabaseDao implements IDatabaseDao { } @Override - public List<Session> getMyVisitedSessions(User user) { + public List<Session> getMyVisitedSessions(User user, final int start, final int limit) { // TODO Auto-generated method stub return null; } @@ -445,7 +445,7 @@ public class StubDatabaseDao implements IDatabaseDao { } @Override - public List<InterposedQuestion> getInterposedQuestions(Session session, User user) { + public List<InterposedQuestion> getInterposedQuestions(Session session, User user, final int start, final int limit) { // TODO Auto-generated method stub return null; } @@ -463,13 +463,13 @@ public class StubDatabaseDao implements IDatabaseDao { } @Override - public List<SessionInfo> getMySessionsInfo(User user) { + public List<SessionInfo> getMySessionsInfo(User user, final int start, final int limit) { // TODO Auto-generated method stub return null; } @Override - public List<SessionInfo> getMyVisitedSessionsInfo(User currentUser) { + public List<SessionInfo> getMyVisitedSessionsInfo(User currentUser, final int start, final int limit) { // TODO Auto-generated method stub return null; }