From 72f3f617207e527f66578ee54cc0c29bbfe407b2 Mon Sep 17 00:00:00 2001
From: Christoph Thelen <christoph.thelen@mni.thm.de>
Date: Fri, 6 Feb 2015 20:48:10 +0100
Subject: [PATCH] Set answer properties on the server

---
 .../LecturerQuestionController.java           |   4 +-
 .../de/thm/arsnova/entities/Question.java     |  47 ++++++
 .../arsnova/entities/transport/Answer.java    |  54 +++++++
 .../entities/transport/package-info.java      |   1 +
 .../arsnova/services/IQuestionService.java    |   4 +-
 .../thm/arsnova/services/QuestionService.java |  27 +++-
 .../de/thm/arsnova/entities/QuestionTest.java | 142 ++++++++++++++++++
 7 files changed, 269 insertions(+), 10 deletions(-)
 create mode 100644 src/main/java/de/thm/arsnova/entities/transport/Answer.java
 create mode 100644 src/main/java/de/thm/arsnova/entities/transport/package-info.java
 create mode 100644 src/test/java/de/thm/arsnova/entities/QuestionTest.java

diff --git a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java
index eb656043..3f9c9bd7 100644
--- a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java
+++ b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java
@@ -310,10 +310,10 @@ public class LecturerQuestionController extends AbstractController {
 	@RequestMapping(value = "/{questionId}/answer/", method = RequestMethod.POST)
 	public final Answer saveAnswer(
 			@PathVariable final String questionId,
-			@RequestBody final Answer answer,
+			@RequestBody final de.thm.arsnova.entities.transport.Answer answer,
 			final HttpServletResponse response
 			) {
-		return questionService.saveAnswer(answer);
+		return questionService.saveAnswer(questionId, answer);
 	}
 
 	@RequestMapping(value = "/{questionId}/answer/{answerId}", method = RequestMethod.PUT)
diff --git a/src/main/java/de/thm/arsnova/entities/Question.java b/src/main/java/de/thm/arsnova/entities/Question.java
index 8f268769..7ab74351 100644
--- a/src/main/java/de/thm/arsnova/entities/Question.java
+++ b/src/main/java/de/thm/arsnova/entities/Question.java
@@ -414,4 +414,51 @@ public class Question {
 	public final String toString() {
 		return "Question type '" + type + "': " + subject + ";\n" + text + possibleAnswers;
 	}
+
+	public int calculateValue(Answer answer) {
+		if (answer.isAbstention()) {
+			return 0;
+		} else if (this.questionType.equals("mc")) {
+			return calculateMultipleChoiceValue(answer);
+		} else if (this.questionType.equals("grid")) {
+			return calculateGridValue(answer);
+		} else {
+			return calculateRegularValue(answer);
+		}
+	}
+
+	private int calculateRegularValue(Answer answer) {
+		String answerText = answer.getAnswerText();
+		for (PossibleAnswer p : this.possibleAnswers) {
+			if (answerText.equals(p.getText())) {
+				return p.getValue();
+			}
+		}
+		return 0;
+	}
+
+	private int calculateGridValue(Answer answer) {
+		int value = 0;
+		String[] answers = answer.getAnswerText().split(",");
+		for (int i = 0; i < answers.length; i++) {
+			for (PossibleAnswer p : this.possibleAnswers) {
+				if (answers[i].equals(p.getText())) {
+					value += p.getValue();
+				}
+			}
+		}
+		return value;
+	}
+
+	private int calculateMultipleChoiceValue(Answer answer) {
+		int value = 0;
+		String[] answers = answer.getAnswerText().split(",");
+		for (int i = 0; i < this.possibleAnswers.size() && i < answers.length; i++) {
+			if (answers[i].equals("1")) {
+				PossibleAnswer p = this.possibleAnswers.get(i);
+				value += p.getValue();
+			}
+		}
+		return value;
+	}
 }
diff --git a/src/main/java/de/thm/arsnova/entities/transport/Answer.java b/src/main/java/de/thm/arsnova/entities/transport/Answer.java
new file mode 100644
index 00000000..77d76e76
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/entities/transport/Answer.java
@@ -0,0 +1,54 @@
+/*
+ * 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.entities.transport;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonInclude(JsonInclude.Include.NON_DEFAULT)
+public class Answer {
+
+	private String answerSubject;
+
+	private String answerText;
+
+	private boolean abstention;
+
+	public String getAnswerText() {
+		return answerText;
+	}
+
+	public void setAnswerText(String answerText) {
+		this.answerText = answerText;
+	}
+
+	public String getAnswerSubject() {
+		return answerSubject;
+	}
+
+	public void setAnswerSubject(String answerSubject) {
+		this.answerSubject = answerSubject;
+	}
+
+	public boolean isAbstention() {
+		return abstention;
+	}
+
+	public void setAbstention(boolean abstention) {
+		this.abstention = abstention;
+	}
+}
diff --git a/src/main/java/de/thm/arsnova/entities/transport/package-info.java b/src/main/java/de/thm/arsnova/entities/transport/package-info.java
new file mode 100644
index 00000000..accb5a5a
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/entities/transport/package-info.java
@@ -0,0 +1 @@
+package de.thm.arsnova.entities.transport;
diff --git a/src/main/java/de/thm/arsnova/services/IQuestionService.java b/src/main/java/de/thm/arsnova/services/IQuestionService.java
index a79438cc..31ce043e 100644
--- a/src/main/java/de/thm/arsnova/services/IQuestionService.java
+++ b/src/main/java/de/thm/arsnova/services/IQuestionService.java
@@ -17,8 +17,8 @@
  */
 package de.thm.arsnova.services;
 
-import java.util.List;
 import java.util.AbstractMap.SimpleEntry;
+import java.util.List;
 
 import de.thm.arsnova.entities.Answer;
 import de.thm.arsnova.entities.InterposedQuestion;
@@ -71,7 +71,7 @@ public interface IQuestionService {
 
 	void deleteAnswers(String questionId);
 
-	Answer saveAnswer(Answer answer);
+	Answer saveAnswer(String questionId, de.thm.arsnova.entities.transport.Answer answer);
 
 	Answer updateAnswer(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 66af7839..041493b8 100644
--- a/src/main/java/de/thm/arsnova/services/QuestionService.java
+++ b/src/main/java/de/thm/arsnova/services/QuestionService.java
@@ -18,12 +18,13 @@
 package de.thm.arsnova.services;
 
 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.AbstractMap.SimpleEntry;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -423,20 +424,34 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 
 	@Override
 	@PreAuthorize("isAuthenticated()")
-	public Answer saveAnswer(final Answer answer) {
+	public Answer saveAnswer(final String questionId, final de.thm.arsnova.entities.transport.Answer answer) {
 		final User user = getCurrentUser();
-		final Question question = getQuestion(answer.getQuestionId());
+		final Question question = getQuestion(questionId);
 		if (question == null) {
 			throw new NotFoundException();
 		}
 
+		// rewrite all fields so that no manipulated data gets written
+		// only answerText, answerSubject, and abstention are allowed
+		Answer theAnswer = new Answer();
+		theAnswer.setAnswerSubject(answer.getAnswerSubject());
+		theAnswer.setAnswerText(answer.getAnswerText());
+		theAnswer.setSessionId(question.getSessionId());
+		theAnswer.setUser(user.getUsername());
+		theAnswer.setQuestionId(question.get_id());
+		theAnswer.setTimestamp(new Date().getTime());
+		theAnswer.setQuestionVariant(question.getQuestionVariant());
+		theAnswer.setAbstention(answer.isAbstention());
+		// calculate learning progress value after all properties are set
+		theAnswer.setQuestionValue(question.calculateValue(theAnswer));
+
 		if ("freetext".equals(question.getQuestionType())) {
-			answer.setPiRound(0);
+			theAnswer.setPiRound(0);
 		} else {
-			answer.setPiRound(question.getPiRound());
+			theAnswer.setPiRound(question.getPiRound());
 		}
 
-		final Answer result = databaseDao.saveAnswer(answer, user);
+		final Answer result = databaseDao.saveAnswer(theAnswer, user);
 		final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword());
 		this.publisher.publishEvent(new NewAnswerEvent(this, result, user, question, session));
 
diff --git a/src/test/java/de/thm/arsnova/entities/QuestionTest.java b/src/test/java/de/thm/arsnova/entities/QuestionTest.java
new file mode 100644
index 00000000..d75ab042
--- /dev/null
+++ b/src/test/java/de/thm/arsnova/entities/QuestionTest.java
@@ -0,0 +1,142 @@
+package de.thm.arsnova.entities;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+
+import org.junit.Test;
+
+public class QuestionTest {
+
+	@SuppressWarnings("serial")
+	@Test
+	public void shouldComputeBasedOnCorrectAnswerWithExactMatch() {
+		final PossibleAnswer p1 = new PossibleAnswer();
+		p1.setText("Yes");
+		p1.setCorrect(true);
+		p1.setValue(10);
+		final PossibleAnswer p2 = new PossibleAnswer();
+		p2.setText("No");
+		p2.setCorrect(false);
+		p2.setValue(-10);
+		Question q = new Question();
+		q.setQuestionType("yesno");
+		q.setPossibleAnswers(new ArrayList<PossibleAnswer>() {{
+			add(p1);
+			add(p2);
+		}});
+		Answer answer1 = new Answer();
+		answer1.setAnswerText("Yes");
+		Answer answer2 = new Answer();
+		answer2.setAnswerText("No");
+
+		assertEquals(10, q.calculateValue(answer1));
+		assertEquals(-10, q.calculateValue(answer2));
+	}
+
+	@SuppressWarnings("serial")
+	@Test
+	public void shouldEqualAbstentionToZeroValue() {
+		final PossibleAnswer p1 = new PossibleAnswer();
+		p1.setText("Yes");
+		p1.setCorrect(true);
+		p1.setValue(10);
+		final PossibleAnswer p2 = new PossibleAnswer();
+		p2.setText("No");
+		p2.setCorrect(false);
+		p2.setValue(-10);
+		Question q = new Question();
+		q.setAbstention(true);
+		q.setPossibleAnswers(new ArrayList<PossibleAnswer>() {{
+			add(p1);
+			add(p2);
+		}});
+		Answer answer = new Answer();
+		answer.setAbstention(true);
+
+		assertEquals(0, q.calculateValue(answer));
+	}
+
+	@SuppressWarnings("serial")
+	@Test
+	public void shouldCalculateMultipleChoiceAnswers() {
+		final PossibleAnswer p1 = new PossibleAnswer();
+		p1.setText("Yes");
+		p1.setCorrect(true);
+		p1.setValue(10);
+		final PossibleAnswer p2 = new PossibleAnswer();
+		p2.setText("No");
+		p2.setCorrect(false);
+		p2.setValue(-10);
+		final PossibleAnswer p3 = new PossibleAnswer();
+		p3.setText("Maybe");
+		p3.setCorrect(true);
+		p3.setValue(10);
+		Question q = new Question();
+		q.setQuestionType("mc");
+		q.setPossibleAnswers(new ArrayList<PossibleAnswer>() {{
+			add(p1);
+			add(p2);
+			add(p3);
+		}});
+		Answer answer1 = createAnswerWithText("0,0,0");
+		Answer answer2 = createAnswerWithText("0,0,1");
+		Answer answer3 = createAnswerWithText("0,1,0");
+		Answer answer4 = createAnswerWithText("0,1,1");
+		Answer answer5 = createAnswerWithText("1,0,0");
+		Answer answer6 = createAnswerWithText("1,0,1");
+		Answer answer7 = createAnswerWithText("1,1,0");
+		Answer answer8 = createAnswerWithText("1,1,1");
+
+		assertEquals(0, q.calculateValue(answer1));
+		assertEquals(10, q.calculateValue(answer2));
+		assertEquals(-10, q.calculateValue(answer3));
+		assertEquals(0, q.calculateValue(answer4));
+		assertEquals(10, q.calculateValue(answer5));
+		assertEquals(20, q.calculateValue(answer6));
+		assertEquals(0, q.calculateValue(answer7));
+		assertEquals(10, q.calculateValue(answer8));
+	}
+
+	@SuppressWarnings("serial")
+	@Test
+	public void shouldCalculatePictureQuestionAnswers() {
+		final PossibleAnswer p1 = new PossibleAnswer();
+		p1.setText("0;0");
+		p1.setCorrect(true);
+		p1.setValue(10);
+		final PossibleAnswer p2 = new PossibleAnswer();
+		p2.setText("0;1");
+		p2.setCorrect(false);
+		p2.setValue(-10);
+		final PossibleAnswer p3 = new PossibleAnswer();
+		p3.setText("1;0");
+		p3.setCorrect(true);
+		p3.setValue(10);
+		final PossibleAnswer p4 = new PossibleAnswer();
+		p4.setText("1;1");
+		p4.setCorrect(true);
+		p4.setValue(10);
+		Question q = new Question();
+		q.setQuestionType("grid");
+		q.setPossibleAnswers(new ArrayList<PossibleAnswer>() {{
+			add(p1);
+			add(p2);
+			add(p3);
+			add(p4);
+		}});
+		Answer answer1 = createAnswerWithText("0;0");
+		Answer answer2 = createAnswerWithText("0;1,1;1");
+		Answer answer3 = createAnswerWithText("0;0,1;0,1;1");
+
+		assertEquals(10, q.calculateValue(answer1));
+		assertEquals(0, q.calculateValue(answer2));
+		assertEquals(30, q.calculateValue(answer3));
+	}
+
+	private static Answer createAnswerWithText(String text) {
+		Answer answer = new Answer();
+		answer.setAnswerText(text);
+		return answer;
+	}
+}
-- 
GitLab