summaryrefslogtreecommitdiff
path: root/src/com/p4square/grow/backend/resources
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/p4square/grow/backend/resources')
-rw-r--r--src/com/p4square/grow/backend/resources/Answer.java57
-rw-r--r--src/com/p4square/grow/backend/resources/Point.java52
-rw-r--r--src/com/p4square/grow/backend/resources/Question.java72
-rw-r--r--src/com/p4square/grow/backend/resources/Score.java34
-rw-r--r--src/com/p4square/grow/backend/resources/SurveyResource.java70
-rw-r--r--src/com/p4square/grow/backend/resources/SurveyResultsResource.java252
-rw-r--r--src/com/p4square/grow/backend/resources/TrainingRecordResource.java155
-rw-r--r--src/com/p4square/grow/backend/resources/TrainingResource.java88
8 files changed, 780 insertions, 0 deletions
diff --git a/src/com/p4square/grow/backend/resources/Answer.java b/src/com/p4square/grow/backend/resources/Answer.java
new file mode 100644
index 0000000..5ba1bce
--- /dev/null
+++ b/src/com/p4square/grow/backend/resources/Answer.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.backend.resources;
+
+import java.util.Map;
+
+/**
+ * This is the model of an assessment question's answer.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+class Answer {
+ public static enum ScoreType {
+ NONE, AVERAGE, TRUMP;
+ }
+
+ private final String mAnswerId;
+ private final String mAnswerText;
+ private final ScoreType mType;
+ private final float mScoreFactor;
+
+ public Answer(final String id, final Map<String, Object> answer) {
+ mAnswerId = id;
+ mAnswerText = (String) answer.get("text");
+ final String typeStr = (String) answer.get("type");
+ if (typeStr == null) {
+ mType = ScoreType.AVERAGE;
+ } else {
+ mType = ScoreType.valueOf(typeStr.toUpperCase());
+ }
+
+ if (mType != ScoreType.NONE) {
+ mScoreFactor = Float.valueOf((String) answer.get("score"));
+ } else {
+ mScoreFactor = 0;
+ }
+
+ }
+
+ public String getId() {
+ return mAnswerId;
+ }
+
+ public String getText() {
+ return mAnswerText;
+ }
+
+ public ScoreType getType() {
+ return mType;
+ }
+
+ public float getScoreFactor() {
+ return mScoreFactor;
+ }
+}
diff --git a/src/com/p4square/grow/backend/resources/Point.java b/src/com/p4square/grow/backend/resources/Point.java
new file mode 100644
index 0000000..e1b15a8
--- /dev/null
+++ b/src/com/p4square/grow/backend/resources/Point.java
@@ -0,0 +1,52 @@
+/*
+ * 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/Question.java b/src/com/p4square/grow/backend/resources/Question.java
new file mode 100644
index 0000000..c53883c
--- /dev/null
+++ b/src/com/p4square/grow/backend/resources/Question.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.backend.resources;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Model of an assessment question.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+class Question {
+ public static enum QuestionType {
+ TEXT, IMAGE, SLIDER, QUAD;
+ }
+
+ private final String mQuestionId;
+ private final QuestionType mType;
+ private final String mQuestionText;
+ private Map<String, Answer> mAnswers;
+
+ private final String mPreviousQuestionId;
+ private final String mNextQuestionId;
+
+ public Question(final Map<String, Object> map) {
+ mQuestionId = (String) map.get("id");
+ mType = QuestionType.valueOf(((String) map.get("type")).toUpperCase());
+
+ mQuestionText = (String) map.get("text");
+
+ mPreviousQuestionId = (String) map.get("previousQuestion");
+ mNextQuestionId = (String) map.get("nextQuestion");
+
+ mAnswers = new HashMap<String, Answer>();
+ for (Map.Entry<String, Object> answer :
+ ((Map<String, Object>) map.get("answers")).entrySet()) {
+
+ final String id = answer.getKey();
+ final Map<String, Object> answerMap = (Map<String, Object>) answer.getValue();
+ final Answer answerObj = new Answer(id, answerMap);
+ mAnswers.put(id, answerObj);
+ }
+ }
+
+ public String getId() {
+ return mQuestionId;
+ }
+
+ public QuestionType getType() {
+ return mType;
+ }
+
+ public String getText() {
+ return mQuestionText;
+ }
+
+ public String getPrevious() {
+ return mPreviousQuestionId;
+ }
+
+ public String getNext() {
+ return mNextQuestionId;
+ }
+
+ public Map<String, Answer> getAnswers() {
+ return Collections.unmodifiableMap(mAnswers);
+ }
+}
diff --git a/src/com/p4square/grow/backend/resources/Score.java b/src/com/p4square/grow/backend/resources/Score.java
new file mode 100644
index 0000000..c7e5ecc
--- /dev/null
+++ b/src/com/p4square/grow/backend/resources/Score.java
@@ -0,0 +1,34 @@
+/*
+ * 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 {
+ 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/SurveyResource.java b/src/com/p4square/grow/backend/resources/SurveyResource.java
new file mode 100644
index 0000000..d22d763
--- /dev/null
+++ b/src/com/p4square/grow/backend/resources/SurveyResource.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.backend.resources;
+
+import java.util.Map;
+import java.util.HashMap;
+
+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.apache.log4j.Logger;
+
+import com.p4square.grow.backend.GrowBackend;
+import com.p4square.grow.backend.db.CassandraDatabase;
+
+/**
+ * This resource manages assessment questions.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class SurveyResource extends ServerResource {
+ private final static Logger cLog = Logger.getLogger(SurveyResource.class);
+
+ private CassandraDatabase mDb;
+
+ private String mQuestionId;
+
+ @Override
+ public void doInit() {
+ super.doInit();
+
+ final GrowBackend backend = (GrowBackend) getApplication();
+ mDb = backend.getDatabase();
+
+ mQuestionId = getAttribute("questionId");
+ }
+
+ /**
+ * Handle GET Requests.
+ */
+ @Override
+ protected Representation get() {
+ String result = "";
+
+ if (mQuestionId == null) {
+ // TODO: List all question ids
+
+ } else if (mQuestionId.equals("first")) {
+ // TODO: Get the first question id from db?
+ result = "1";
+
+ } else {
+ // Get a question by id
+ result = mDb.getKey("strings", "/questions/" + mQuestionId);
+
+ if (result == null) {
+ // 404
+ setStatus(Status.CLIENT_ERROR_NOT_FOUND);
+ return null;
+ }
+ }
+
+ return new StringRepresentation(result);
+ }
+}
diff --git a/src/com/p4square/grow/backend/resources/SurveyResultsResource.java b/src/com/p4square/grow/backend/resources/SurveyResultsResource.java
new file mode 100644
index 0000000..db7dad0
--- /dev/null
+++ b/src/com/p4square/grow/backend/resources/SurveyResultsResource.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.backend.resources;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import com.netflix.astyanax.model.Column;
+import com.netflix.astyanax.model.ColumnList;
+
+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.apache.log4j.Logger;
+
+import com.p4square.grow.backend.GrowBackend;
+import com.p4square.grow.backend.db.CassandraDatabase;
+
+/**
+ * Store the user's answers to the assessment and generate their score.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class SurveyResultsResource extends ServerResource {
+ private final static Logger cLog = Logger.getLogger(SurveyResultsResource.class);
+
+ private final static ObjectMapper cMapper = new ObjectMapper();
+
+ static enum RequestType {
+ ASSESSMENT, ANSWER
+ }
+
+ private CassandraDatabase mDb;
+
+ private RequestType mRequestType;
+ private String mUserId;
+ private String mQuestionId;
+
+ @Override
+ public void doInit() {
+ super.doInit();
+
+ final GrowBackend backend = (GrowBackend) getApplication();
+ mDb = backend.getDatabase();
+
+ mUserId = getAttribute("userId");
+ mQuestionId = getAttribute("questionId");
+
+ mRequestType = RequestType.ASSESSMENT;
+ if (mQuestionId != null) {
+ mRequestType = RequestType.ANSWER;
+ }
+ }
+
+ /**
+ * Handle GET Requests.
+ */
+ @Override
+ protected Representation get() {
+ String result = null;
+
+ switch (mRequestType) {
+ case ANSWER:
+ result = mDb.getKey("assessments", mUserId, mQuestionId);
+ break;
+
+ case ASSESSMENT:
+ result = buildAssessment();
+ break;
+ }
+
+ if (result == null) {
+ setStatus(Status.CLIENT_ERROR_NOT_FOUND);
+ return null;
+ }
+
+ return new StringRepresentation(result);
+ }
+
+ /**
+ * Handle PUT requests
+ */
+ @Override
+ protected Representation put(Representation entity) {
+ boolean success = false;
+
+ switch (mRequestType) {
+ case ANSWER:
+ try {
+ mDb.putKey("assessments", mUserId, mQuestionId, entity.getText());
+ mDb.putKey("assessments", mUserId, "lastAnswered", mQuestionId);
+ mDb.deleteKey("assessments", mUserId, "summary");
+ success = true;
+
+ } catch (Exception e) {
+ cLog.warn("Caught exception putting answer: " + e.getMessage(), e);
+ }
+ break;
+
+ default:
+ setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
+ }
+
+ if (success) {
+ setStatus(Status.SUCCESS_NO_CONTENT);
+
+ } else {
+ setStatus(Status.SERVER_ERROR_INTERNAL);
+ }
+
+ return null;
+ }
+
+ /**
+ * This method compiles assessment results.
+ */
+ private String buildAssessment() {
+ StringBuilder sb = new StringBuilder("{ ");
+
+ // Last question answered
+ final String lastAnswered = mDb.getKey("assessments", mUserId, "lastAnswered");
+ if (lastAnswered != null) {
+ sb.append("\"lastAnswered\": \"" + lastAnswered + "\"");
+ }
+
+ // Compute score
+ ColumnList<String> row = mDb.getRow("assessments", mUserId);
+ if (!row.isEmpty()) {
+ Score score = new Score();
+ for (Column<String> c : row) {
+ if (c.getName().equals("lastAnswered")) {
+ continue;
+ }
+
+ final String questionId = c.getName();
+ final String answerId = c.getStringValue();
+ if (!scoreQuestion(score, questionId, answerId)) {
+ break;
+ }
+ }
+
+ sb.append(", \"score\":" + score.sum / score.count);
+ sb.append(", \"sum\":" + score.sum);
+ sb.append(", \"count\":" + score.count);
+ sb.append(", \"result\":\"" + score.toString() + "\"");
+ }
+
+ sb.append(" }");
+ return sb.toString();
+ }
+
+ private boolean scoreQuestion(final Score score, final String questionId,
+ final String answerJson) {
+
+ final String data = mDb.getKey("strings", "/questions/" + questionId);
+
+ try {
+ final Map<?,?> questionMap = cMapper.readValue(data, Map.class);
+ final Map<?,?> answerMap = cMapper.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 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
new file mode 100644
index 0000000..93f4fbc
--- /dev/null
+++ b/src/com/p4square/grow/backend/resources/TrainingRecordResource.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.backend.resources;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import com.netflix.astyanax.model.Column;
+import com.netflix.astyanax.model.ColumnList;
+
+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.apache.log4j.Logger;
+
+import com.p4square.grow.backend.GrowBackend;
+import com.p4square.grow.backend.db.CassandraDatabase;
+
+/**
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class TrainingRecordResource extends ServerResource {
+ private final static Logger cLog = Logger.getLogger(TrainingRecordResource.class);
+
+ static enum RequestType {
+ SUMMARY, VIDEO
+ }
+
+ private GrowBackend mBackend;
+ private CassandraDatabase mDb;
+
+ private RequestType mRequestType;
+ private String mUserId;
+ private String mVideoId;
+
+ @Override
+ public void doInit() {
+ super.doInit();
+
+ mBackend = (GrowBackend) getApplication();
+ mDb = mBackend.getDatabase();
+
+ mUserId = getAttribute("userId");
+ mVideoId = getAttribute("videoId");
+
+ mRequestType = RequestType.SUMMARY;
+ if (mVideoId != null) {
+ mRequestType = RequestType.VIDEO;
+ }
+ }
+
+ /**
+ * Handle GET Requests.
+ */
+ @Override
+ protected Representation get() {
+ String result = null;
+
+ switch (mRequestType) {
+ case VIDEO:
+ result = mDb.getKey("training", mUserId, mVideoId);
+ break;
+
+ case SUMMARY:
+ result = buildSummary();
+ break;
+ }
+
+ if (result == null) {
+ setStatus(Status.CLIENT_ERROR_NOT_FOUND);
+ return null;
+ }
+
+ return new StringRepresentation(result);
+ }
+
+ /**
+ * Handle PUT requests
+ */
+ @Override
+ protected Representation put(Representation entity) {
+ boolean success = false;
+
+ switch (mRequestType) {
+ case VIDEO:
+ try {
+ mDb.putKey("training", mUserId, mVideoId, entity.getText());
+ mDb.putKey("training", mUserId, "lastVideo", mVideoId);
+ success = true;
+
+ } catch (Exception e) {
+ cLog.warn("Caught exception updating training record: " + e.getMessage(), e);
+ }
+ break;
+
+ default:
+ setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
+ }
+
+ if (success) {
+ setStatus(Status.SUCCESS_NO_CONTENT);
+
+ } else {
+ setStatus(Status.SERVER_ERROR_INTERNAL);
+ }
+
+ return null;
+ }
+
+ /**
+ * This method compiles the summary of the training completed.
+ */
+ private String buildSummary() {
+ StringBuilder sb = new StringBuilder("{ ");
+
+ // Last question answered
+ final String lastVideo = mDb.getKey("training", mUserId, "lastVideo");
+ if (lastVideo != null) {
+ sb.append("\"lastVideo\": \"" + lastVideo + "\", ");
+ }
+
+ // List of videos watched
+ sb.append("\"videos\": { ");
+ ColumnList<String> row = mDb.getRow("training", mUserId);
+ if (!row.isEmpty()) {
+ boolean first = true;
+ for (Column<String> c : row) {
+ if ("lastVideo".equals(c.getName())) {
+ continue;
+ }
+
+ if (first) {
+ sb.append("\"" + c.getName() + "\": ");
+ first = false;
+ } else {
+ sb.append(", \"" + c.getName() + "\": ");
+ }
+
+ sb.append(c.getStringValue());
+ }
+ }
+ sb.append(" }");
+
+
+ sb.append(" }");
+ return sb.toString();
+ }
+
+}
diff --git a/src/com/p4square/grow/backend/resources/TrainingResource.java b/src/com/p4square/grow/backend/resources/TrainingResource.java
new file mode 100644
index 0000000..85d08c1
--- /dev/null
+++ b/src/com/p4square/grow/backend/resources/TrainingResource.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.backend.resources;
+
+import com.netflix.astyanax.model.Column;
+import com.netflix.astyanax.model.ColumnList;
+
+import org.restlet.data.Status;
+import org.restlet.resource.ServerResource;
+import org.restlet.representation.Representation;
+import org.restlet.representation.StringRepresentation;
+
+import org.apache.log4j.Logger;
+
+import com.p4square.grow.backend.GrowBackend;
+import com.p4square.grow.backend.db.CassandraDatabase;
+
+/**
+ * This resource returns a listing of training items for a particular level.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class TrainingResource extends ServerResource {
+ private final static Logger cLog = Logger.getLogger(TrainingResource.class);
+
+ private GrowBackend mBackend;
+ private CassandraDatabase mDb;
+
+ private String mLevel;
+ private String mVideoId;
+
+ @Override
+ public void doInit() {
+ super.doInit();
+
+ mBackend = (GrowBackend) getApplication();
+ mDb = mBackend.getDatabase();
+
+ mLevel = getAttribute("level");
+ mVideoId = getAttribute("videoId");
+ }
+
+ /**
+ * Handle GET Requests.
+ */
+ @Override
+ protected Representation get() {
+ String result = null;
+
+ if (mLevel == null) {
+ setStatus(Status.CLIENT_ERROR_NOT_FOUND);
+ return null;
+ }
+
+ if (mVideoId == null) {
+ // Get all videos
+ ColumnList<String> row = mDb.getRow("strings", "/training/" + mLevel);
+ if (!row.isEmpty()) {
+ StringBuilder sb = new StringBuilder("{ \"level\": \"" + mLevel + "\"");
+ sb.append(", \"videos\": [");
+ boolean first = true;
+ for (Column<String> c : row) {
+ if (!first) {
+ sb.append(", ");
+ }
+ sb.append(c.getStringValue());
+ first = false;
+ }
+ sb.append("] }");
+ result = sb.toString();
+ }
+
+ } else {
+ // Get single video
+ result = mDb.getKey("strings", "/training/" + mLevel, mVideoId);
+ }
+
+ if (result == null) {
+ // 404
+ setStatus(Status.CLIENT_ERROR_NOT_FOUND);
+ return null;
+ }
+
+ return new StringRepresentation(result);
+ }
+}