1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
|
/*
* 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.provider.QuestionProvider;
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;
// 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 QuestionProvider<String>(new JsonRequestProvider<Question>(getContext().getClientDispatcher(), Question.class)) {
@Override
public String makeKey(String questionId) {
return getBackendEndpoint() + "/assessment/question/" + questionId;
}
};
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 Map<?, ?> getAccount(String id) {
try {
Map<?, ?> account = null;
JsonResponse response = backendGet("/accounts/" + id);
if (!response.getStatus().isSuccess()) {
return null;
}
account = response.getMap();
return account;
} catch (ClientException e) {
LOG.warn("Error fetching account.", e);
return null;
}
}
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
Map account = getAccount(mUserId);
if (account == null) {
account = new HashMap();
}
account.put("landing", "training");
backendPut("/accounts/" + mUserId, account);
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;
}
}
|