From e9160e808bb2bfcd9a894cf5cddfa5447557ee73 Mon Sep 17 00:00:00 2001
From: agrt56 <andreas.gaertner@mni.thm.de>
Date: Wed, 11 Mar 2015 14:25:28 +0100
Subject: [PATCH] Added implementation for delayed pi-round change. Task #15346

The delayed pi-round change can now be triggered via a simple api call.
After the api call a timed task will be started, which executes the
necessary steps for a round change.
---
 .../LecturerQuestionController.java           | 13 ++++
 .../java/de/thm/arsnova/dao/CacheBuster.java  |  8 +++
 .../domain/LearningProgressFactory.java       |  8 +++
 .../de/thm/arsnova/entities/Question.java     | 28 ++++++++
 .../thm/arsnova/events/NovaEventVisitor.java  |  4 ++
 .../events/PiRoundDelayedStartEvent.java      | 71 +++++++++++++++++++
 .../thm/arsnova/events/PiRoundEndEvent.java   | 43 +++++++++++
 .../arsnova/services/IQuestionService.java    |  8 +++
 .../thm/arsnova/services/QuestionService.java | 70 +++++++++++++++++-
 .../arsnova/socket/ARSnovaSocketIOServer.java | 18 +++++
 10 files changed, 270 insertions(+), 1 deletion(-)
 create mode 100644 src/main/java/de/thm/arsnova/events/PiRoundDelayedStartEvent.java
 create mode 100644 src/main/java/de/thm/arsnova/events/PiRoundEndEvent.java

diff --git a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java
index 07f17995a..cf15ea470 100644
--- a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java
+++ b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java
@@ -84,6 +84,19 @@ public class LecturerQuestionController extends AbstractController {
 		}
 	}
 
+	@RequestMapping(value = "/{questionId}/startNewPiRound", method = RequestMethod.GET)
+	public void startSecondPiRound(
+			@PathVariable final String questionId,
+			@RequestParam(value = "time", defaultValue = "0", required = false) final int time
+			) {
+
+		if(time == 0) {
+			questionService.startNewPiRound(questionId, null);
+		} else {
+			questionService.startNewPiRoundDelayed(questionId, time);
+		}
+	}
+
 	@RequestMapping(value = "/{questionId}/publish", method = RequestMethod.POST)
 	public void publishQuestion(
 			@PathVariable final String questionId,
diff --git a/src/main/java/de/thm/arsnova/dao/CacheBuster.java b/src/main/java/de/thm/arsnova/dao/CacheBuster.java
index 5d39e3997..d96e40945 100644
--- a/src/main/java/de/thm/arsnova/dao/CacheBuster.java
+++ b/src/main/java/de/thm/arsnova/dao/CacheBuster.java
@@ -33,6 +33,8 @@ import de.thm.arsnova.events.NewFeedbackEvent;
 import de.thm.arsnova.events.NewInterposedQuestionEvent;
 import de.thm.arsnova.events.NewQuestionEvent;
 import de.thm.arsnova.events.NovaEventVisitor;
+import de.thm.arsnova.events.PiRoundDelayedStartEvent;
+import de.thm.arsnova.events.PiRoundEndEvent;
 import de.thm.arsnova.events.StatusSessionEvent;
 
 /**
@@ -81,4 +83,10 @@ public class CacheBuster implements ICacheBuster, NovaEventVisitor {
 
 	@Override
 	public void visit(ChangeLearningProgress changeLearningProgress) {}
+
+	@Override
+	public void visit(PiRoundDelayedStartEvent piRoundDelayedStartEvent) {}
+
+	@Override
+	public void visit(PiRoundEndEvent piRoundEndEvent) {}
 }
diff --git a/src/main/java/de/thm/arsnova/domain/LearningProgressFactory.java b/src/main/java/de/thm/arsnova/domain/LearningProgressFactory.java
index d2936f57a..23432273b 100644
--- a/src/main/java/de/thm/arsnova/domain/LearningProgressFactory.java
+++ b/src/main/java/de/thm/arsnova/domain/LearningProgressFactory.java
@@ -37,6 +37,8 @@ import de.thm.arsnova.events.NewFeedbackEvent;
 import de.thm.arsnova.events.NewInterposedQuestionEvent;
 import de.thm.arsnova.events.NewQuestionEvent;
 import de.thm.arsnova.events.NovaEventVisitor;
+import de.thm.arsnova.events.PiRoundDelayedStartEvent;
+import de.thm.arsnova.events.PiRoundEndEvent;
 import de.thm.arsnova.events.StatusSessionEvent;
 
 @Component
@@ -121,4 +123,10 @@ public class LearningProgressFactory implements NovaEventVisitor, ILearningProgr
 		this.publisher = publisher;
 	}
 
+	@Override
+	public void visit(PiRoundDelayedStartEvent piRoundDelayedStartEvent) {}
+
+	@Override
+	public void visit(PiRoundEndEvent piRoundEndEvent) {}
+
 }
diff --git a/src/main/java/de/thm/arsnova/entities/Question.java b/src/main/java/de/thm/arsnova/entities/Question.java
index 956512a62..393769376 100644
--- a/src/main/java/de/thm/arsnova/entities/Question.java
+++ b/src/main/java/de/thm/arsnova/entities/Question.java
@@ -17,6 +17,7 @@
  */
 package de.thm.arsnova.entities;
 
+import java.util.HashMap;
 import java.util.List;
 
 public class Question {
@@ -38,6 +39,9 @@ public class Question {
 	private int number;
 	private int duration;
 	private int piRound;
+	private long piRoundEndTime;
+	private long piRoundStartTime;
+	private boolean piRoundActive;
 	private boolean showStatistic; // sic
 	private boolean showAnswer;
 	private boolean abstention;
@@ -195,6 +199,30 @@ public class Question {
 		this.piRound = piRound;
 	}
 
+	public long getPiRoundEndTime() {
+		return piRoundEndTime;
+	}
+
+	public void setPiRoundEndTime(long piRoundEndTime) {
+		this.piRoundEndTime = piRoundEndTime;
+	}
+
+	public long getPiRoundStartTime() {
+		return piRoundStartTime;
+	}
+
+	public void setPiRoundStartTime(long piRoundStartTime) {
+		this.piRoundStartTime = piRoundStartTime;
+	}
+
+	public boolean isPiRoundActive() {
+		return piRoundActive;
+	}
+
+	public void setPiRoundActive(boolean piRoundActive) {
+		this.piRoundActive = piRoundActive;
+	}
+
 	public boolean isShowStatistic() {
 		return showStatistic;
 	}
diff --git a/src/main/java/de/thm/arsnova/events/NovaEventVisitor.java b/src/main/java/de/thm/arsnova/events/NovaEventVisitor.java
index c1eb3e68f..f8a2a76d4 100644
--- a/src/main/java/de/thm/arsnova/events/NovaEventVisitor.java
+++ b/src/main/java/de/thm/arsnova/events/NovaEventVisitor.java
@@ -45,4 +45,8 @@ public interface NovaEventVisitor {
 
 	void visit(ChangeLearningProgress changeLearningProgress);
 
+	void visit(PiRoundDelayedStartEvent piRoundDelayedStartEvent);
+
+	void visit(PiRoundEndEvent piRoundEndEvent);
+
 }
diff --git a/src/main/java/de/thm/arsnova/events/PiRoundDelayedStartEvent.java b/src/main/java/de/thm/arsnova/events/PiRoundDelayedStartEvent.java
new file mode 100644
index 000000000..9329cdfa7
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/events/PiRoundDelayedStartEvent.java
@@ -0,0 +1,71 @@
+/*
+ * 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.events;
+
+import java.util.Date;
+import java.util.HashMap;
+
+import de.thm.arsnova.entities.Question;
+import de.thm.arsnova.entities.Session;
+
+public class PiRoundDelayedStartEvent extends SessionEvent {
+
+	private static final long serialVersionUID = 1L;
+
+	private final String questionId;
+
+	private final Long startTime;
+
+	private final Long endTime;
+
+	public PiRoundDelayedStartEvent(Object source, Session session, Question question) {
+		super(source, session);
+		this.questionId = question.get_id();
+		this.startTime = question.getPiRoundStartTime();
+		this.endTime = question.getPiRoundEndTime();
+	}
+
+	@Override
+	public void accept(NovaEventVisitor visitor) {
+		visitor.visit(this);
+	}
+
+	public String getQuestionId() {
+		return questionId;
+	}
+
+	public Long getStartTime() {
+		return startTime;
+	}
+
+	public Long getEndTime() {
+		return endTime;
+	}
+
+	public HashMap<String, String> getPiRoundInformations() {
+		HashMap<String, String> map = new HashMap<String, String>();
+
+		map.put("id", getQuestionId());
+		map.put("endTime", getEndTime().toString());
+		map.put("startTime", getStartTime().toString());
+		map.put("actualTime", String.valueOf(new Date().getTime()));
+
+		return map;
+	}
+
+}
diff --git a/src/main/java/de/thm/arsnova/events/PiRoundEndEvent.java b/src/main/java/de/thm/arsnova/events/PiRoundEndEvent.java
new file mode 100644
index 000000000..3d064b092
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/events/PiRoundEndEvent.java
@@ -0,0 +1,43 @@
+/*
+ * 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.events;
+
+import de.thm.arsnova.entities.Question;
+import de.thm.arsnova.entities.Session;
+
+public class PiRoundEndEvent extends SessionEvent {
+
+	private static final long serialVersionUID = 1L;
+
+	private final String questionId;
+
+	public PiRoundEndEvent(Object source, Session session, Question question) {
+		super(source, session);
+		questionId = question.get_id();
+	}
+
+	@Override
+	public void accept(NovaEventVisitor visitor) {
+		visitor.visit(this);
+	}
+
+	public String getQuestionId() {
+		return questionId;
+	}
+
+}
diff --git a/src/main/java/de/thm/arsnova/services/IQuestionService.java b/src/main/java/de/thm/arsnova/services/IQuestionService.java
index 457b9fc7b..8b440166a 100644
--- a/src/main/java/de/thm/arsnova/services/IQuestionService.java
+++ b/src/main/java/de/thm/arsnova/services/IQuestionService.java
@@ -41,6 +41,12 @@ public interface IQuestionService {
 
 	void deleteAllQuestions(String sessionKeyword);
 
+	void startNewPiRound(String questionId, User user);
+
+	void startNewPiRoundDelayed(String questionId, int time);
+
+	public void cancelDelayedPiRoundChange(final String questionId);
+
 	List<String> getUnAnsweredQuestionIds(String sessionKey);
 
 	Answer getMyAnswer(String questionId);
@@ -69,6 +75,8 @@ public interface IQuestionService {
 
 	Question update(Question question);
 
+	Question update(Question question, User user);
+
 	void deleteAnswers(String questionId);
 
 	Answer saveAnswer(String questionId, de.thm.arsnova.entities.transport.Answer answer);
diff --git a/src/main/java/de/thm/arsnova/services/QuestionService.java b/src/main/java/de/thm/arsnova/services/QuestionService.java
index 9db7d05d5..535bd5a33 100644
--- a/src/main/java/de/thm/arsnova/services/QuestionService.java
+++ b/src/main/java/de/thm/arsnova/services/QuestionService.java
@@ -21,9 +21,12 @@ import java.util.AbstractMap;
 import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
 
 import de.thm.arsnova.exceptions.ForbiddenException;
 
@@ -53,6 +56,8 @@ import de.thm.arsnova.events.DeleteQuestionEvent;
 import de.thm.arsnova.events.NewAnswerEvent;
 import de.thm.arsnova.events.NewInterposedQuestionEvent;
 import de.thm.arsnova.events.NewQuestionEvent;
+import de.thm.arsnova.events.PiRoundDelayedStartEvent;
+import de.thm.arsnova.events.PiRoundEndEvent;
 import de.thm.arsnova.exceptions.BadRequestException;
 import de.thm.arsnova.exceptions.NotFoundException;
 import de.thm.arsnova.exceptions.UnauthorizedException;
@@ -73,6 +78,8 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 
 	public static final Logger LOGGER = LoggerFactory.getLogger(QuestionService.class);
 
+	private HashMap<String, Timer> timerList = new HashMap<String, Timer>();
+
 	public void setDatabaseDao(final IDatabaseDao databaseDao) {
 		this.databaseDao = databaseDao;
 	}
@@ -190,6 +197,61 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 		this.publisher.publishEvent(event);
 	}
 
+	public void startNewPiRound(final String questionId, User user) {
+		if(null == user) {
+			user = userService.getCurrentUser();
+		}
+
+		cancelDelayedPiRoundChange(questionId);
+
+		final Question question = databaseDao.getQuestion(questionId);
+		final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword());
+
+		question.setPiRound(question.getPiRound() + 1);
+		question.setActive(false);
+		update(question, user);
+
+		this.publisher.publishEvent(new PiRoundEndEvent(this, session, question));
+	}
+
+	@Override
+	@PreAuthorize("isAuthenticated() and hasPermission(#questionId, 'question', 'owner')")
+	public void startNewPiRoundDelayed(final String questionId, final int time) {
+		final Timer timer = new Timer();
+		final Date date = new Date();
+		final Date endDate = new Date(date.getTime() + (time * 1000));
+		final IQuestionService questionService = this;
+		final User user = userService.getCurrentUser();
+		final Question question = databaseDao.getQuestion(questionId);
+		final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword());
+
+		cancelDelayedPiRoundChange(questionId);
+
+		timer.schedule(new TimerTask() {
+			public void run() {
+				questionService.startNewPiRound(questionId, user);
+			}
+		}, endDate);
+
+		timerList.put(questionId, timer);
+		question.setPiRoundActive(true);
+		question.setPiRoundStartTime(date.getTime());
+		question.setPiRoundEndTime(endDate.getTime());
+		update(question);
+
+		this.publisher.publishEvent(new PiRoundDelayedStartEvent(this, session, question));
+	}
+
+	public void cancelDelayedPiRoundChange(final String questionId) {
+		Timer timer = timerList.get(questionId);
+
+		if(null != timer) {
+			timer.cancel();
+			timerList.remove(questionId);
+			timer.purge();
+		}
+	}
+
 	private Session getSessionWithAuthCheck(final String sessionKeyword) {
 		final User user = userService.getCurrentUser();
 		final Session session = databaseDao.getSessionFromKeyword(sessionKeyword);
@@ -425,12 +487,18 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 	@Override
 	@PreAuthorize("isAuthenticated()")
 	public Question update(final Question question) {
+		final User user = userService.getCurrentUser();
+		return update(question, user);
+	}
+	
+	@Override
+	@PreAuthorize("isAuthenticated()")
+	public Question update(final Question question, User user) {
 		final Question oldQuestion = databaseDao.getQuestion(question.get_id());
 		if (null == oldQuestion) {
 			throw new NotFoundException();
 		}
 
-		final User user = userService.getCurrentUser();
 		final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword());
 		if (user == null || session == null || !session.isCreator(user)) {
 			throw new UnauthorizedException();
diff --git a/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java b/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java
index f8d7b6a58..1764f9f21 100644
--- a/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java
+++ b/src/main/java/de/thm/arsnova/socket/ARSnovaSocketIOServer.java
@@ -62,6 +62,8 @@ import de.thm.arsnova.events.NewFeedbackEvent;
 import de.thm.arsnova.events.NewInterposedQuestionEvent;
 import de.thm.arsnova.events.NewQuestionEvent;
 import de.thm.arsnova.events.NovaEventVisitor;
+import de.thm.arsnova.events.PiRoundDelayedStartEvent;
+import de.thm.arsnova.events.PiRoundEndEvent;
 import de.thm.arsnova.events.StatusSessionEvent;
 import de.thm.arsnova.exceptions.UnauthorizedException;
 import de.thm.arsnova.exceptions.NoContentException;
@@ -474,6 +476,22 @@ public class ARSnovaSocketIOServer implements ARSnovaSocket, NovaEventVisitor {
 		broadcastInSession(sessionKey, "countPreparationQuestionAnswers", questionService.countPreparationQuestionAnswersInternal(sessionKey));
 	}
 
+	@Async
+	@Override
+	public void visit(PiRoundDelayedStartEvent event) {
+		final String sessionKey = event.getSession().getKeyword();
+
+		broadcastInSession(sessionKey, "startDelayedPiRound", event.getPiRoundInformations());
+	}
+
+	@Async
+	@Override
+	public void visit(PiRoundEndEvent event) {
+		final String sessionKey = event.getSession().getKeyword();
+
+		broadcastInSession(sessionKey, "endPiRound", event.getQuestionId());
+	}
+
 	@Override
 	public void visit(DeleteQuestionEvent deleteQuestionEvent) {
 		// TODO Auto-generated method stub
-- 
GitLab