diff options
Diffstat (limited to 'src/main/java/com/p4square/grow/frontend/SurveyPageResource.java')
-rw-r--r-- | src/main/java/com/p4square/grow/frontend/SurveyPageResource.java | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/src/main/java/com/p4square/grow/frontend/SurveyPageResource.java b/src/main/java/com/p4square/grow/frontend/SurveyPageResource.java new file mode 100644 index 0000000..3575fe3 --- /dev/null +++ b/src/main/java/com/p4square/grow/frontend/SurveyPageResource.java @@ -0,0 +1,343 @@ +/* + * Copyright 2013 Jesse Morgan + */ + +package com.p4square.grow.frontend; + +import java.io.IOException; + +import java.util.Map; +import java.util.HashMap; + +import freemarker.template.Template; + +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Status; +import org.restlet.ext.freemarker.TemplateRepresentation; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; +import org.restlet.resource.ServerResource; + +import org.apache.log4j.Logger; + +import com.p4square.fmfacade.json.JsonRequestClient; +import com.p4square.fmfacade.json.JsonResponse; +import com.p4square.fmfacade.json.ClientException; + +import com.p4square.fmfacade.FreeMarkerPageResource; + +import com.p4square.grow.config.Config; +import com.p4square.grow.model.Question; +import com.p4square.grow.model.UserRecord; +import com.p4square.grow.provider.DelegateProvider; +import com.p4square.grow.provider.JsonEncodedProvider; +import com.p4square.grow.provider.Provider; + +/** + * SurveyPageResource handles rendering the survey and processing user's answers. + * + * This resource expects the user to be authenticated and the ClientInfo User object + * to be populated. Each question is requested from the backend along with the + * user's previous answer. Each answer is sent to the backend and the user is redirected + * to the next question. After the last question the user is sent to his results. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class SurveyPageResource extends FreeMarkerPageResource { + private static final Logger LOG = Logger.getLogger(SurveyPageResource.class); + + private Config mConfig; + private Template mSurveyTemplate; + private JsonRequestClient mJsonClient; + private Provider<String, Question> mQuestionProvider; + private Provider<String, UserRecord> mUserRecordProvider; + + // Fields pertaining to this request. + private String mQuestionId; + private String mUserId; + + @Override + public void doInit() { + super.doInit(); + + GrowFrontend growFrontend = (GrowFrontend) getApplication(); + mConfig = growFrontend.getConfig(); + mSurveyTemplate = growFrontend.getTemplate("templates/survey.ftl"); + if (mSurveyTemplate == null) { + LOG.fatal("Could not find survey template."); + setStatus(Status.SERVER_ERROR_INTERNAL); + } + + mJsonClient = new JsonRequestClient(getContext().getClientDispatcher()); + mQuestionProvider = new DelegateProvider<String, String, Question>( + new JsonRequestProvider<Question>(getContext().getClientDispatcher(), + Question.class)) { + @Override + public String makeKey(String questionId) { + return getBackendEndpoint() + "/assessment/question/" + questionId; + } + }; + + mUserRecordProvider = new DelegateProvider<String, String, UserRecord>( + new JsonRequestProvider<UserRecord>(getContext().getClientDispatcher(), + UserRecord.class)) { + @Override + public String makeKey(String userid) { + return getBackendEndpoint() + "/accounts/" + userid; + } + }; + + mQuestionId = getAttribute("questionId"); + mUserId = getRequest().getClientInfo().getUser().getIdentifier(); + } + + /** + * Return a page with a survey question. + */ + @Override + protected Representation get() { + try { + // Get the current question. + if (mQuestionId == null) { + // Get user's current question + mQuestionId = getCurrentQuestionId(); + + if (mQuestionId != null) { + Question lastQuestion = getQuestion(mQuestionId); + return redirectToNextQuestion(lastQuestion, getAnswer(mQuestionId)); + } + } + + // If we don't have a current question, get the first one. + if (mQuestionId == null) { + mQuestionId = "first"; + } + + Question question = getQuestion(mQuestionId); + if (question == null) { + setStatus(Status.CLIENT_ERROR_NOT_FOUND); + return new ErrorPage("Could not find the question."); + } + + // Set the real question id if a meta-id was used (i.e. first) + mQuestionId = question.getId(); + + // Get any previous answer to the question + String selectedAnswer = getAnswer(mQuestionId); + + Map root = getRootObject(); + root.put("question", question); + root.put("selectedAnswerId", selectedAnswer); + + // Get the question count and compute progress + { + JsonResponse response = backendGet("/assessment/question/count"); + if (response.getStatus().isSuccess()) { + Map countData = response.getMap(); + if (countData != null) { + response = backendGet("/accounts/" + mUserId + "/assessment"); + if (response.getStatus().isSuccess()) { + Integer completed = (Integer) response.getMap().get("totalAnswers"); + Integer total = (Integer) countData.get("count"); + + if (completed != null && total != null && total != 0) { + root.put("percentComplete", String.valueOf((int) (100.0 * completed) / total)); + } + } + } + } + } + + return new TemplateRepresentation(mSurveyTemplate, root, MediaType.TEXT_HTML); + + } catch (Exception e) { + LOG.fatal("Could not render page: " + e.getMessage(), e); + setStatus(Status.SERVER_ERROR_INTERNAL); + return ErrorPage.RENDER_ERROR; + } + } + + /** + * Record a survey answer and redirect to the next question. + */ + @Override + protected Representation post(Representation entity) { + final Form form = new Form(entity); + final String answerId = form.getFirstValue("answer"); + final String direction = form.getFirstValue("direction"); + boolean justGoBack = false; // FIXME: Ugly hack + + if (mQuestionId == null || answerId == null || answerId.length() == 0) { + if ("previous".equals(direction)) { + // Just go back + justGoBack = true; + + } else { + // Something is wrong. + setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + return new ErrorPage("Question or answer messing."); + } + } + + try { + // Find the question + Question question = getQuestion(mQuestionId); + if (question == null) { + // User is answering a question which doesn't exist + setStatus(Status.CLIENT_ERROR_NOT_FOUND); + return new ErrorPage("Question not found."); + } + + // Store answer + if (!justGoBack) { + Map<String, String> answer = new HashMap<String, String>(); + answer.put("answerId", answerId); + JsonResponse response = backendPut("/accounts/" + mUserId + + "/assessment/answers/" + mQuestionId, answer); + + if (!response.getStatus().isSuccess()) { + // Something went wrong talking to the backend, error out. + LOG.fatal("Error recording survey answer " + response.getStatus()); + setStatus(Status.SERVER_ERROR_INTERNAL); + return ErrorPage.BACKEND_ERROR; + } + } + + // Find the next question or finish the assessment. + if ("previous".equals(direction)) { + return redirectToPreviousQuestion(question); + + } else { + return redirectToNextQuestion(question, answerId); + } + + } catch (Exception e) { + LOG.fatal("Could not render page: " + e.getMessage(), e); + setStatus(Status.SERVER_ERROR_INTERNAL); + return ErrorPage.RENDER_ERROR; + } + } + + private Question getQuestion(String id) { + try { + return mQuestionProvider.get(id); + + } catch (IOException e) { + LOG.warn("Error fetching question.", e); + return null; + } + } + + private String getAnswer(String questionId) { + try { + JsonResponse response = backendGet("/accounts/" + mUserId + "/assessment/answers/" + questionId); + if (response.getStatus().isSuccess()) { + return (String) response.getMap().get("answerId"); + } + + } catch (ClientException e) { + LOG.warn("Error fetching answer to question " + questionId, e); + } + + return null; + } + + private Representation redirectToNextQuestion(Question question, String answerid) { + String nextQuestionId = question.getNextQuestion(answerid); + + if (nextQuestionId == null) { + // Just finished the last question. Update the user's account + try { + UserRecord account = null; + try { + account = mUserRecordProvider.get(mUserId); + } catch (NotFoundException e) { + // User record doesn't exist, so create a new one. + account = new UserRecord(getRequest().getClientInfo().getUser()); + } + account.setLanding("training"); + mUserRecordProvider.put(mUserId, account); + } catch (IOException e) { + LOG.warn("IOException updating landing for " + mUserId, e); + } + + String nextPage = mConfig.getString("dynamicRoot", ""); + nextPage += "/account/assessment/results"; + getResponse().redirectSeeOther(nextPage); + return new StringRepresentation("Redirecting to " + nextPage); + } + + return redirectToQuestion(nextQuestionId); + } + + private Representation redirectToPreviousQuestion(Question question) { + String nextQuestionId = question.getPreviousQuestion(); + + if (nextQuestionId == null) { + nextQuestionId = (String) question.getId(); + } + + return redirectToQuestion(nextQuestionId); + } + + private Representation redirectToQuestion(String id) { + String nextPage = mConfig.getString("dynamicRoot", ""); + nextPage += "/account/assessment/question/" + id; + getResponse().redirectSeeOther(nextPage); + return new StringRepresentation("Redirecting to " + nextPage); + } + + private String getCurrentQuestionId() { + String id = null; + try { + JsonResponse response = backendGet("/accounts/" + mUserId + "/assessment"); + + if (response.getStatus().isSuccess()) { + return (String) response.getMap().get("lastAnswered"); + + } else { + LOG.warn("Failed to get assessment results: " + response.getStatus()); + } + + } catch (ClientException e) { + LOG.error("Exception getting assessment results.", e); + } + + return null; + } + + /** + * @return The backend endpoint URI + */ + private String getBackendEndpoint() { + return mConfig.getString("backendUri", "riap://component/backend"); + } + + /** + * Helper method to send a GET to the backend. + */ + private JsonResponse backendGet(final String uri) { + LOG.debug("Sending backend GET " + uri); + + final JsonResponse response = mJsonClient.get(getBackendEndpoint() + uri); + final Status status = response.getStatus(); + if (!status.isSuccess() && !Status.CLIENT_ERROR_NOT_FOUND.equals(status)) { + LOG.warn("Error making backend request for '" + uri + "'. status = " + response.getStatus().toString()); + } + + return response; + } + + protected JsonResponse backendPut(final String uri, final Map data) { + LOG.debug("Sending backend PUT " + uri); + + final JsonResponse response = mJsonClient.put(getBackendEndpoint() + uri, data); + final Status status = response.getStatus(); + if (!status.isSuccess() && !Status.CLIENT_ERROR_NOT_FOUND.equals(status)) { + LOG.warn("Error making backend request for '" + uri + "'. status = " + response.getStatus().toString()); + } + + return response; + } +} |