diff options
Diffstat (limited to 'src/main/java/com/p4square/grow/backend/resources')
6 files changed, 872 insertions, 0 deletions
diff --git a/src/main/java/com/p4square/grow/backend/resources/AccountResource.java b/src/main/java/com/p4square/grow/backend/resources/AccountResource.java new file mode 100644 index 0000000..2ac7061 --- /dev/null +++ b/src/main/java/com/p4square/grow/backend/resources/AccountResource.java @@ -0,0 +1,87 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package com.p4square.grow.backend.resources; + +import java.io.IOException; + +import org.restlet.data.Status; +import org.restlet.resource.ServerResource; +import org.restlet.representation.Representation; + +import org.restlet.ext.jackson.JacksonRepresentation; + +import org.apache.log4j.Logger; + +import com.p4square.grow.model.UserRecord; +import com.p4square.grow.provider.Provider; +import com.p4square.grow.provider.ProvidesUserRecords; +import com.p4square.grow.provider.JsonEncodedProvider; + +/** + * Stores a document about a user. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class AccountResource extends ServerResource { + private static final Logger LOG = Logger.getLogger(AccountResource.class); + + private Provider<String, UserRecord> mUserRecordProvider; + + private String mUserId; + + @Override + public void doInit() { + super.doInit(); + + final ProvidesUserRecords backend = (ProvidesUserRecords) getApplication(); + mUserRecordProvider = backend.getUserRecordProvider(); + + mUserId = getAttribute("userId"); + } + + /** + * Handle GET Requests. + */ + @Override + protected Representation get() { + try { + UserRecord result = mUserRecordProvider.get(mUserId); + + if (result == null) { + setStatus(Status.CLIENT_ERROR_NOT_FOUND); + return null; + } + + JacksonRepresentation<UserRecord> rep = new JacksonRepresentation<UserRecord>(result); + rep.setObjectMapper(JsonEncodedProvider.MAPPER); + return rep; + + } catch (IOException e) { + setStatus(Status.SERVER_ERROR_INTERNAL); + return null; + } + } + + /** + * Handle PUT requests + */ + @Override + protected Representation put(Representation entity) { + try { + JacksonRepresentation<UserRecord> representation = + new JacksonRepresentation<>(entity, UserRecord.class); + representation.setObjectMapper(JsonEncodedProvider.MAPPER); + UserRecord record = representation.getObject(); + + mUserRecordProvider.put(mUserId, record); + setStatus(Status.SUCCESS_NO_CONTENT); + + } catch (IOException e) { + setStatus(Status.SERVER_ERROR_INTERNAL); + } + + return null; + } +} diff --git a/src/main/java/com/p4square/grow/backend/resources/BannerResource.java b/src/main/java/com/p4square/grow/backend/resources/BannerResource.java new file mode 100644 index 0000000..2b9c8e6 --- /dev/null +++ b/src/main/java/com/p4square/grow/backend/resources/BannerResource.java @@ -0,0 +1,85 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package com.p4square.grow.backend.resources; + +import java.io.IOException; + +import org.restlet.data.Status; +import org.restlet.ext.jackson.JacksonRepresentation; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; +import org.restlet.resource.ServerResource; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.apache.log4j.Logger; + +import com.p4square.grow.backend.GrowBackend; +import com.p4square.grow.model.Banner; +import com.p4square.grow.provider.JsonEncodedProvider; +import com.p4square.grow.provider.Provider; + +/** + * Fetches or sets the banner string. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class BannerResource extends ServerResource { + private static final Logger LOG = Logger.getLogger(BannerResource.class); + + public static final ObjectMapper MAPPER = JsonEncodedProvider.MAPPER; + + private Provider<String, String> mStringProvider; + + @Override + public void doInit() { + super.doInit(); + + final GrowBackend backend = (GrowBackend) getApplication(); + mStringProvider = backend.getStringProvider(); + } + + /** + * Handle GET Requests. + */ + @Override + protected Representation get() { + String result = null; + try { + result = mStringProvider.get("banner"); + + } catch (IOException e) { + LOG.warn("Exception loading banner: " + e); + } + + if (result == null || result.length() == 0) { + result = "{\"html\":null}"; + } + + return new StringRepresentation(result); + } + + /** + * Handle PUT requests + */ + @Override + protected Representation put(Representation entity) { + try { + JacksonRepresentation<Banner> representation = + new JacksonRepresentation<>(entity, Banner.class); + representation.setObjectMapper(MAPPER); + + Banner banner = representation.getObject(); + + mStringProvider.put("banner", MAPPER.writeValueAsString(banner)); + setStatus(Status.SUCCESS_NO_CONTENT); + + } catch (IOException e) { + setStatus(Status.SERVER_ERROR_INTERNAL); + } + + return null; + } +} diff --git a/src/main/java/com/p4square/grow/backend/resources/SurveyResource.java b/src/main/java/com/p4square/grow/backend/resources/SurveyResource.java new file mode 100644 index 0000000..8723ee2 --- /dev/null +++ b/src/main/java/com/p4square/grow/backend/resources/SurveyResource.java @@ -0,0 +1,115 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package com.p4square.grow.backend.resources; + +import java.io.IOException; + +import java.util.Map; +import java.util.HashMap; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.restlet.data.MediaType; +import org.restlet.data.Status; +import org.restlet.ext.jackson.JacksonRepresentation; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; +import org.restlet.resource.ServerResource; + +import org.apache.log4j.Logger; + +import com.p4square.grow.backend.GrowBackend; +import com.p4square.grow.model.Question; +import com.p4square.grow.provider.JsonEncodedProvider; +import com.p4square.grow.provider.Provider; + +/** + * This resource manages assessment questions. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class SurveyResource extends ServerResource { + private static final Logger LOG = Logger.getLogger(SurveyResource.class); + + private static final ObjectMapper MAPPER = JsonEncodedProvider.MAPPER; + + private Provider<String, Question> mQuestionProvider; + private Provider<String, String> mStringProvider; + + private String mQuestionId; + + @Override + public void doInit() { + super.doInit(); + + final GrowBackend backend = (GrowBackend) getApplication(); + mQuestionProvider = backend.getQuestionProvider(); + mStringProvider = backend.getStringProvider(); + + 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")) { + // Get the first question id from db? + Map<?, ?> questionSummary = getQuestionsSummary(); + mQuestionId = (String) questionSummary.get("first"); + + } else if (mQuestionId.equals("count")) { + // Get the first question id from db? + Map<?, ?> questionSummary = getQuestionsSummary(); + + return new StringRepresentation("{\"count\":" + + String.valueOf((Integer) questionSummary.get("count")) + "}"); + } + + if (mQuestionId != null) { + // Get a question by id + Question question = null; + try { + question = mQuestionProvider.get(mQuestionId); + } catch (IOException e) { + LOG.error("IOException loading question: " + e); + } + + if (question == null) { + // 404 + setStatus(Status.CLIENT_ERROR_NOT_FOUND); + return null; + } + + JacksonRepresentation<Question> rep = new JacksonRepresentation<>(question); + rep.setObjectMapper(MAPPER); + return rep; + } + + return new StringRepresentation(result); + } + + private Map<?, ?> getQuestionsSummary() { + try { + // TODO: This could be better. Quick fix for provider support. + String json = mStringProvider.get("/questions"); + + if (json != null) { + return MAPPER.readValue(json, Map.class); + } + + } catch (IOException e) { + LOG.info("Exception reading questions summary.", e); + } + + return null; + } +} diff --git a/src/main/java/com/p4square/grow/backend/resources/SurveyResultsResource.java b/src/main/java/com/p4square/grow/backend/resources/SurveyResultsResource.java new file mode 100644 index 0000000..7c15cfd --- /dev/null +++ b/src/main/java/com/p4square/grow/backend/resources/SurveyResultsResource.java @@ -0,0 +1,253 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package com.p4square.grow.backend.resources; + +import java.io.IOException; +import java.util.Map; +import java.util.HashMap; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.restlet.data.MediaType; +import org.restlet.data.Status; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; +import org.restlet.resource.ServerResource; + +import org.apache.log4j.Logger; + +import com.p4square.grow.backend.GrowBackend; +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.model.UserRecord; +import com.p4square.grow.provider.CollectionProvider; +import com.p4square.grow.provider.Provider; + + +/** + * Store the user's answers to the assessment and generate their score. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class SurveyResultsResource extends ServerResource { + private static final Logger LOG = Logger.getLogger(SurveyResultsResource.class); + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + static enum RequestType { + ASSESSMENT, ANSWER + } + + private CollectionProvider<String, String, String> mAnswerProvider; + private Provider<String, Question> mQuestionProvider; + private Provider<String, UserRecord> mUserRecordProvider; + + private RequestType mRequestType; + private String mUserId; + private String mQuestionId; + + @Override + public void doInit() { + super.doInit(); + + final GrowBackend backend = (GrowBackend) getApplication(); + mAnswerProvider = backend.getAnswerProvider(); + mQuestionProvider = backend.getQuestionProvider(); + mUserRecordProvider = backend.getUserRecordProvider(); + + mUserId = getAttribute("userId"); + mQuestionId = getAttribute("questionId"); + + mRequestType = RequestType.ASSESSMENT; + if (mQuestionId != null) { + mRequestType = RequestType.ANSWER; + } + } + + /** + * Handle GET Requests. + */ + @Override + protected Representation get() { + try { + String result = null; + + switch (mRequestType) { + case ANSWER: + result = mAnswerProvider.get(mUserId, mQuestionId); + break; + + case ASSESSMENT: + result = mAnswerProvider.get(mUserId, "summary"); + if (result == null || result.length() == 0) { + result = buildAssessment(); + } + break; + } + + if (result == null) { + setStatus(Status.CLIENT_ERROR_NOT_FOUND); + return null; + } + + return new StringRepresentation(result); + } catch (IOException e) { + LOG.error("IOException getting answer: ", e); + setStatus(Status.SERVER_ERROR_INTERNAL); + return null; + } + } + + /** + * Handle PUT requests + */ + @Override + protected Representation put(Representation entity) { + boolean success = false; + + switch (mRequestType) { + case ANSWER: + try { + mAnswerProvider.put(mUserId, mQuestionId, entity.getText()); + mAnswerProvider.put(mUserId, "lastAnswered", mQuestionId); + mAnswerProvider.put(mUserId, "summary", null); + success = true; + + } catch (Exception e) { + LOG.warn("Caught exception putting answer: " + e.getMessage(), e); + } + break; + + default: + setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + return null; + } + + if (success) { + setStatus(Status.SUCCESS_NO_CONTENT); + + } else { + setStatus(Status.SERVER_ERROR_INTERNAL); + } + + return null; + } + + /** + * Clear assessment results. + */ + @Override + protected Representation delete() { + boolean success = false; + + switch (mRequestType) { + case ANSWER: + try { + mAnswerProvider.put(mUserId, mQuestionId, null); + mAnswerProvider.put(mUserId, "summary", null); + success = true; + + } catch (Exception e) { + LOG.warn("Caught exception putting answer: " + e.getMessage(), e); + } + break; + + case ASSESSMENT: + try { + mAnswerProvider.put(mUserId, "summary", null); + mAnswerProvider.put(mUserId, "lastAnswered", null); + // TODO Delete answers + + UserRecord record = mUserRecordProvider.get(mUserId); + if (record != null) { + record.setLanding("assessment"); + mUserRecordProvider.put(mUserId, record); + } + + success = true; + + } catch (Exception e) { + LOG.warn("Caught exception putting answer: " + e.getMessage(), e); + } + break; + + default: + setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + return null; + } + + if (success) { + setStatus(Status.SUCCESS_NO_CONTENT); + + } else { + setStatus(Status.SERVER_ERROR_INTERNAL); + } + + return null; + + } + + /** + * This method compiles assessment results. + */ + private String buildAssessment() throws IOException { + StringBuilder sb = new StringBuilder("{ "); + + // Last question answered + final String lastAnswered = mAnswerProvider.get(mUserId, "lastAnswered"); + if (lastAnswered != null && lastAnswered.length() > 0) { + sb.append("\"lastAnswered\": \"" + lastAnswered + "\", "); + } + + // Compute score + Map<String, String> row = mAnswerProvider.query(mUserId); + if (row.size() > 0) { + Score score = new Score(); + boolean scoringDone = false; + int totalAnswers = 0; + for (Map.Entry<String, String> c : row.entrySet()) { + if (c.getKey().equals("lastAnswered") || c.getKey().equals("summary")) { + continue; + } + + try { + Question question = mQuestionProvider.get(c.getKey()); + RecordedAnswer userAnswer = MAPPER.readValue(c.getValue(), RecordedAnswer.class); + + if (question == null) { + LOG.warn("Answer for unknown question: " + c.getKey()); + continue; + } + + LOG.debug("Scoring questionId: " + c.getKey()); + scoringDone = !question.scoreAnswer(score, userAnswer); + + } catch (Exception e) { + LOG.error("Failed to score question: {userid: \"" + mUserId + + "\", questionid:\"" + c.getKey() + + "\", userAnswer:\"" + c.getValue() + "\"}", e); + } + + totalAnswers++; + } + + sb.append("\"score\":" + score.getScore()); + sb.append(", \"sum\":" + score.getSum()); + sb.append(", \"count\":" + score.getCount()); + sb.append(", \"totalAnswers\":" + totalAnswers); + sb.append(", \"result\":\"" + score.toString() + "\""); + } + + sb.append(" }"); + String summary = sb.toString(); + + // Persist summary + mAnswerProvider.put(mUserId, "summary", summary); + + return summary; + } +} diff --git a/src/main/java/com/p4square/grow/backend/resources/TrainingRecordResource.java b/src/main/java/com/p4square/grow/backend/resources/TrainingRecordResource.java new file mode 100644 index 0000000..51ba56a --- /dev/null +++ b/src/main/java/com/p4square/grow/backend/resources/TrainingRecordResource.java @@ -0,0 +1,235 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package com.p4square.grow.backend.resources; + +import java.io.IOException; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.HashMap; + +import com.fasterxml.jackson.databind.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.ext.jackson.JacksonRepresentation; + +import org.apache.log4j.Logger; + +import com.p4square.grow.backend.GrowBackend; + +import com.p4square.grow.model.Chapter; +import com.p4square.grow.model.Playlist; +import com.p4square.grow.model.VideoRecord; +import com.p4square.grow.model.TrainingRecord; + +import com.p4square.grow.provider.CollectionProvider; +import com.p4square.grow.provider.JsonEncodedProvider; +import com.p4square.grow.provider.Provider; +import com.p4square.grow.provider.ProvidesAssessments; +import com.p4square.grow.provider.ProvidesTrainingRecords; + +import com.p4square.grow.model.Score; + +/** + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class TrainingRecordResource extends ServerResource { + private static final Logger LOG = Logger.getLogger(TrainingRecordResource.class); + private static final ObjectMapper MAPPER = JsonEncodedProvider.MAPPER; + + static enum RequestType { + SUMMARY, VIDEO + } + + private Provider<String, TrainingRecord> mTrainingRecordProvider; + private CollectionProvider<String, String, String> mAnswerProvider; + + private RequestType mRequestType; + private String mUserId; + private String mVideoId; + private TrainingRecord mRecord; + + @Override + public void doInit() { + super.doInit(); + + mTrainingRecordProvider = ((ProvidesTrainingRecords) getApplication()).getTrainingRecordProvider(); + mAnswerProvider = ((ProvidesAssessments) getApplication()).getAnswerProvider(); + + mUserId = getAttribute("userId"); + mVideoId = getAttribute("videoId"); + + try { + Playlist defaultPlaylist = ((ProvidesTrainingRecords) getApplication()).getDefaultPlaylist(); + + mRecord = mTrainingRecordProvider.get(mUserId); + if (mRecord == null) { + mRecord = new TrainingRecord(); + mRecord.setPlaylist(defaultPlaylist); + skipAssessedChapters(mUserId, mRecord); + } else { + // Merge the playlist with the most recent version. + mRecord.getPlaylist().merge(defaultPlaylist); + } + + } catch (IOException e) { + LOG.error("IOException loading TrainingRecord: " + e.getMessage(), e); + mRecord = null; + } + + mRequestType = RequestType.SUMMARY; + if (mVideoId != null) { + mRequestType = RequestType.VIDEO; + } + } + + /** + * Handle GET Requests. + */ + @Override + protected Representation get() { + JacksonRepresentation<?> rep = null; + + if (mRecord == null) { + setStatus(Status.SERVER_ERROR_INTERNAL); + return null; + } + + switch (mRequestType) { + case VIDEO: + VideoRecord video = mRecord.getPlaylist().find(mVideoId); + if (video == null) { + break; // Fall through and return 404 + } + rep = new JacksonRepresentation<VideoRecord>(video); + break; + + case SUMMARY: + rep = new JacksonRepresentation<TrainingRecord>(mRecord); + break; + } + + if (rep == null) { + setStatus(Status.CLIENT_ERROR_NOT_FOUND); + return null; + + } else { + rep.setObjectMapper(JsonEncodedProvider.MAPPER); + return rep; + } + } + + /** + * Handle PUT requests + */ + @Override + protected Representation put(Representation entity) { + if (mRecord == null) { + setStatus(Status.SERVER_ERROR_INTERNAL); + return null; + } + + switch (mRequestType) { + case VIDEO: + try { + JacksonRepresentation<VideoRecord> representation = + new JacksonRepresentation<>(entity, VideoRecord.class); + representation.setObjectMapper(JsonEncodedProvider.MAPPER); + VideoRecord update = representation.getObject(); + VideoRecord video = mRecord.getPlaylist().find(mVideoId); + + if (video == null) { + // TODO: Video isn't on their playlist... + LOG.warn("Skipping video completion for video missing from playlist."); + + } else if (update.getComplete() && !video.getComplete()) { + // Video was newly completed + video.complete(); + mRecord.setLastVideo(mVideoId); + + mTrainingRecordProvider.put(mUserId, mRecord); + } + + setStatus(Status.SUCCESS_NO_CONTENT); + + } catch (Exception e) { + LOG.warn("Caught exception updating training record: " + e.getMessage(), e); + setStatus(Status.SERVER_ERROR_INTERNAL); + } + break; + + default: + setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } + + return null; + } + + private Score getAssessedScore(String userId) throws IOException { + // Get the user's score. + Score assessedScore = new Score(0, 0); + + String summaryString = mAnswerProvider.get(userId, "summary"); + if (summaryString == null) { + throw new IOException("Asked to create training record for unassessed user " + userId); + } + + Map<?,?> summary = MAPPER.readValue(summaryString, Map.class); + + if (summary.containsKey("sum") && summary.containsKey("count")) { + double sum = (Double) summary.get("sum"); + int count = (Integer) summary.get("count"); + assessedScore = new Score(sum, count); + } + + return assessedScore; + } + + /** + * Mark the chapters which the user assessed through as not required. + */ + private void skipAssessedChapters(String userId, TrainingRecord record) { + // Get the user's score. + Score assessedScore = new Score(0, 0); + + try { + assessedScore = getAssessedScore(userId); + } catch (IOException e) { + LOG.error("IOException fetching assessment record for " + userId, e); + return; + } + + // Mark the correct videos as not required. + Playlist playlist = record.getPlaylist(); + + for (Map.Entry<String, Chapter> entry : playlist.getChaptersMap().entrySet()) { + String chapterId = entry.getKey(); + Chapter chapter = entry.getValue(); + boolean required; + + if ("introduction".equals(chapter)) { + // Introduction chapter is always required + required = true; + + } else { + // Chapter required if the floor of the score is <= the chapter's numeric value. + required = assessedScore.floor() <= Score.numericScore(chapterId); + } + + if (!required) { + for (VideoRecord video : chapter.getVideos().values()) { + video.setRequired(required); + } + } + } + } +} diff --git a/src/main/java/com/p4square/grow/backend/resources/TrainingResource.java b/src/main/java/com/p4square/grow/backend/resources/TrainingResource.java new file mode 100644 index 0000000..6efdfab --- /dev/null +++ b/src/main/java/com/p4square/grow/backend/resources/TrainingResource.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package com.p4square.grow.backend.resources; + +import java.io.IOException; +import java.util.Map; + +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; + +import com.p4square.grow.provider.CollectionProvider; +/** + * 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 LOG = Logger.getLogger(TrainingResource.class); + + private CollectionProvider<String, String, String> mVideoProvider; + + private String mLevel; + private String mVideoId; + + @Override + public void doInit() { + super.doInit(); + + GrowBackend backend = (GrowBackend) getApplication(); + mVideoProvider = backend.getVideoProvider(); + + 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; + } + + try { + if (mVideoId == null) { + // Get all videos + // TODO: This could be improved, but this is the quickest way to get + // providers working. + Map<String, String> videos = mVideoProvider.query(mLevel); + if (videos.size() > 0) { + StringBuilder sb = new StringBuilder("{ \"level\": \"" + mLevel + "\""); + sb.append(", \"videos\": ["); + boolean first = true; + for (String value : videos.values()) { + if (!first) { + sb.append(", "); + } + sb.append(value); + first = false; + } + sb.append("] }"); + result = sb.toString(); + } + + } else { + // Get single video + result = mVideoProvider.get(mLevel, mVideoId); + } + + if (result == null) { + // 404 + setStatus(Status.CLIENT_ERROR_NOT_FOUND); + return null; + } + + return new StringRepresentation(result); + + } catch (IOException e) { + LOG.error("IOException fetch video: " + e.getMessage(), e); + setStatus(Status.SERVER_ERROR_INTERNAL); + return null; + } + } +} |