summaryrefslogtreecommitdiff
path: root/src/com/p4square/grow/backend
diff options
context:
space:
mode:
authorJesse Morgan <jesse@jesterpm.net>2013-10-20 23:14:51 -0700
committerJesse Morgan <jesse@jesterpm.net>2013-10-20 23:14:51 -0700
commite77c9b418fdfb935ff8e99f10f607a4bbd7e1c8c (patch)
tree9005f8c0e3b18f853fb8093b01f6aad64949bc13 /src/com/p4square/grow/backend
parent5037f4797461649994068d97a8433b6cd793c523 (diff)
First stage of a major refactoring.
Question and Answer can now be serialized and deserialized to/from JSON. As such, I no longer have to pass awkward maps around. As part of this change I have introduced a Provider interface to abstract out loading and persisting these beans. The scoring logic has been completed factored out of SurveyResultsResource and into the various ScoringEngines. Tests have been added for Question, Answer, and the ScoringEngines. A bug has been fixed in computing the value for slider questions. The label identifiers in the circle questions have changed from all lower case to camel case. That is, topleft is now topLeft. Several issues have been corrected in the circle answers where the point values did not match the labels. Testing and code coverage support and reports have been added.
Diffstat (limited to 'src/com/p4square/grow/backend')
-rw-r--r--src/com/p4square/grow/backend/GrowBackend.java22
-rw-r--r--src/com/p4square/grow/backend/db/CassandraKey.java28
-rw-r--r--src/com/p4square/grow/backend/db/CassandraProviderImpl.java42
-rw-r--r--src/com/p4square/grow/backend/resources/Point.java52
-rw-r--r--src/com/p4square/grow/backend/resources/Score.java49
-rw-r--r--src/com/p4square/grow/backend/resources/SurveyResultsResource.java139
-rw-r--r--src/com/p4square/grow/backend/resources/TrainingRecordResource.java2
7 files changed, 125 insertions, 209 deletions
diff --git a/src/com/p4square/grow/backend/GrowBackend.java b/src/com/p4square/grow/backend/GrowBackend.java
index 533cf09..45e0fa2 100644
--- a/src/com/p4square/grow/backend/GrowBackend.java
+++ b/src/com/p4square/grow/backend/GrowBackend.java
@@ -15,6 +15,13 @@ import org.restlet.routing.Router;
import com.p4square.grow.config.Config;
import com.p4square.grow.backend.db.CassandraDatabase;
+import com.p4square.grow.backend.db.CassandraKey;
+import com.p4square.grow.backend.db.CassandraProviderImpl;
+
+import com.p4square.grow.model.Question;
+
+import com.p4square.grow.provider.Provider;
+import com.p4square.grow.provider.QuestionProvider;
import com.p4square.grow.backend.resources.AccountResource;
import com.p4square.grow.backend.resources.BannerResource;
@@ -29,11 +36,15 @@ import com.p4square.grow.backend.resources.TrainingResource;
* @author Jesse Morgan <jesse@jesterpm.net>
*/
public class GrowBackend extends Application {
+ private static final String DEFAULT_COLUMN = "value";
+
private final static Logger LOG = Logger.getLogger(GrowBackend.class);
private final Config mConfig;
private final CassandraDatabase mDatabase;
+ private final Provider<String, Question> mQuestionProvider;
+
public GrowBackend() {
this(new Config());
}
@@ -41,6 +52,13 @@ public class GrowBackend extends Application {
public GrowBackend(Config config) {
mConfig = config;
mDatabase = new CassandraDatabase();
+
+ mQuestionProvider = new QuestionProvider<CassandraKey>(new CassandraProviderImpl<Question>(mDatabase, "strings", Question.class)) {
+ @Override
+ public CassandraKey makeKey(String questionId) {
+ return new CassandraKey("/questions/" + questionId, DEFAULT_COLUMN);
+ }
+ };
}
@Override
@@ -102,6 +120,10 @@ public class GrowBackend extends Application {
return mDatabase;
}
+ public Provider<String, Question> getQuestionProvider() {
+ return mQuestionProvider;
+ }
+
/**
* Stand-alone main for testing.
*/
diff --git a/src/com/p4square/grow/backend/db/CassandraKey.java b/src/com/p4square/grow/backend/db/CassandraKey.java
new file mode 100644
index 0000000..8e23087
--- /dev/null
+++ b/src/com/p4square/grow/backend/db/CassandraKey.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.backend.db;
+
+/**
+ * CassandraKey represents a Cassandra key / column pair.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class CassandraKey {
+ private final String mId;
+ private final String mColumn;
+
+ public CassandraKey(String id, String column) {
+ mId = id;
+ mColumn = column;
+ }
+
+ public String getId() {
+ return mId;
+ }
+
+ public String getColumn() {
+ return mColumn;
+ }
+}
diff --git a/src/com/p4square/grow/backend/db/CassandraProviderImpl.java b/src/com/p4square/grow/backend/db/CassandraProviderImpl.java
new file mode 100644
index 0000000..fb6e34e
--- /dev/null
+++ b/src/com/p4square/grow/backend/db/CassandraProviderImpl.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.backend.db;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.map.DeserializationConfig;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+
+import com.p4square.grow.provider.JsonEncodedProvider;
+
+/**
+ * Provider implementation backed by a Cassandra ColumnFamily.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class CassandraProviderImpl<V> extends JsonEncodedProvider<CassandraKey, V> {
+ private final CassandraDatabase mDb;
+ private final String mColumnFamily;
+
+ public CassandraProviderImpl(CassandraDatabase db, String columnFamily, Class<V> clazz) {
+ super(clazz);
+
+ mDb = db;
+ mColumnFamily = columnFamily;
+ }
+
+ @Override
+ public V get(CassandraKey key) throws IOException {
+ String blob = mDb.getKey(mColumnFamily, key.getId(), key.getColumn());
+ return decode(blob);
+ }
+
+ @Override
+ public void put(CassandraKey key, V obj) throws IOException {
+ String blob = encode(obj);
+ mDb.putKey(mColumnFamily, key.getId(), key.getColumn(), blob);
+ }
+}
diff --git a/src/com/p4square/grow/backend/resources/Point.java b/src/com/p4square/grow/backend/resources/Point.java
deleted file mode 100644
index e1b15a8..0000000
--- a/src/com/p4square/grow/backend/resources/Point.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2013 Jesse Morgan
- */
-
-package com.p4square.grow.backend.resources;
-
-/**
- * Simple double based point class.
- *
- * @author Jesse Morgan <jesse@jesterpm.net>
- */
-class Point {
- public static Point valueOf(String str) {
- final int comma = str.indexOf(',');
- if (comma == -1) {
- throw new IllegalArgumentException("Malformed point string");
- }
-
- final String sX = str.substring(0, comma);
- final String sY = str.substring(comma + 1);
-
- return new Point(Double.valueOf(sX), Double.valueOf(sY));
- }
-
- private final double mX;
- private final double mY;
-
- public Point(double x, double y) {
- mX = x;
- mY = y;
- }
-
- public double distance(Point other) {
- final double dx = mX - other.mX;
- final double dy = mY - other.mY;
-
- return Math.sqrt(dx*dx + dy*dy);
- }
-
- public double getX() {
- return mX;
- }
-
- public double getY() {
- return mY;
- }
-
- @Override
- public String toString() {
- return String.format("%.2f,%.2f", mX, mY);
- }
-}
diff --git a/src/com/p4square/grow/backend/resources/Score.java b/src/com/p4square/grow/backend/resources/Score.java
deleted file mode 100644
index 6f52c02..0000000
--- a/src/com/p4square/grow/backend/resources/Score.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2013 Jesse Morgan
- */
-
-package com.p4square.grow.backend.resources;
-
-/**
- * Simple structure containing a score's sum and count.
- *
- * @author Jesse Morgan <jesse@jesterpm.net>
- */
-class Score {
- /**
- * Return the integer value for the given Score String.
- */
- public static int numericScore(String score) {
- if ("teacher".equals(score)) {
- return 4;
- } else if ("disciple".equals(score)) {
- return 3;
- } else if ("believer".equals(score)) {
- return 2;
- } else {
- return 1;
- }
- }
-
- double sum;
- int count;
-
- @Override
- public String toString() {
- final double score = sum / count;
-
- if (score >= 4) {
- return "teacher";
-
- } else if (score >= 3) {
- return "disciple";
-
- } else if (score >= 2) {
- return "believer";
-
- } else {
- return "seeker";
- }
- }
-
-}
diff --git a/src/com/p4square/grow/backend/resources/SurveyResultsResource.java b/src/com/p4square/grow/backend/resources/SurveyResultsResource.java
index f0bb2aa..91d4d0f 100644
--- a/src/com/p4square/grow/backend/resources/SurveyResultsResource.java
+++ b/src/com/p4square/grow/backend/resources/SurveyResultsResource.java
@@ -14,16 +14,20 @@ import org.codehaus.jackson.map.ObjectMapper;
import org.restlet.data.MediaType;
import org.restlet.data.Status;
-import org.restlet.resource.ServerResource;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
+import org.restlet.resource.ServerResource;
import org.apache.log4j.Logger;
-import com.p4square.grow.model.Answer;
-import com.p4square.grow.model.Question;
import com.p4square.grow.backend.GrowBackend;
import com.p4square.grow.backend.db.CassandraDatabase;
+import com.p4square.grow.model.Answer;
+import com.p4square.grow.model.Question;
+import com.p4square.grow.model.RecordedAnswer;
+import com.p4square.grow.model.Score;
+import com.p4square.grow.provider.Provider;
+
/**
* Store the user's answers to the assessment and generate their score.
@@ -31,15 +35,16 @@ import com.p4square.grow.backend.db.CassandraDatabase;
* @author Jesse Morgan <jesse@jesterpm.net>
*/
public class SurveyResultsResource extends ServerResource {
- private final static Logger cLog = Logger.getLogger(SurveyResultsResource.class);
+ private static final Logger LOG = Logger.getLogger(SurveyResultsResource.class);
- private final static ObjectMapper MAPPER = new ObjectMapper();
+ private static final ObjectMapper MAPPER = new ObjectMapper();
static enum RequestType {
ASSESSMENT, ANSWER
}
private CassandraDatabase mDb;
+ private Provider<String, Question> mQuestionProvider;
private RequestType mRequestType;
private String mUserId;
@@ -51,6 +56,7 @@ public class SurveyResultsResource extends ServerResource {
final GrowBackend backend = (GrowBackend) getApplication();
mDb = backend.getDatabase();
+ mQuestionProvider = backend.getQuestionProvider();
mUserId = getAttribute("userId");
mQuestionId = getAttribute("questionId");
@@ -105,7 +111,7 @@ public class SurveyResultsResource extends ServerResource {
success = true;
} catch (Exception e) {
- cLog.warn("Caught exception putting answer: " + e.getMessage(), e);
+ LOG.warn("Caught exception putting answer: " + e.getMessage(), e);
}
break;
@@ -146,18 +152,30 @@ public class SurveyResultsResource extends ServerResource {
continue;
}
- final String questionId = c.getName();
- final String answerId = c.getStringValue();
- if (!scoringDone) {
- scoringDone = !scoreQuestion(score, questionId, answerId);
+ try {
+ Question question = mQuestionProvider.get(c.getName());
+ RecordedAnswer userAnswer = MAPPER.readValue(c.getStringValue(), RecordedAnswer.class);
+
+ if (question == null) {
+ LOG.warn("Answer for unknown question: " + c.getName());
+ continue;
+ }
+
+ LOG.error("Scoring questionId: " + c.getName());
+ scoringDone = !question.scoreAnswer(score, userAnswer);
+
+ } catch (Exception e) {
+ LOG.error("Failed to score question: {userid: \"" + mUserId +
+ "\", questionid:\"" + c.getName() +
+ "\", userAnswer:\"" + c.getStringValue() + "\"}", e);
}
totalAnswers++;
}
- sb.append(", \"score\":" + score.sum / score.count);
- sb.append(", \"sum\":" + score.sum);
- sb.append(", \"count\":" + score.count);
+ sb.append(", \"score\":" + score.getScore());
+ sb.append(", \"sum\":" + score.getSum());
+ sb.append(", \"count\":" + score.getCount());
sb.append(", \"totalAnswers\":" + totalAnswers);
sb.append(", \"result\":\"" + score.toString() + "\"");
}
@@ -170,99 +188,4 @@ public class SurveyResultsResource extends ServerResource {
return summary;
}
-
- private boolean scoreQuestion(final Score score, final String questionId,
- final String answerJson) {
-
- final String data = mDb.getKey("strings", "/questions/" + questionId);
-
- try {
- final Map<?,?> questionMap = MAPPER.readValue(data, Map.class);
- final Map<?,?> answerMap = MAPPER.readValue(answerJson, Map.class);
- final Question question = new Question((Map<String, Object>) questionMap);
- final String answerId = (String) answerMap.get("answerId");
-
- switch (question.getType()) {
- case TEXT:
- case IMAGE:
- final Answer answer = question.getAnswers().get(answerId);
- if (answer == null) {
- cLog.warn("Got unknown answer " + answerId
- + " for question " + questionId);
- } else {
- if (!scoreAnswer(score, answer)) {
- return false; // Quit scoring
- }
- }
- break;
-
- case SLIDER:
- score.sum += Double.valueOf(answerId) * 4 + 1;
- score.count++;
- break;
-
- case CIRCLE:
- case QUAD:
- scoreQuad(score, question, answerId);
- break;
- }
-
- } catch (Exception e) {
- cLog.error("Exception parsing question id " + questionId, e);
- }
-
- return true;
- }
-
- private boolean scoreAnswer(final Score score, final Answer answer) {
- switch (answer.getType()) {
- case TRUMP:
- score.sum = answer.getScoreFactor();
- score.count = 1;
- return false; // Quit scoring.
-
- case AVERAGE:
- score.sum += answer.getScoreFactor();
- score.count++;
- break;
-
- case NONE:
- break;
- }
-
- return true; // Continue scoring
- }
-
- private boolean scoreQuad(final Score score, final Question question,
- final String answerId) {
-
- Point[] answers = new Point[question.getAnswers().size()];
- {
- int i = 0;
- for (String answer : question.getAnswers().keySet()) {
- answers[i++] = Point.valueOf(answer);
- }
- }
-
- Point userAnswer = Point.valueOf(answerId);
-
- double minDistance = Double.MAX_VALUE;
- int answerIndex = 0;
- for (int i = 0; i < answers.length; i++) {
- final double distance = userAnswer.distance(answers[i]);
- if (distance < minDistance) {
- minDistance = distance;
- answerIndex = i;
- }
- }
-
- cLog.debug("Quad " + question.getId() + ": Got answer "
- + answers[answerIndex].toString() + " for user point " + answerId);
-
- final Answer answer = question.getAnswers().get(answers[answerIndex].toString());
- score.sum += answer.getScoreFactor();
- score.count++;
-
- return true; // Continue scoring
- }
}
diff --git a/src/com/p4square/grow/backend/resources/TrainingRecordResource.java b/src/com/p4square/grow/backend/resources/TrainingRecordResource.java
index 009d0fe..6de9507 100644
--- a/src/com/p4square/grow/backend/resources/TrainingRecordResource.java
+++ b/src/com/p4square/grow/backend/resources/TrainingRecordResource.java
@@ -27,6 +27,8 @@ import org.apache.log4j.Logger;
import com.p4square.grow.backend.GrowBackend;
import com.p4square.grow.backend.db.CassandraDatabase;
+import com.p4square.grow.model.Score;
+
/**
*
* @author Jesse Morgan <jesse@jesterpm.net>