diff --git a/samples/get-agents/pom.xml b/samples/get-agents/pom.xml
new file mode 100644
index 00000000..75e8eba8
--- /dev/null
+++ b/samples/get-agents/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+ dev.learning.xapi.samples
+ xapi-samples-build
+ 1.0.6-SNAPSHOT
+
+ get-agents
+ Get xAPI Agents Sample
+ Get xAPI Agents
+
+
+ dev.learning.xapi
+ xapi-client
+
+
+ dev.learning.xapi.samples
+ core
+
+
+
diff --git a/samples/get-agents/src/main/java/dev/learning/xapi/samples/getagents/GetAgentsApplication.java b/samples/get-agents/src/main/java/dev/learning/xapi/samples/getagents/GetAgentsApplication.java
new file mode 100644
index 00000000..f6347c56
--- /dev/null
+++ b/samples/get-agents/src/main/java/dev/learning/xapi/samples/getagents/GetAgentsApplication.java
@@ -0,0 +1,79 @@
+package dev.learning.xapi.samples.getagents;
+
+import dev.learning.xapi.client.XapiClient;
+import dev.learning.xapi.model.Person;
+import dev.learning.xapi.model.Verb;
+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 agents.
+ *
+ * @author Thomas Turrell-Croft
+ */
+@SpringBootApplication
+public class GetAgentsApplication implements CommandLineRunner {
+
+ private final XapiClient client;
+
+ /**
+ * Constructor for application. In this sample the WebClient.Builder instance is injected by the
+ * Spring Framework.
+ */
+ public GetAgentsApplication(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(GetAgentsApplication.class, args).close();
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+
+ // Post statement for later retrieval of activity
+ postStatement();
+
+ // Get Activity
+ ResponseEntity response =
+ client.getAgents(r -> r.agent(a -> a.name("A N Other").mbox("mailto:another@example.com")))
+ .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 5cc5d176..14b349a4 100644
--- a/samples/pom.xml
+++ b/samples/pom.xml
@@ -12,9 +12,9 @@
xAPI Samples Build
learning.dev xAPI Samples Build
-
+
true
-
+
org.springframework.boot
@@ -34,10 +34,11 @@
core
delete-agent-profile
- delete-state
+ delete-state
get-activity
get-agent-profile
get-agent-profiles
+ get-agents
get-state
get-statement
get-statements
diff --git a/xapi-client/src/main/java/dev/learning/xapi/client/GetAgentsRequest.java b/xapi-client/src/main/java/dev/learning/xapi/client/GetAgentsRequest.java
new file mode 100644
index 00000000..b0620399
--- /dev/null
+++ b/xapi-client/src/main/java/dev/learning/xapi/client/GetAgentsRequest.java
@@ -0,0 +1,103 @@
+package dev.learning.xapi.client;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import dev.learning.xapi.model.Agent;
+import java.util.Map;
+import java.util.function.Consumer;
+import lombok.Builder;
+import lombok.NonNull;
+import org.springframework.http.HttpMethod;
+import org.springframework.web.util.UriBuilder;
+
+/**
+ * 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.
+ *
+ * @see Agents
+ * Resource
+ *
+ * @author Thomas Turrell-Croft
+ */
+@Builder
+public class GetAgentsRequest implements Request {
+
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * The Agent representation to use in fetching expanded Agent information.
+ */
+ @NonNull
+ private Agent agent;
+
+ @Override
+ public UriBuilder url(UriBuilder uriBuilder, Map queryParams) {
+
+ queryParams.put("agent", agentToJsonString());
+
+ return uriBuilder.path("/agents").queryParam("agent", "{agent}");
+ }
+
+ @Override
+ public HttpMethod getMethod() {
+ return HttpMethod.GET;
+ }
+
+ /**
+ * Builder for GetAgentsRequest.
+ */
+ public static class Builder {
+
+ /**
+ * Consumer Builder for agent.
+ *
+ * @param agent The Consumer Builder for agent.
+ *
+ * @return This builder
+ *
+ * @see GetAgentsRequest#agent
+ */
+ public Builder agent(Consumer> agent) {
+
+ final Agent.Builder, ?> builder = Agent.builder();
+
+ agent.accept(builder);
+
+ return agent(builder.build());
+
+ }
+
+ /**
+ * Sets the agent.
+ *
+ * @param agent The Agent of the GetAgentsRequest.
+ *
+ * @return This builder
+ *
+ * @see GetAgentsRequest#agent
+ */
+ public Builder agent(Agent agent) {
+
+ this.agent = agent;
+
+ return this;
+
+ }
+
+ }
+
+
+ private String agentToJsonString() {
+
+ try {
+ return objectMapper.writeValueAsString(agent);
+ } catch (JsonProcessingException e) {
+ // Should not happen
+ return null;
+ }
+
+ }
+
+}
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 f378bd70..26d5102f 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
@@ -5,6 +5,7 @@
package dev.learning.xapi.client;
import dev.learning.xapi.model.Activity;
+import dev.learning.xapi.model.Person;
import dev.learning.xapi.model.Statement;
import dev.learning.xapi.model.StatementResult;
import java.util.HashMap;
@@ -944,4 +945,48 @@ public Mono> getActivity(Consumer> 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());
+
+ }
+
}
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 1cc2035f..25c4e965 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
@@ -7,6 +7,7 @@
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.Person;
import dev.learning.xapi.model.Statement;
import dev.learning.xapi.model.StatementFormat;
import dev.learning.xapi.model.Verb;
@@ -1475,7 +1476,7 @@ void whenPuttingASingleAgentProfileWithoutContentTypeThenBodyIsExpected()
.profileId("person")
- .profile(new Person("A N", "Other")))
+ .profile(new SamplePerson("A N", "Other")))
.block();
@@ -1593,7 +1594,7 @@ void whenPostingASingleAgentProfileWithoutContentTypeThenBodyIsExpected()
.profileId("person")
- .profile(new Person("A N", "Other")))
+ .profile(new SamplePerson("A N", "Other")))
.block();
@@ -1668,7 +1669,7 @@ 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
+ // Get Activity
@Test
void whenGettingActivityThenMethodIsGet() throws InterruptedException {
@@ -1723,13 +1724,63 @@ void whenGettingActivityThenBodyIsInstanceOfActivity() throws InterruptedExcepti
+ // Get Agents
+
+ @Test
+ void whenGettingAgentsThenMethodIsGet() throws InterruptedException {
+
+ mockWebServer.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK"));
+
+ // When Getting Agents
+ client.getAgents(r -> r.agent(a -> a.mbox("mailto:another@example.com"))).block();
+
+ RecordedRequest recordedRequest = mockWebServer.takeRequest();
+
+ // Then Method Is Get
+ assertThat(recordedRequest.getMethod(), is("GET"));
+ }
+
+ @Test
+ void whenGettingAgentsThenPathIsExpected() throws InterruptedException {
+
+ mockWebServer.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK"));
+
+ // When Getting Agents
+ client.getAgents(r -> r.agent(a -> a.mbox("mailto:another@example.com"))).block();
+
+ RecordedRequest recordedRequest = mockWebServer.takeRequest();
+
+ // Then Path Is Expected
+ assertThat(recordedRequest.getPath(),
+ is("/agents?agent=%7B%22mbox%22%3A%22mailto%3Aanother%40example.com%22%7D"));
+ }
+
+ @Test
+ void whenGettingAgentsThenBodyIsInstanceOfPerson() throws InterruptedException {
+
+ mockWebServer.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK")
+
+ .setBody(
+ "{\"name\":[\"A N Other\"],\"mbox\":[\"mailto:another@example.com\"],\"objectType\":\"Person\"}")
+ .addHeader("Content-Type", "application/json; charset=utf-8"));
+
+ // When Getting Agents
+ var response =
+ client.getAgents(r -> r.agent(a -> a.mbox("mailto:another@example.com"))).block();
+
+ // Then Body Is Instance Of Activity
+ assertThat(response.getBody(), instanceOf(Person.class));
+ }
+
+
+
@Getter
- private static class Person {
+ private static class SamplePerson {
private String firstName;
private String lastName;
- public Person(String firstName, String lastName) {
+ public SamplePerson(String firstName, String lastName) {
super();
this.firstName = firstName;
this.lastName = lastName;