From 067bdb7ef8cd1ecbc16de91e4a8d0bf921ee9cc9 Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 6 Mar 2023 15:34:48 +0000 Subject: [PATCH 1/4] Add about resource --- samples/get-about/pom.xml | 22 +++ .../samples/getabout/GetAboutApplication.java | 54 ++++++ samples/pom.xml | 1 + .../dev/learning/xapi/client/XapiClient.java | 171 +++++++++++------- .../learning/xapi/client/XapiClientTests.java | 64 +++++-- 5 files changed, 232 insertions(+), 80 deletions(-) create mode 100644 samples/get-about/pom.xml create mode 100644 samples/get-about/src/main/java/dev/learning/xapi/samples/getabout/GetAboutApplication.java diff --git a/samples/get-about/pom.xml b/samples/get-about/pom.xml new file mode 100644 index 00000000..9d7f21f2 --- /dev/null +++ b/samples/get-about/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + dev.learning.xapi.samples + xapi-samples-build + 1.0.6-SNAPSHOT + + get-about + Get xAPI About Sample + Get xAPI About + + + dev.learning.xapi + xapi-client + + + dev.learning.xapi.samples + core + + + diff --git a/samples/get-about/src/main/java/dev/learning/xapi/samples/getabout/GetAboutApplication.java b/samples/get-about/src/main/java/dev/learning/xapi/samples/getabout/GetAboutApplication.java new file mode 100644 index 00000000..d173fcf1 --- /dev/null +++ b/samples/get-about/src/main/java/dev/learning/xapi/samples/getabout/GetAboutApplication.java @@ -0,0 +1,54 @@ +package dev.learning.xapi.samples.getabout; + +import dev.learning.xapi.client.XapiClient; +import dev.learning.xapi.model.About; +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 about. + * + * @author Thomas Turrell-Croft + */ +@SpringBootApplication +public class GetAboutApplication implements CommandLineRunner { + + private final XapiClient client; + + /** + * Constructor for application. In this sample the WebClient.Builder instance is injected by the + * Spring Framework. + */ + public GetAboutApplication(WebClient.Builder webClientBuilder) { + + webClientBuilder + // Change for the URL of your LRS + .baseUrl("https://cloud.scorm.com/lrs/QVVLD8EVWD/") + // Set the Authorization value + .defaultHeader("Authorization", + "Basic NUJZLWhXay1IcXdtOVFGWFh3Yzo1LTRNSDFBSFVvbDJGM2x1SE1J") + + .build(); + + + client = new XapiClient(webClientBuilder); + } + + public static void main(String[] args) { + SpringApplication.run(GetAboutApplication.class, args).close(); + } + + @Override + public void run(String... args) throws Exception { + + // Get About + ResponseEntity response = client.getAbout().block(); + + // Print the returned activity to the console + System.out.println(response.getBody()); + } + +} diff --git a/samples/pom.xml b/samples/pom.xml index 14b349a4..f674817f 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -35,6 +35,7 @@ core delete-agent-profile delete-state + get-about get-activity get-agent-profile get-agent-profiles 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 26d5102f..2ba66ddc 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.About; import dev.learning.xapi.model.Activity; import dev.learning.xapi.model.Person; import dev.learning.xapi.model.Statement; @@ -349,6 +350,9 @@ public Mono> getMoreStatements( } + + // State Resource + /** * Gets a single document specified by the given stateId activity, agent, and optional * registration. @@ -655,7 +659,98 @@ public Mono> deleteStates( } + // Agents Resource + + /** + * Return a special, Person Object for a specified Agent. The Person Object is very similar to an + * Agent Object, but instead of each attribute having a single value, each attribute has an array + * value, and it is legal to include multiple identifying properties. + * + * @param request The parameters of the get agents request + * + * @return the ResponseEntity + */ + public Mono> getAgents(GetAgentsRequest request) { + + Map queryParams = new HashMap<>(); + + return this.webClient + + .method(request.getMethod()) + + .uri(u -> request.url(u, queryParams).build(queryParams)) + + .retrieve() + + .toEntity(Person.class); + + } + + /** + * Return a special, Person Object for a specified Agent. The Person Object is very similar to an + * Agent Object, but instead of each attribute having a single value, each attribute has an array + * value, and it is legal to include multiple identifying properties. + * + * @param request The Consumer Builder for the get agents request + * + * @return the ResponseEntity + */ + public Mono> getAgents(Consumer request) { + + final GetAgentsRequest.Builder builder = GetAgentsRequest.builder(); + + request.accept(builder); + + return getAgents(builder.build()); + + } + + // Activities Resource + + /** + * 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()); + + } + + // Agent Profile Resource + + /** + * * Gets a single agent profile by the given agent and profileId. * *

@@ -905,87 +1000,25 @@ 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); - - } + // About Resource /** - * Loads the complete Activity Object specified. - * - * @param request The Consumer Builder for the get activity request + * Returns JSON Object containing information about this LRS, including the xAPI version + * supported. * * @return the ResponseEntity */ - public Mono> getActivity(Consumer request) { - - final GetActivityRequest.Builder builder = GetActivityRequest.builder(); - - request.accept(builder); - - return getActivity(builder.build()); - - } - - /** - * Return a special, Person Object for a specified Agent. The Person Object is very similar to an - * Agent Object, but instead of each attribute having a single value, each attribute has an array - * value, and it is legal to include multiple identifying properties. - * - * @param request The parameters of the get agents request - * - * @return the ResponseEntity - */ - public Mono> getAgents(GetAgentsRequest request) { - - Map queryParams = new HashMap<>(); + public Mono> getAbout() { return this.webClient - .method(request.getMethod()) + .get() - .uri(u -> request.url(u, queryParams).build(queryParams)) + .uri(u -> u.path("/about").build()) .retrieve() - .toEntity(Person.class); - - } - - /** - * Return a special, Person Object for a specified Agent. The Person Object is very similar to an - * Agent Object, but instead of each attribute having a single value, each attribute has an array - * value, and it is legal to include multiple identifying properties. - * - * @param request The Consumer Builder for the get agents request - * - * @return the ResponseEntity - */ - public Mono> getAgents(Consumer request) { - - final GetAgentsRequest.Builder builder = GetAgentsRequest.builder(); - - request.accept(builder); - - return getAgents(builder.build()); + .toEntity(About.class); } 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 25c4e965..6391c0cd 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,6 +6,7 @@ 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.About; import dev.learning.xapi.model.Activity; import dev.learning.xapi.model.Person; import dev.learning.xapi.model.Statement; @@ -416,7 +417,6 @@ void whenGettingStatementsThenMethodIsGet() throws InterruptedException { assertThat(recordedRequest.getMethod(), is("GET")); } - @Test void whenGettingStatementsThenPathIsExpected() throws InterruptedException { @@ -529,8 +529,6 @@ void whenGettingStatementsWithActivityParameterThenPathIsExpected() throws Inter is("/statements?activity=https%3A%2F%2Fexample.com%2Factivity%2F1")); } - - @Test void whenGettingMoreStatementsThenRequestMethodIsGet() throws InterruptedException { @@ -1313,7 +1311,6 @@ void whenGettingASingleAgentProfileThenMethodIsGet() throws InterruptedException assertThat(recordedRequest.getMethod(), is("GET")); } - @Test void whenGettingASingleAgentProfileThenPathIsExpected() throws InterruptedException { @@ -1487,8 +1484,6 @@ void whenPuttingASingleAgentProfileWithoutContentTypeThenBodyIsExpected() is("{\"firstName\":\"A N\",\"lastName\":\"Other\"}")); } - - // Post Single Agent Profile @Test @@ -1605,8 +1600,6 @@ void whenPostingASingleAgentProfileWithoutContentTypeThenBodyIsExpected() is("{\"firstName\":\"A N\",\"lastName\":\"Other\"}")); } - - @Test void whenGettingProfilesThenMethodIsGet() throws InterruptedException { @@ -1646,7 +1639,6 @@ void whenGettingProfilesThenPathIsExpected() throws InterruptedException { "/agents/profile?agent=%7B%22name%22%3A%22A%20N%20Other%22%2C%22mbox%22%3A%22mailto%3Aanother%40example.com%22%7D")); } - @Test void whenGettingProfilesWithSinceParameterThenPathIsExpected() throws InterruptedException { @@ -1722,8 +1714,6 @@ void whenGettingActivityThenBodyIsInstanceOfActivity() throws InterruptedExcepti assertThat(response.getBody(), instanceOf(Activity.class)); } - - // Get Agents @Test @@ -1772,7 +1762,59 @@ void whenGettingAgentsThenBodyIsInstanceOfPerson() throws InterruptedException { assertThat(response.getBody(), instanceOf(Person.class)); } + // Get About + + @Test + void whenGettingAboutThenMethodIsGet() throws InterruptedException { + + mockWebServer.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK") + + .setBody( + "{\"extensions\":{\"https://example.com/extensions/test\":{\"name\":\"Example extension\"}},\"version\":[\"0.9\",\"0.95\",\"1.0.3\"]}") + .addHeader("Content-Type", "application/json; charset=utf-8")); + // When Getting About + client.getAbout().block(); + + RecordedRequest recordedRequest = mockWebServer.takeRequest(); + + // Then Method Is Get + assertThat(recordedRequest.getMethod(), is("GET")); + } + + @Test + void whenGettingAboutThenPathIsExpected() throws InterruptedException { + + mockWebServer.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK") + + .setBody( + "{\"extensions\":{\"https://example.com/extensions/test\":{\"name\":\"Example extension\"}},\"version\":[\"0.9\",\"0.95\",\"1.0.3\"]}") + .addHeader("Content-Type", "application/json; charset=utf-8")); + + // When Getting About + client.getAbout().block(); + + RecordedRequest recordedRequest = mockWebServer.takeRequest(); + + // Then Path Is Expected + assertThat(recordedRequest.getPath(), is("/about")); + } + + @Test + void whenGettingAboutThenBodyIsInstanceOfAbout() throws InterruptedException { + + mockWebServer.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK") + + .setBody( + "{\"extensions\":{\"https://example.com/extensions/test\":{\"name\":\"Example extension\"}},\"version\":[\"0.9\",\"0.95\",\"1.0.3\"]}") + .addHeader("Content-Type", "application/json; charset=utf-8")); + + // When Getting About + var response = client.getAbout().block(); + + // Then Body Is Instance Of About + assertThat(response.getBody(), instanceOf(About.class)); + } @Getter private static class SamplePerson { From e1750ff0e2479b4f458d8b90870ebe24a026bfaf Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 6 Mar 2023 15:39:48 +0000 Subject: [PATCH 2/4] tip --- .../java/dev/learning/xapi/client/GetStatementsRequest.java | 2 -- .../src/main/java/dev/learning/xapi/client/XapiClient.java | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/xapi-client/src/main/java/dev/learning/xapi/client/GetStatementsRequest.java b/xapi-client/src/main/java/dev/learning/xapi/client/GetStatementsRequest.java index cf6005f4..27414d5c 100644 --- a/xapi-client/src/main/java/dev/learning/xapi/client/GetStatementsRequest.java +++ b/xapi-client/src/main/java/dev/learning/xapi/client/GetStatementsRequest.java @@ -207,8 +207,6 @@ public Builder activity(String activity) { return this; } - - /** * Sets the registration. * 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 2ba66ddc..27fecadd 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 @@ -46,6 +46,8 @@ public XapiClient(WebClient.Builder builder) { .build(); } + // Statement Resource + /** * Gets a Statement. * @@ -350,7 +352,6 @@ public Mono> getMoreStatements( } - // State Resource /** From 0bd2c4bb2d840be1d47cb2a0d387273628721fbc Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 6 Mar 2023 15:56:21 +0000 Subject: [PATCH 3/4] tip --- .../src/main/java/dev/learning/xapi/client/XapiClient.java | 1 - 1 file changed, 1 deletion(-) 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 27fecadd..2eeff738 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 @@ -751,7 +751,6 @@ public Mono> getActivity(Consumer From ac4b332ed2d98d936429f3ad0a713c48b27f84f4 Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Mon, 6 Mar 2023 16:02:21 +0000 Subject: [PATCH 4/4] tip --- .../learning/xapi/samples/getabout/GetAboutApplication.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/samples/get-about/src/main/java/dev/learning/xapi/samples/getabout/GetAboutApplication.java b/samples/get-about/src/main/java/dev/learning/xapi/samples/getabout/GetAboutApplication.java index d173fcf1..e0141a29 100644 --- a/samples/get-about/src/main/java/dev/learning/xapi/samples/getabout/GetAboutApplication.java +++ b/samples/get-about/src/main/java/dev/learning/xapi/samples/getabout/GetAboutApplication.java @@ -26,14 +26,12 @@ public GetAboutApplication(WebClient.Builder webClientBuilder) { webClientBuilder // Change for the URL of your LRS - .baseUrl("https://cloud.scorm.com/lrs/QVVLD8EVWD/") + .baseUrl("https://example.com/xapi/") // Set the Authorization value - .defaultHeader("Authorization", - "Basic NUJZLWhXay1IcXdtOVFGWFh3Yzo1LTRNSDFBSFVvbDJGM2x1SE1J") + .defaultHeader("Authorization", "") .build(); - client = new XapiClient(webClientBuilder); }