submissionResultFetcher =
+ tmcServerCommunicationTaskFactory.getSubmissionFetchTask(
+ submissionResponse.submissionUrl);
+
+ String submissionStatus = submissionResultFetcher.call();
+ JsonElement submission = new JsonParser().parse(submissionStatus);
+ if (isProcessing(submission)) {
+ logger.debug("Skillifier not done, sleeping for {}", DEFAULT_POLL_INTERVAL);
+ informObserver(0.3, "Waiting for response from skillifier");
+ Thread.sleep(DEFAULT_POLL_INTERVAL);
+ } else {
+ logger.debug("Skillifier done, parsing results");
+ informObserver(0.6, "Reading submission result");
+
+ Gson gson = new GsonBuilder().create();
+ SubmissionResult result = gson.fromJson(submissionStatus, AdaptiveSubmissionResult.class).toSubmissionResult();
+
+ logger.debug("Done parsing server response");
+ informObserver(1, "Successfully read submission results");
+
+ return result;
+ }
+ } catch (Exception ex) {
+ informObserver(1, "Error while waiting for response from server");
+ logger.warn("Error while updating submission status from server, continuing", ex);
+ }
+ }
+ */
+ }
+
+
+ private boolean isProcessing(JsonElement submissionStatus) {
+ return submissionStatus.getAsJsonObject().get("status").getAsString().equals("processing");
+ }
+}
diff --git a/src/main/java/fi/helsinki/cs/tmc/core/communication/TmcServerCommunicationTaskFactory.java b/src/main/java/fi/helsinki/cs/tmc/core/communication/TmcServerCommunicationTaskFactory.java
index 02d9fcd7..c9658928 100644
--- a/src/main/java/fi/helsinki/cs/tmc/core/communication/TmcServerCommunicationTaskFactory.java
+++ b/src/main/java/fi/helsinki/cs/tmc/core/communication/TmcServerCommunicationTaskFactory.java
@@ -2,16 +2,25 @@
import fi.helsinki.cs.tmc.core.communication.http.HttpTasks;
import fi.helsinki.cs.tmc.core.communication.http.UriUtils;
+import fi.helsinki.cs.tmc.core.communication.oauth2.Oauth;
+import fi.helsinki.cs.tmc.core.communication.serialization.AdaptiveExerciseParser;
import fi.helsinki.cs.tmc.core.communication.serialization.ByteArrayGsonSerializer;
import fi.helsinki.cs.tmc.core.communication.serialization.CourseInfoParser;
import fi.helsinki.cs.tmc.core.communication.serialization.CourseListParser;
+import fi.helsinki.cs.tmc.core.communication.serialization.ExerciseListParser;
import fi.helsinki.cs.tmc.core.communication.serialization.ReviewListParser;
+import fi.helsinki.cs.tmc.core.communication.serialization.SkillListParser;
import fi.helsinki.cs.tmc.core.configuration.TmcSettings;
import fi.helsinki.cs.tmc.core.domain.Course;
import fi.helsinki.cs.tmc.core.domain.Exercise;
+import fi.helsinki.cs.tmc.core.domain.OauthCredentials;
+import fi.helsinki.cs.tmc.core.domain.Organization;
import fi.helsinki.cs.tmc.core.domain.Review;
+import fi.helsinki.cs.tmc.core.domain.Skill;
+import fi.helsinki.cs.tmc.core.domain.Theme;
import fi.helsinki.cs.tmc.core.domain.submission.FeedbackAnswer;
import fi.helsinki.cs.tmc.core.exceptions.FailedHttpResponseException;
+import fi.helsinki.cs.tmc.core.exceptions.NotLoggedInException;
import fi.helsinki.cs.tmc.core.exceptions.ObsoleteClientException;
import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder;
import fi.helsinki.cs.tmc.core.utilities.JsonMaker;
@@ -24,6 +33,10 @@
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
+import org.apache.commons.io.IOUtils;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -35,6 +48,8 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import java.util.zip.GZIPOutputStream;
/**
@@ -42,32 +57,45 @@
*/
public class TmcServerCommunicationTaskFactory {
- public static final int API_VERSION = 7;
+ private static final Logger LOG = Logger.getLogger(
+ TmcServerCommunicationTaskFactory.class.getName());
+ public static final int API_VERSION = 8;
private TmcSettings settings;
+ private Oauth oauth;
+ private AdaptiveExerciseParser adaptiveExerciseParser;
private CourseListParser courseListParser;
private CourseInfoParser courseInfoParser;
+ private ExerciseListParser exerciseListParser;
private ReviewListParser reviewListParser;
private String clientVersion;
+ private SkillListParser skillListParser;
public TmcServerCommunicationTaskFactory() {
- this(TmcSettingsHolder.get());
+ this(TmcSettingsHolder.get(), Oauth.getInstance());
}
- public TmcServerCommunicationTaskFactory(TmcSettings settings) {
- this(settings, new CourseListParser(), new CourseInfoParser(), new ReviewListParser());
+ public TmcServerCommunicationTaskFactory(TmcSettings settings, Oauth oauth) {
+ this(settings, oauth, new AdaptiveExerciseParser(), new CourseListParser(),
+ new CourseInfoParser(), new ReviewListParser());
}
public TmcServerCommunicationTaskFactory(
- TmcSettings settings,
- CourseListParser courseListParser,
- CourseInfoParser courseInfoParser,
- ReviewListParser reviewListParser) {
+ TmcSettings settings,
+ Oauth oauth,
+ AdaptiveExerciseParser adaptiveExerciseParser,
+ CourseListParser courseListParser,
+ CourseInfoParser courseInfoParser,
+ ReviewListParser reviewListParser) {
this.settings = settings;
+ this.oauth = oauth;
+ this.adaptiveExerciseParser = adaptiveExerciseParser;
this.courseListParser = courseListParser;
this.courseInfoParser = courseInfoParser;
this.reviewListParser = reviewListParser;
this.clientVersion = getClientVersion();
+ this.exerciseListParser = new ExerciseListParser();
+ this.skillListParser = new SkillListParser();
}
private static String getClientVersion() {
@@ -78,46 +106,81 @@ public void setSettings(TmcSettings settings) {
this.settings = settings;
}
- private URI getCourseListUrl() {
+ /**
+ * Returns a Callable that calls the given Callable.
+ *
+ * If the call fails once, the oauth token is refreshed and the call is done again.
+ * @param return type of the callable
+ * @param callable Callable to be wrapped
+ * @return The given Callable wrapped in another Callable
+ */
+ private Callable wrapWithNotLoggedInException(final Callable callable) {
+ return new Callable() {
+ @Override
+ public T call() throws Exception {
+ try {
+ return callable.call();
+ } catch (FailedHttpResponseException e) {
+ LOG.log(Level.WARNING,
+ "Communication with the server failed!");
+ throw new NotLoggedInException();
+ }
+ }
+ };
+ }
+
+ private URI getCourseListUrl()
+ throws OAuthSystemException, OAuthProblemException, NotLoggedInException {
String serverAddress = settings.getServerAddress();
String url;
+ // "/core/org/" => "/core/org"
+ String urlLastPart = "api/v" + API_VERSION + "/core/org" + settings.getOrganization() + "/courses.json";
if (serverAddress.endsWith("/")) {
- url = settings.getServerAddress() + "courses.json";
+ url = serverAddress + urlLastPart;
} else {
- url = settings.getServerAddress() + "/courses.json";
+ url = serverAddress + "/" + urlLastPart;
}
return addApiCallQueryParameters(URI.create(url));
}
- private URI addApiCallQueryParameters(URI url) {
- url = UriUtils.withQueryParam(url, "api_version", "" + API_VERSION);
+ private URI addApiCallQueryParameters(URI url) throws NotLoggedInException {
url = UriUtils.withQueryParam(url, "client", settings.clientName());
url = UriUtils.withQueryParam(url, "client_version", clientVersion);
+ url = UriUtils.withQueryParam(url, "access_token", oauth.getToken());
return url;
}
- private HttpTasks createHttpTasks() {
- return new HttpTasks().setCredentials(settings.getUsername(), settings.getPassword());
- }
-
- public boolean hasEnoughSettings() {
- return !settings.getUsername().isEmpty()
- && !settings.getPassword().isEmpty()
- && !settings.getServerAddress().isEmpty();
+ public URI getSkillifierUrl(String addition) {
+ if (!addition.isEmpty()) {
+ return URI.create("http://localhost:3000/" + addition);
+ }
+ return URI.create("http://localhost:3000/");
}
- public boolean needsOnlyPassword() {
- return !settings.getUsername().isEmpty()
- && settings.getPassword().isEmpty()
- && !settings.getServerAddress().isEmpty();
+ public Callable getAdaptiveExercise(final Theme theme, final Course course)
+ throws OAuthSystemException, OAuthProblemException, NotLoggedInException {
+ return wrapWithNotLoggedInException(new Callable() {
+ @Override
+ public Exercise call() throws Exception {
+ try {
+ Callable download = new HttpTasks()
+ .getForText(getSkillifierUrl(course.getName() + "/" + theme.getName() + "/next.json?token=" + oauth.getToken()));
+ String json = download.call();
+ return adaptiveExerciseParser.parseFromJson(json);
+ } catch (Exception ex) {
+ LOG.log(Level.WARNING, "Downloading and parsing adaptive exercise URL failed.");
+ return null;
+ }
+ }
+ });
}
public Callable> getDownloadingCourseListTask() {
- final Callable download = createHttpTasks().getForText(getCourseListUrl());
- return new Callable>() {
+ return wrapWithNotLoggedInException(new Callable>() {
@Override
public List call() throws Exception {
try {
+ Callable download = new HttpTasks().getForText(getCourseListUrl());
String text = download.call();
return courseListParser.parseFromJson(text);
} catch (FailedHttpResponseException ex) {
@@ -125,35 +188,65 @@ public List call() throws Exception {
}
//TODO: Cancellable?
}
- };
+ });
}
- public Callable getFullCourseInfoTask(Course courseStub) {
- URI url = addApiCallQueryParameters(courseStub.getDetailsUrl());
- final Callable download = createHttpTasks().getForText(url);
- return new Callable() {
+ public Callable getFullCourseInfoTask(final Course courseStub) {
+ return wrapWithNotLoggedInException(new Callable() {
@Override
public Course call() throws Exception {
try {
- String text = download.call();
- return courseInfoParser.parseFromJson(text);
+ URI serverUrl = addApiCallQueryParameters(courseStub.getDetailsUrl());
+ Course returnedFromServer = getCourseInfo(serverUrl);
+ returnedFromServer.generateThemes();
+ try {
+ URI skillifierExercisesUrl = getSkillifierUrl("courses/" + courseStub.getName() + "/uexercises?token=" + oauth.getToken());
+ List returnedFromSkillifier = getExerciseList(skillifierExercisesUrl);
+ returnedFromServer.getExercises().addAll(returnedFromSkillifier);
+
+ URI skillifierSkillsUrl = getSkillifierUrl("courses/" + courseStub.getName() + "/skills?token=" + oauth.getToken());
+ List skillsFromSkillifier = getSkillList(skillifierSkillsUrl);
+ returnedFromServer.addSkillsToThemes(skillsFromSkillifier);
+ } catch (Exception e) {
+ LOG.log(Level.WARNING, "Downloading adaptive exercise info from skillifier failed.");
+ }
+
+ return returnedFromServer;
} catch (FailedHttpResponseException ex) {
return checkForObsoleteClient(ex);
}
}
//TODO: Cancellable?
- };
+ });
+ }
+
+ private List getSkillList(URI uri) throws Exception {
+ final Callable downloadFromServer = new HttpTasks().getForText(uri);
+ String jsonFromServer = downloadFromServer.call();
+ return skillListParser.parseFromJson(jsonFromServer);
+ }
+
+ private List getExerciseList(URI uri) throws Exception {
+ final Callable downloadFromServer = new HttpTasks().getForText(uri);
+ String jsonFromServer = downloadFromServer.call();
+ return exerciseListParser.parseFromJson(jsonFromServer);
}
- public Callable getUnlockingTask(Course course) {
- Map params = Collections.emptyMap();
- final Callable download =
- createHttpTasks().postForText(getUnlockUrl(course), params);
- return new Callable() {
+ private Course getCourseInfo(URI uri) throws Exception {
+ final Callable downloadFromServer = new HttpTasks().getForText(uri);
+ String jsonFromServer = downloadFromServer.call();
+ return courseInfoParser.parseFromJson(jsonFromServer);
+ }
+
+ public Callable getUnlockingTask(final Course course) {
+ final Map params = Collections.emptyMap();
+ return wrapWithNotLoggedInException(new Callable() {
@Override
public Void call() throws Exception {
try {
+ final Callable download = new HttpTasks()
+ .postForText(getUnlockUrl(course), params);
download.call();
return null;
} catch (FailedHttpResponseException ex) {
@@ -162,67 +255,104 @@ public Void call() throws Exception {
}
//TODO: Cancellable?
- };
+ });
}
- private URI getUnlockUrl(Course course) {
+ private URI getUnlockUrl(Course course) throws NotLoggedInException {
return addApiCallQueryParameters(course.getUnlockUrl());
}
+ public Callable getDownloadingAdaptiveExerciseZipTask(Exercise exercise) throws NotLoggedInException {
+ exercise.setZipUrl(URI.create(exercise.getZipUrl() + "?token=" + oauth.getToken()));
+ exercise.setDownloadUrl(exercise.getZipUrl());
+ return getDownloadingExerciseZipTask(exercise);
+ }
+
public Callable getDownloadingExerciseZipTask(Exercise exercise) {
URI zipUrl = exercise.getDownloadUrl();
- return createHttpTasks().getForBinary(zipUrl);
+ return new HttpTasks().getForBinary(zipUrl);
}
public Callable getDownloadingExerciseSolutionZipTask(Exercise exercise) {
URI zipUrl = exercise.getSolutionDownloadUrl();
- return createHttpTasks().getForBinary(zipUrl);
+ return new HttpTasks().getForBinary(zipUrl);
}
+ public Callable getSubmittingExerciseToSkillifierTask(
+ final Exercise exercise, final byte[] byteToSend, Map extraParams) {
+
+ final Map params = new LinkedHashMap<>();
+ params.put("client_time", "" + (System.currentTimeMillis() / 1000L));
+ params.put("client_nanotime", "" + System.nanoTime());
+ params.putAll(extraParams);
+
+ return wrapWithNotLoggedInException(new Callable() {
+ @Override
+ public String call() throws Exception {
+ String response;
+ try {
+ final URI submitUrl = getSkillifierUrl("/submitZip?token=" + oauth.getToken());
+ final Callable upload = new HttpTasks()
+ .uploadRawDataForTextDownload(submitUrl, params,
+ byteToSend);
+ response = upload.call();
+ } catch (FailedHttpResponseException ex) {
+ return checkForObsoleteClient(ex);
+ }
+ return response;
+ }
+
+ //TODO: Cancellable?
+ });
+ }
+
+
public Callable getSubmittingExerciseTask(
final Exercise exercise, final byte[] sourceZip, Map extraParams) {
- final URI submitUrl = addApiCallQueryParameters(exercise.getReturnUrl());
- Map params = new LinkedHashMap<>();
+ final Map params = new LinkedHashMap<>();
params.put("client_time", "" + (System.currentTimeMillis() / 1000L));
params.put("client_nanotime", "" + System.nanoTime());
params.putAll(extraParams);
- final Callable upload =
- createHttpTasks()
- .uploadFileForTextDownload(
- submitUrl, params, "submission[file]", sourceZip);
-
- return new Callable() {
+ return wrapWithNotLoggedInException(new Callable() {
@Override
public SubmissionResponse call() throws Exception {
String response;
try {
+ final URI submitUrl = addApiCallQueryParameters(exercise.getReturnUrl());
+ final Callable upload = new HttpTasks()
+ .uploadFileForTextDownload(submitUrl, params,
+ "submission[file]", sourceZip);
response = upload.call();
} catch (FailedHttpResponseException ex) {
return checkForObsoleteClient(ex);
}
JsonObject respJson = new JsonParser().parse(response).getAsJsonObject();
- if (respJson.get("error") != null) {
- throw new RuntimeException(
- "Server responded with error: " + respJson.get("error"));
- } else if (respJson.get("submission_url") != null) {
- try {
- URI submissionUrl = new URI(respJson.get("submission_url").getAsString());
- URI pasteUrl = new URI(respJson.get("paste_url").getAsString());
- return new SubmissionResponse(submissionUrl, pasteUrl);
- } catch (Exception e) {
- throw new RuntimeException(
- "Server responded with malformed " + "submission url");
- }
- } else {
- throw new RuntimeException("Server returned unknown response");
- }
+ return getSubmissionResponse(respJson);
}
//TODO: Cancellable?
- };
+ });
+ }
+
+ private SubmissionResponse getSubmissionResponse(JsonObject respJson) {
+ if (respJson.get("error") != null) {
+ throw new RuntimeException(
+ "Server responded with error: " + respJson.get("error"));
+ } else if (respJson.get("submission_url") != null) {
+ try {
+ URI submissionUrl = new URI(respJson.get("submission_url").getAsString());
+ URI pasteUrl = new URI(respJson.get("paste_url").getAsString());
+ return new SubmissionResponse(submissionUrl, pasteUrl);
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Server responded with malformed " + "submission url");
+ }
+ } else {
+ throw new RuntimeException("Server returned unknown response");
+ }
}
public static class SubmissionResponse {
@@ -236,17 +366,17 @@ public SubmissionResponse(URI submissionUrl, URI pasteUrl) {
}
}
- public Callable getSubmissionFetchTask(URI submissionUrl) {
- return createHttpTasks().getForText(submissionUrl);
+ public Callable getSubmissionFetchTask(URI submissionUrl) throws NotLoggedInException {
+ return new HttpTasks().getForText(addApiCallQueryParameters(submissionUrl));
}
- public Callable> getDownloadingReviewListTask(Course course) {
- URI url = addApiCallQueryParameters(course.getReviewsUrl());
- final Callable download = createHttpTasks().getForText(url);
- return new Callable>() {
+ public Callable> getDownloadingReviewListTask(final Course course) {
+ return wrapWithNotLoggedInException(new Callable>() {
@Override
public List call() throws Exception {
try {
+ URI url = addApiCallQueryParameters(course.getReviewsUrl());
+ final Callable download = new HttpTasks().getForText(url);
String text = download.call();
return reviewListParser.parseFromJson(text);
} catch (FailedHttpResponseException ex) {
@@ -255,12 +385,11 @@ public List call() throws Exception {
}
//TODO: Cancellable?
- };
+ });
}
- public Callable getMarkingReviewAsReadTask(Review review, boolean read) {
- URI url = addApiCallQueryParameters(URI.create(review.getUpdateUrl() + ".json"));
- Map params = new HashMap<>();
+ public Callable getMarkingReviewAsReadTask(final Review review, boolean read) {
+ final Map params = new HashMap<>();
params.put("_method", "put");
if (read) {
params.put("mark_as_read", "1");
@@ -268,22 +397,22 @@ public Callable getMarkingReviewAsReadTask(Review review, boolean read) {
params.put("mark_as_unread", "1");
}
- final Callable task = createHttpTasks().postForText(url, params);
- return new Callable() {
+ return wrapWithNotLoggedInException(new Callable() {
@Override
public Void call() throws Exception {
+ URI url = addApiCallQueryParameters(URI.create(review.getUpdateUrl() + ".json"));
+ final Callable task = new HttpTasks().postForText(url, params);
task.call();
return null;
}
//TODO: Cancellable?
- };
+ });
}
- public Callable getFeedbackAnsweringJob(URI answerUrl, List answers) {
- final URI submitUrl = addApiCallQueryParameters(answerUrl);
-
- Map params = new HashMap<>();
+ public Callable getFeedbackAnsweringJob(final URI answerUrl,
+ List answers) {
+ final Map params = new HashMap<>();
for (int i = 0; i < answers.size(); ++i) {
String keyPrefix = "answers[" + i + "]";
FeedbackAnswer answer = answers.get(i);
@@ -291,12 +420,13 @@ public Callable getFeedbackAnsweringJob(URI answerUrl, List upload = createHttpTasks().postForText(submitUrl, params);
-
- return new Callable() {
+ return wrapWithNotLoggedInException(new Callable() {
@Override
public String call() throws Exception {
try {
+ final URI submitUrl = addApiCallQueryParameters(answerUrl);
+ final Callable upload = new HttpTasks()
+ .postForText(submitUrl, params);
return upload.call();
} catch (FailedHttpResponseException ex) {
return checkForObsoleteClient(ex);
@@ -304,35 +434,67 @@ public String call() throws Exception {
}
//TODO: Cancellable?
- };
+ });
}
- public Callable