diff options
author | Jesse Morgan <jesse@jesterpm.net> | 2013-11-09 15:24:56 -0800 |
---|---|---|
committer | Jesse Morgan <jesse@jesterpm.net> | 2013-11-09 15:24:56 -0800 |
commit | 0d90da39f77ac3cfa607a68bc59336bf0bdff240 (patch) | |
tree | 1a2133dea8035004052e1fddf9b4c022fb8e21e1 /src/com/p4square/grow/backend | |
parent | ebbfb39ca9b63c170ca7b609dd07d234d89ab23a (diff) |
Refactored TrainingResource to use the Provider interface.
Playlists are now generated from a default playlist and regularly
merged with the default playlist to get updates.
Also adding the Question tests that got left out of a previous commit.
Diffstat (limited to 'src/com/p4square/grow/backend')
9 files changed, 181 insertions, 336 deletions
diff --git a/src/com/p4square/grow/backend/GrowBackend.java b/src/com/p4square/grow/backend/GrowBackend.java index 45e0fa2..195554e 100644 --- a/src/com/p4square/grow/backend/GrowBackend.java +++ b/src/com/p4square/grow/backend/GrowBackend.java @@ -4,6 +4,8 @@ package com.p4square.grow.backend; +import java.io.IOException; + import org.apache.log4j.Logger; import org.restlet.Application; @@ -17,10 +19,15 @@ 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.backend.db.CassandraTrainingRecordProvider; import com.p4square.grow.model.Question; +import com.p4square.grow.model.TrainingRecord; +import com.p4square.grow.model.Playlist; import com.p4square.grow.provider.Provider; +import com.p4square.grow.provider.ProvidesQuestions; +import com.p4square.grow.provider.ProvidesTrainingRecords; import com.p4square.grow.provider.QuestionProvider; import com.p4square.grow.backend.resources.AccountResource; @@ -35,7 +42,8 @@ import com.p4square.grow.backend.resources.TrainingResource; * * @author Jesse Morgan <jesse@jesterpm.net> */ -public class GrowBackend extends Application { +public class GrowBackend extends Application + implements ProvidesQuestions, ProvidesTrainingRecords { private static final String DEFAULT_COLUMN = "value"; private final static Logger LOG = Logger.getLogger(GrowBackend.class); @@ -44,6 +52,7 @@ public class GrowBackend extends Application { private final CassandraDatabase mDatabase; private final Provider<String, Question> mQuestionProvider; + private final CassandraTrainingRecordProvider mTrainingRecordProvider; public GrowBackend() { this(new Config()); @@ -53,12 +62,14 @@ public class GrowBackend extends Application { mConfig = config; mDatabase = new CassandraDatabase(); - mQuestionProvider = new QuestionProvider<CassandraKey>(new CassandraProviderImpl<Question>(mDatabase, "strings", Question.class)) { + mQuestionProvider = new QuestionProvider<CassandraKey>(new CassandraProviderImpl<Question>(mDatabase, Question.class)) { @Override public CassandraKey makeKey(String questionId) { - return new CassandraKey("/questions/" + questionId, DEFAULT_COLUMN); + return new CassandraKey("strings", "/questions/" + questionId, DEFAULT_COLUMN); } }; + + mTrainingRecordProvider = new CassandraTrainingRecordProvider(mDatabase); } @Override @@ -120,10 +131,23 @@ public class GrowBackend extends Application { return mDatabase; } + @Override public Provider<String, Question> getQuestionProvider() { return mQuestionProvider; } + @Override + public Provider<String, TrainingRecord> getTrainingRecordProvider() { + return mTrainingRecordProvider; + } + + /** + * @return the Default Playlist. + */ + public Playlist getDefaultPlaylist() throws IOException { + return mTrainingRecordProvider.getDefaultPlaylist(); + } + /** * 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 index 8e23087..853fe96 100644 --- a/src/com/p4square/grow/backend/db/CassandraKey.java +++ b/src/com/p4square/grow/backend/db/CassandraKey.java @@ -10,14 +10,20 @@ package com.p4square.grow.backend.db; * @author Jesse Morgan <jesse@jesterpm.net> */ public class CassandraKey { + private final String mColumnFamily; private final String mId; private final String mColumn; - public CassandraKey(String id, String column) { + public CassandraKey(String columnFamily, String id, String column) { + mColumnFamily = columnFamily; mId = id; mColumn = column; } + public String getColumnFamily() { + return mColumnFamily; + } + public String getId() { return mId; } diff --git a/src/com/p4square/grow/backend/db/CassandraProviderImpl.java b/src/com/p4square/grow/backend/db/CassandraProviderImpl.java index fb6e34e..c1f6e6d 100644 --- a/src/com/p4square/grow/backend/db/CassandraProviderImpl.java +++ b/src/com/p4square/grow/backend/db/CassandraProviderImpl.java @@ -6,10 +6,6 @@ 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; /** @@ -19,24 +15,22 @@ import com.p4square.grow.provider.JsonEncodedProvider; */ 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) { + public CassandraProviderImpl(CassandraDatabase db, 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()); + String blob = mDb.getKey(key.getColumnFamily(), 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); + mDb.putKey(key.getColumnFamily(), key.getId(), key.getColumn(), blob); } } diff --git a/src/com/p4square/grow/backend/db/CassandraTrainingRecordProvider.java b/src/com/p4square/grow/backend/db/CassandraTrainingRecordProvider.java new file mode 100644 index 0000000..4face52 --- /dev/null +++ b/src/com/p4square/grow/backend/db/CassandraTrainingRecordProvider.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package com.p4square.grow.backend.db; + +import java.io.IOException; + +import com.p4square.grow.model.Playlist; +import com.p4square.grow.model.TrainingRecord; + +import com.p4square.grow.provider.JsonEncodedProvider; +import com.p4square.grow.provider.Provider; + +/** + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class CassandraTrainingRecordProvider implements Provider<String, TrainingRecord> { + private static final CassandraKey DEFAULT_PLAYLIST_KEY = new CassandraKey("strings", "defaultPlaylist", "value"); + + private static final String COLUMN_FAMILY = "training"; + private static final String PLAYLIST_KEY = "playlist"; + private static final String LAST_VIDEO_KEY = "lastVideo"; + + private final CassandraDatabase mDb; + private final Provider<CassandraKey, Playlist> mPlaylistProvider; + + public CassandraTrainingRecordProvider(CassandraDatabase db) { + mDb = db; + mPlaylistProvider = new CassandraProviderImpl<>(db, Playlist.class); + } + + @Override + public TrainingRecord get(String userid) throws IOException { + Playlist playlist = mPlaylistProvider.get(new CassandraKey(COLUMN_FAMILY, userid, PLAYLIST_KEY)); + + if (playlist == null) { + // We consider no playlist to mean no record whatsoever. + return null; + } + + TrainingRecord r = new TrainingRecord(); + r.setPlaylist(playlist); + r.setLastVideo(mDb.getKey(COLUMN_FAMILY, userid, LAST_VIDEO_KEY)); + + return r; + } + + @Override + public void put(String userid, TrainingRecord record) throws IOException { + String lastVideo = record.getLastVideo(); + Playlist playlist = record.getPlaylist(); + + mDb.putKey(COLUMN_FAMILY, userid, LAST_VIDEO_KEY, lastVideo); + mPlaylistProvider.put(new CassandraKey(COLUMN_FAMILY, userid, PLAYLIST_KEY), playlist); + } + + /** + * @return the default playlist stored in the database. + */ + public Playlist getDefaultPlaylist() throws IOException { + Playlist playlist = mPlaylistProvider.get(DEFAULT_PLAYLIST_KEY); + + if (playlist == null) { + playlist = new Playlist(); + } + + return playlist; + } +} diff --git a/src/com/p4square/grow/backend/resources/Playlist.java b/src/com/p4square/grow/backend/resources/Playlist.java deleted file mode 100644 index f3d2f08..0000000 --- a/src/com/p4square/grow/backend/resources/Playlist.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2013 Jesse Morgan - */ - -package com.p4square.grow.backend.resources; - -import java.io.IOException; - -import java.util.HashMap; -import java.util.Map; - -import org.codehaus.jackson.map.ObjectMapper; -import org.codehaus.jackson.type.TypeReference; - -import com.p4square.grow.backend.db.CassandraDatabase; - -/** - * - * @author Jesse Morgan <jesse@jesterpm.net> - */ -class Playlist { - private static final ObjectMapper MAPPER = new ObjectMapper(); - - /** - * Load a Playlist from the database. - */ - public static Playlist load(CassandraDatabase db, String userId) throws IOException { - String playlistString = db.getKey("training", userId, "playlist"); - - if (playlistString == null) { - return null; - } - - Map<String, Map<String, VideoRecord>> playlist = - MAPPER.readValue(playlistString, new TypeReference<Map<String, Map<String, VideoRecord>>>() { }); - - return new Playlist(playlist); - - } - - /** - * Persist the Playlist for the given user. - * @return The String serialization of the playlist. - */ - public static String save(CassandraDatabase db, String userId, Playlist playlist) throws IOException { - String playlistString = MAPPER.writeValueAsString(playlist.mPlaylist); - db.putKey("training", userId, "playlist", playlistString); - return playlistString; - } - - - private Map<String, Map<String, VideoRecord>> mPlaylist; - - /** - * Construct an empty playlist. - */ - public Playlist() { - mPlaylist = new HashMap<String, Map<String, VideoRecord>>(); - } - - /** - * Constructor for database initialization. - */ - private Playlist(Map<String, Map<String, VideoRecord>> playlist) { - mPlaylist = playlist; - } - - public VideoRecord find(String videoId) { - for (Map<String, VideoRecord> chapter : mPlaylist.values()) { - VideoRecord r = chapter.get(videoId); - - if (r != null) { - return r; - } - } - - return null; - } - - /** - * Add a video to the playlist. - */ - public VideoRecord add(String chapter, String videoId) { - Map<String, VideoRecord> chapterMap = mPlaylist.get(chapter); - - if (chapterMap == null) { - chapterMap = new HashMap<String, VideoRecord>(); - mPlaylist.put(chapter, chapterMap); - } - - VideoRecord r = new VideoRecord(); - chapterMap.put(videoId, r); - return r; - } - - /** - * @return The last chapter to be completed. - */ - public Map<String, Boolean> getChapterStatuses() { - Map<String, Boolean> completed = new HashMap<String, Boolean>(); - - for (String chapter : mPlaylist.keySet()) { - completed.put(chapter, isChapterComplete(chapter)); - } - - return completed; - } - - public boolean isChapterComplete(String chapterId) { - boolean complete = true; - - Map<String, VideoRecord> chapter = mPlaylist.get(chapterId); - if (chapter != null) { - for (VideoRecord r : chapter.values()) { - if (r.getRequired() && !r.getComplete()) { - return false; - } - } - } - - return complete; - } - - @Override - public String toString() { - try { - return MAPPER.writeValueAsString(mPlaylist); - - } catch (IOException e) { - return super.toString(); - } - } - -} diff --git a/src/com/p4square/grow/backend/resources/SurveyResource.java b/src/com/p4square/grow/backend/resources/SurveyResource.java index 83c4cad..497978f 100644 --- a/src/com/p4square/grow/backend/resources/SurveyResource.java +++ b/src/com/p4square/grow/backend/resources/SurveyResource.java @@ -9,7 +9,7 @@ import java.io.IOException; import java.util.Map; import java.util.HashMap; -import org.codehaus.jackson.map.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper; import org.restlet.data.MediaType; import org.restlet.data.Status; diff --git a/src/com/p4square/grow/backend/resources/SurveyResultsResource.java b/src/com/p4square/grow/backend/resources/SurveyResultsResource.java index 91d4d0f..e87126d 100644 --- a/src/com/p4square/grow/backend/resources/SurveyResultsResource.java +++ b/src/com/p4square/grow/backend/resources/SurveyResultsResource.java @@ -10,7 +10,7 @@ import java.util.HashMap; import com.netflix.astyanax.model.Column; import com.netflix.astyanax.model.ColumnList; -import org.codehaus.jackson.map.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper; import org.restlet.data.MediaType; import org.restlet.data.Status; diff --git a/src/com/p4square/grow/backend/resources/TrainingRecordResource.java b/src/com/p4square/grow/backend/resources/TrainingRecordResource.java index 6de9507..e42456e 100644 --- a/src/com/p4square/grow/backend/resources/TrainingRecordResource.java +++ b/src/com/p4square/grow/backend/resources/TrainingRecordResource.java @@ -14,7 +14,7 @@ import java.util.HashMap; import com.netflix.astyanax.model.Column; import com.netflix.astyanax.model.ColumnList; -import org.codehaus.jackson.map.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper; import org.restlet.data.MediaType; import org.restlet.data.Status; @@ -22,11 +22,21 @@ 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.backend.db.CassandraDatabase; +import com.p4square.grow.model.Playlist; +import com.p4square.grow.model.VideoRecord; +import com.p4square.grow.model.TrainingRecord; + +import com.p4square.grow.provider.Provider; +import com.p4square.grow.provider.ProvidesTrainingRecords; +import com.p4square.grow.provider.JsonEncodedProvider; + import com.p4square.grow.model.Score; /** @@ -43,23 +53,41 @@ public class TrainingRecordResource extends ServerResource { SUMMARY, VIDEO } - private GrowBackend mBackend; private CassandraDatabase mDb; + private Provider<String, TrainingRecord> mTrainingRecordProvider; private RequestType mRequestType; private String mUserId; private String mVideoId; + private TrainingRecord mRecord; @Override public void doInit() { super.doInit(); - mBackend = (GrowBackend) getApplication(); - mDb = mBackend.getDatabase(); + mDb = ((GrowBackend) getApplication()).getDatabase(); + mTrainingRecordProvider = ((ProvidesTrainingRecords) getApplication()).getTrainingRecordProvider(); mUserId = getAttribute("userId"); mVideoId = getAttribute("videoId"); + try { + Playlist defaultPlaylist = ((GrowBackend) getApplication()).getDefaultPlaylist(); + + mRecord = mTrainingRecordProvider.get(mUserId); + if (mRecord == null) { + mRecord = new TrainingRecord(); + mRecord.setPlaylist(defaultPlaylist); + } 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; @@ -71,24 +99,35 @@ public class TrainingRecordResource extends ServerResource { */ @Override protected Representation get() { - String result = null; + JacksonRepresentation<?> rep = null; + + if (mRecord == null) { + setStatus(Status.SERVER_ERROR_INTERNAL); + return null; + } switch (mRequestType) { case VIDEO: - result = mDb.getKey("training", mUserId, mVideoId); + VideoRecord video = mRecord.getPlaylist().find(mVideoId); + if (video == null) { + break; // Fall through and return 404 + } + rep = new JacksonRepresentation<VideoRecord>(video); break; case SUMMARY: - result = buildSummary(); + rep = new JacksonRepresentation<TrainingRecord>(mRecord); break; } - if (result == null) { + if (rep == null) { setStatus(Status.CLIENT_ERROR_NOT_FOUND); return null; - } - return new StringRepresentation(result); + } else { + rep.setObjectMapper(JsonEncodedProvider.MAPPER); + return rep; + } } /** @@ -96,27 +135,37 @@ public class TrainingRecordResource extends ServerResource { */ @Override protected Representation put(Representation entity) { - boolean success = false; + if (mRecord == null) { + setStatus(Status.SERVER_ERROR_INTERNAL); + return null; + } switch (mRequestType) { case VIDEO: try { - mDb.putKey("training", mUserId, mVideoId, entity.getText()); - mDb.putKey("training", mUserId, "lastVideo", mVideoId); - - Playlist playlist = Playlist.load(mDb, mUserId); - if (playlist != null) { - VideoRecord r = playlist.find(mVideoId); - if (r != null && !r.getComplete()) { - r.complete(); - Playlist.save(mDb, mUserId, playlist); - } + 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); } - success = true; + setStatus(Status.SUCCESS_NO_CONTENT); } catch (Exception e) { LOG.warn("Caught exception updating training record: " + e.getMessage(), e); + setStatus(Status.SERVER_ERROR_INTERNAL); } break; @@ -124,116 +173,7 @@ public class TrainingRecordResource extends ServerResource { 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 watch video - final String lastVideo = mDb.getKey("training", mUserId, "lastVideo"); - if (lastVideo != null) { - sb.append("\"lastVideo\": \"" + lastVideo + "\", "); - } - - // Get the user's video history - 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()) || - "playlist".equals(c.getName())) { - continue; - } - - if (first) { - sb.append("\"" + c.getName() + "\": "); - first = false; - } else { - sb.append(", \"" + c.getName() + "\": "); - } - - sb.append(c.getStringValue()); - } - } - sb.append(" }"); - - // Get the user's playlist - try { - Playlist playlist = Playlist.load(mDb, mUserId); - if (playlist == null) { - playlist = createInitialPlaylist(); - } - - sb.append(", \"playlist\": "); - sb.append(playlist.toString()); - - // Last Completed Section - Map<String, Boolean> chapters = playlist.getChapterStatuses(); - String chaptersString = MAPPER.writeValueAsString(chapters); - sb.append(", \"chapters\":"); - sb.append(chaptersString); - - - } catch (IOException e) { - LOG.warn("IOException loading playlist for user " + mUserId, e); - } - - - sb.append(" }"); - return sb.toString(); - } - - /** - * Create the user's initial playlist. - * - * @return Returns the String representation of the initial playlist. - */ - private Playlist createInitialPlaylist() throws IOException { - Playlist playlist = new Playlist(); - - // Get assessment score - String summaryString = mDb.getKey("assessments", mUserId, "summary"); - if (summaryString == null) { - return null; - } - Map<?,?> summary = MAPPER.readValue(summaryString, Map.class); - double score = (Double) summary.get("score"); - - // Get videos for each section and build playlist - for (String chapter : CHAPTERS) { - 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 = score < Score.numericScore(chapter) + 1; - } - - ColumnList<String> row = mDb.getRow("strings", "/training/" + chapter); - if (!row.isEmpty()) { - for (Column<String> c : row) { - VideoRecord r = playlist.add(chapter, c.getName()); - r.setRequired(required); - } - } - } - - Playlist.save(mDb, mUserId, playlist); - - return playlist; - } } diff --git a/src/com/p4square/grow/backend/resources/VideoRecord.java b/src/com/p4square/grow/backend/resources/VideoRecord.java deleted file mode 100644 index 2ba28c3..0000000 --- a/src/com/p4square/grow/backend/resources/VideoRecord.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2013 Jesse Morgan - */ - -package com.p4square.grow.backend.resources; - -import java.util.Date; - -/** - * Simple bean containing video completion data. - * - * @author Jesse Morgan <jesse@jesterpm.net> - */ -class VideoRecord { - private boolean mComplete; - private boolean mRequired; - private Date mCompletionDate; - - public VideoRecord() { - mComplete = false; - mRequired = true; - mCompletionDate = null; - } - - public boolean getComplete() { - return mComplete; - } - - public void setComplete(boolean complete) { - mComplete = complete; - } - - public boolean getRequired() { - return mRequired; - } - - public void setRequired(boolean complete) { - mRequired = complete; - } - - public Date getCompletionDate() { - return mCompletionDate; - } - - public void setCompletionDate(Date date) { - mCompletionDate = date; - } - - /** - * Convenience method to mark a video complete. - */ - public void complete() { - mComplete = true; - mCompletionDate = new Date(); - } -} |