diff options
Diffstat (limited to 'src/com/p4square/grow/backend/resources')
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); +    } +} | 
