diff --git a/README.md b/README.md index bf392659..294339d6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ There are two projects in this [Monorepo](https://en.wikipedia.org/wiki/Monorepo Both the xAPI Client and xAPI Model use a [fluent interface](https://en.wikipedia.org/wiki/Fluent_interface). Objects are [immutable](https://en.wikipedia.org/wiki/Immutable_object). -[CheckStyle](https://checkstyle.sourceforge.io) is used to enforce the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html). Sonar performs automatic pull request reviews. [CodeQL](https://codeql.github.com) scans for vulnerabilities. The number of bugs, code smells and vulnerabilities in the codebase can be viewed in SonarCloud. The code coverage and code duplication percentages can also be viewed in SonarCloud. Over two-hundred unit tests ensure conformance with the xAPI specification. +[CheckStyle](https://checkstyle.sourceforge.io) is used to enforce the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html). Sonar performs automatic pull request reviews. [CodeQL](https://codeql.github.com) scans for vulnerabilities. The number of bugs, code smells and vulnerabilities in the codebase can be viewed in SonarCloud. The code coverage and code duplication percentages can also be viewed in SonarCloud. Over three-hundred unit tests ensure conformance with the xAPI specification. ## xAPI Java Client diff --git a/samples/get-activity/pom.xml b/samples/get-activity/pom.xml new file mode 100644 index 00000000..c1eed501 --- /dev/null +++ b/samples/get-activity/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + dev.learning.xapi.samples + xapi-samples-build + 1.0.6-SNAPSHOT + + get-activity + Get xAPI Activity Sample + Get xAPI Activity + + + dev.learning.xapi + xapi-client + + + dev.learning.xapi.samples + core + + + diff --git a/samples/get-activity/src/main/java/dev/learning/xapi/samples/getactivity/GetActivityApplication.java b/samples/get-activity/src/main/java/dev/learning/xapi/samples/getactivity/GetActivityApplication.java new file mode 100644 index 00000000..82372e67 --- /dev/null +++ b/samples/get-activity/src/main/java/dev/learning/xapi/samples/getactivity/GetActivityApplication.java @@ -0,0 +1,80 @@ +package dev.learning.xapi.samples.getactivity; + +import dev.learning.xapi.client.XapiClient; +import dev.learning.xapi.model.Activity; +import dev.learning.xapi.model.Verb; +import java.net.URI; +import java.util.Locale; +import java.util.UUID; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.http.ResponseEntity; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Sample using xAPI client to get an activity. + * + * @author Thomas Turrell-Croft + */ +@SpringBootApplication +public class GetActivityApplication implements CommandLineRunner { + + private final XapiClient client; + + /** + * Constructor for application. In this sample the WebClient.Builder instance is injected by the + * Spring Framework. + */ + public GetActivityApplication(WebClient.Builder webClientBuilder) { + + webClientBuilder + // Change for the URL of your LRS + .baseUrl("https://example.com/xapi/") + // Set the Authorization value + .defaultHeader("Authorization", "") + + .build(); + + + client = new XapiClient(webClientBuilder); + } + + public static void main(String[] args) { + SpringApplication.run(GetActivityApplication.class, args).close(); + } + + @Override + public void run(String... args) throws Exception { + + // Post statement for later retrieval of activity + postStatement(); + + // Get Activity + ResponseEntity response = client + .getActivity(r -> r.activityId(URI.create("https://example.com/activity/simplestatement"))) + .block(); + + // Print the returned activity to the console + System.out.println(response.getBody()); + } + + private UUID postStatement() { + + // Post a statement + ResponseEntity< + UUID> response = + client + .postStatement(r -> r.statement( + s -> s.actor(a -> a.name("A N Other").mbox("mailto:another@example.com")) + + .verb(Verb.ATTEMPTED) + + .activityObject(o -> o.id("https://example.com/activity/simplestatement") + .definition(d -> d.addName(Locale.ENGLISH, "Simple Statement"))))) + .block(); + + return response.getBody(); + } + +} diff --git a/samples/pom.xml b/samples/pom.xml index c6a10524..5cc5d176 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -34,7 +34,8 @@ core delete-agent-profile - delete-state + delete-state + get-activity get-agent-profile get-agent-profiles get-state diff --git a/xapi-client/src/main/java/dev/learning/xapi/client/GetActivityRequest.java b/xapi-client/src/main/java/dev/learning/xapi/client/GetActivityRequest.java new file mode 100644 index 00000000..a07c3731 --- /dev/null +++ b/xapi-client/src/main/java/dev/learning/xapi/client/GetActivityRequest.java @@ -0,0 +1,41 @@ +package dev.learning.xapi.client; + +import java.net.URI; +import java.util.Map; +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; +import org.springframework.http.HttpMethod; +import org.springframework.web.util.UriBuilder; + +/** + * Request for getting a single Activity. + * + * @see Full + * Activity Object GET + * + * @author Thomas Turrell-Croft + */ +@Getter +@Builder +public class GetActivityRequest implements Request { + + @NonNull + private URI activityId; + + @Override + public UriBuilder url(UriBuilder uriBuilder, Map queryParams) { + + queryParams.put("activityId", activityId); + + return uriBuilder.path("/activities").queryParam("activityId", "{activityId}"); + } + + @Override + public HttpMethod getMethod() { + + return HttpMethod.GET; + } + +} diff --git a/xapi-client/src/main/java/dev/learning/xapi/client/XapiClient.java b/xapi-client/src/main/java/dev/learning/xapi/client/XapiClient.java index 129b87a4..f378bd70 100644 --- a/xapi-client/src/main/java/dev/learning/xapi/client/XapiClient.java +++ b/xapi-client/src/main/java/dev/learning/xapi/client/XapiClient.java @@ -4,6 +4,7 @@ package dev.learning.xapi.client; +import dev.learning.xapi.model.Activity; import dev.learning.xapi.model.Statement; import dev.learning.xapi.model.StatementResult; import java.util.HashMap; @@ -903,4 +904,44 @@ public Mono> getAgentProfiles( } + /** + * Loads the complete Activity Object specified. + * + * @param request The parameters of the get activity request + * + * @return the ResponseEntity + */ + public Mono> getActivity(GetActivityRequest request) { + + Map queryParams = new HashMap<>(); + + return this.webClient + + .method(request.getMethod()) + + .uri(u -> request.url(u, queryParams).build(queryParams)) + + .retrieve() + + .toEntity(Activity.class); + + } + + /** + * Loads the complete Activity Object specified. + * + * @param request The Consumer Builder for the get activity request + * + * @return the ResponseEntity + */ + public Mono> getActivity(Consumer request) { + + final GetActivityRequest.Builder builder = GetActivityRequest.builder(); + + request.accept(builder); + + return getActivity(builder.build()); + + } + } diff --git a/xapi-client/src/test/java/dev/learning/xapi/client/XapiClientTests.java b/xapi-client/src/test/java/dev/learning/xapi/client/XapiClientTests.java index b3d8552b..1cc2035f 100644 --- a/xapi-client/src/test/java/dev/learning/xapi/client/XapiClientTests.java +++ b/xapi-client/src/test/java/dev/learning/xapi/client/XapiClientTests.java @@ -6,9 +6,11 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsInstanceOf.instanceOf; +import dev.learning.xapi.model.Activity; import dev.learning.xapi.model.Statement; import dev.learning.xapi.model.StatementFormat; import dev.learning.xapi.model.Verb; +import java.net.URI; import java.time.Instant; import java.util.Locale; import java.util.UUID; @@ -1666,6 +1668,61 @@ void whenGettingProfilesWithSinceParameterThenPathIsExpected() throws Interrupte "/agents/profile?agent=%7B%22name%22%3A%22A%20N%20Other%22%2C%22mbox%22%3A%22mailto%3Aanother%40example.com%22%7D&since=2016-01-01T00%3A00%3A00Z")); } + // Get Statement + + @Test + void whenGettingActivityThenMethodIsGet() throws InterruptedException { + + mockWebServer.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK")); + + // When Getting Activity + client + .getActivity(r -> r.activityId(URI.create("https://example.com/activity/simplestatement"))) + .block(); + + RecordedRequest recordedRequest = mockWebServer.takeRequest(); + + // Then Method Is Get + assertThat(recordedRequest.getMethod(), is("GET")); + } + + @Test + void whenGettingActivityThenPathIsExpected() throws InterruptedException { + + mockWebServer.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK")); + + // When Getting Activity + client + .getActivity(r -> r.activityId(URI.create("https://example.com/activity/simplestatement"))) + .block(); + + RecordedRequest recordedRequest = mockWebServer.takeRequest(); + + // Then Path Is Expected + assertThat(recordedRequest.getPath(), + is("/activities?activityId=https%3A%2F%2Fexample.com%2Factivity%2Fsimplestatement")); + } + + @Test + void whenGettingActivityThenBodyIsInstanceOfActivity() throws InterruptedException { + + mockWebServer.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK") + + .setBody( + "{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}}") + .addHeader("Content-Type", "application/json; charset=utf-8")); + + // When Getting Activity + var response = client + .getActivity(r -> r.activityId(URI.create("https://example.com/activity/simplestatement"))) + .block(); + + // Then Body Is Instance Of Activity + assertThat(response.getBody(), instanceOf(Activity.class)); + } + + + @Getter private static class Person {