Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e6d18dc
Add jakarta.validation-api dependency
kelvinqian00 Dec 3, 2024
8680376
Add validation implementation dep
kelvinqian00 Dec 3, 2024
cc0de68
Remove warnings in model package
kelvinqian00 Dec 4, 2024
4b2ba11
Remove warning in test case
kelvinqian00 Dec 4, 2024
847d1b9
Remove remaining warnings from xapi model
kelvinqian00 Dec 4, 2024
fa031cd
Add validation that Agents only have 1 IFI
kelvinqian00 Dec 5, 2024
1e1553c
Add validation for anonymous vs identified groups
kelvinqian00 Dec 5, 2024
dd6b673
Add NotNull constraints to Account
kelvinqian00 Dec 5, 2024
1798221
Add tests and TODOs for actor fields
kelvinqian00 Dec 5, 2024
6314acd
Don't forget imports in Agent and Group classes
kelvinqian00 Dec 5, 2024
8191807
Add Verb validation
kelvinqian00 Dec 5, 2024
f8e7427
Add Activity + ActivityDefinition validation
kelvinqian00 Dec 6, 2024
2bc3d50
Add Result and Score
kelvinqian00 Dec 6, 2024
7e54c63
Add InteractionComponent
kelvinqian00 Dec 6, 2024
94725e4
Add Attachment constraints
kelvinqian00 Dec 6, 2024
1007b07
Add StatementRef constraints
kelvinqian00 Dec 6, 2024
72445b6
Implement constraints for Statements (incl Authority checks)
kelvinqian00 Dec 6, 2024
9074e73
Add StatementResult
kelvinqian00 Dec 6, 2024
91d5ccd
Add ValidationUtils class with custom asserts
kelvinqian00 Dec 9, 2024
131c380
Add isEmpty function to validate that JSON objects are not empty
kelvinqian00 Dec 9, 2024
90704aa
Make statement's own isValidSubStmt method
kelvinqian00 Dec 10, 2024
46b9f84
Apply JsonIgnore annotation to prevent Jackson for serializing extra …
kelvinqian00 Dec 10, 2024
33c2543
merge main and update
cliffcaseyyet Jul 7, 2026
d2b5129
new validation for sha1 and mbox and tests. result extensions in tests
cliffcaseyyet Jul 7, 2026
55a60ed
sha256 support
cliffcaseyyet Jul 7, 2026
c7cb7b0
message specificity for constraint violations
cliffcaseyyet Jul 7, 2026
c1439be
added validation helper, and tests, and some additional validation sp…
cliffcaseyyet Jul 8, 2026
cac79ef
version + some missing valid enforcement for children
cliffcaseyyet Jul 8, 2026
252704e
whitespace
cliffcaseyyet Jul 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,20 @@ More methods will be added in future to support other resources and also attachm

## xAPI Validation

Coming Soon...
The xAPI model in this library can be used for statement validation using the `jakarta.validation` standard. You can either validate specific parts of a statement using the `jakarta.validation` API directly, or you can use the included helper which takes either a `String` or `Statement` argument and returns a validation object (including the `Statement` object, if valid):


```
String statement = "...";
StatementValidator validator = new StatementValidator();

// validate by string
StatementValidationResult result = validator.validateStatement(stmt);

result.isValid(); // boolean
result.getErrors(); // Set<String> containing the violation messages
result.getStatement(); // valid Statement object (if isValid() is true)
```

## License

Expand Down
14 changes: 13 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<groupId>com.yetanalytics</groupId>
<artifactId>xapi-tools</artifactId>
<packaging>jar</packaging>
<version>0.0.4</version>
<version>0.0.5</version>
<name>xAPI Tools</name>
<description>Java Serialization Model and Tools for xAPI Standard (IEEE 9274.1.1)</description>
<url>https://github.com/yetanalytics/java-xapi-tools</url>
Expand Down Expand Up @@ -53,6 +53,18 @@
<artifactId>json-path</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.bval</groupId>
<artifactId>bval-jsr</artifactId>
<version>3.0.1</version>
</dependency>
<!-- Test dependencies -->

<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.net.URI;
import java.net.URISyntaxException;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
Expand Down
44 changes: 42 additions & 2 deletions src/main/java/com/yetanalytics/xapi/model/AbstractActor.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.yetanalytics.xapi.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import java.net.URI;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.yetanalytics.xapi.model.deserializers.AbstractActorDeserializer;
import com.yetanalytics.xapi.validation.MboxUri;
import com.yetanalytics.xapi.validation.Sha1Sum;

import jakarta.validation.Valid;

/**
* Abstract Class for serialization and deserialization of xAPI Actors
Expand All @@ -13,13 +18,19 @@ public abstract class AbstractActor extends AbstractObject {

private String name;

//IFI
// IFIs

@MboxUri
private URI mbox;
// TODO: Validate that mbox_sha1sum is a SHA1, 40-char hex string
@Sha1Sum
private String mbox_sha1sum;

private URI openid;

@Valid
private Account account;

// Getters and Setters
public String getName() {
return name;
}
Expand Down Expand Up @@ -54,4 +65,33 @@ public Account getAccount() {
public void setAccount(Account account) {
this.account = account;
}

// Validation
protected int countIFIs() {
int notNullCount = 0;
if (mbox != null) {
++notNullCount;
}
if (mbox_sha1sum != null) {
++notNullCount;
}
if (openid != null) {
++notNullCount;
}
if (account != null) {
++notNullCount;
}
return notNullCount;
}

public abstract boolean isValidAuthority();

@Override
@JsonIgnore
public boolean isEmpty() {
return (
mbox == null && mbox_sha1sum == null &&
openid == null && account == null
);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.yetanalytics.xapi.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.yetanalytics.xapi.model.deserializers.AbstractObjectDeserializer;

/**
* Abstract Class for serialization and deserialization of xAPI Objects
*/
@JsonDeserialize(using = AbstractObjectDeserializer.class)
public class AbstractObject {
public abstract class AbstractObject implements JSONObject {

private ObjectType objectType;

Expand All @@ -18,4 +19,7 @@ public void setObjectType(ObjectType objectType) {
this.objectType = objectType;
}

@Override
@JsonIgnore
public abstract boolean isEmpty();
}
17 changes: 15 additions & 2 deletions src/main/java/com/yetanalytics/xapi/model/Account.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package com.yetanalytics.xapi.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import java.net.URI;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

import jakarta.validation.constraints.AssertFalse;
import jakarta.validation.constraints.NotNull;

/**
* Class representation of the Account Component of the
* <a href="https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#2424-account-object">9274.1.1 xAPI Specification</a>.
*/
@JsonInclude(Include.NON_NULL)
public class Account {

public class Account implements JSONObject {

@NotNull
private URI homePage;

@NotNull
private String name;

public URI getHomePage() {
Expand All @@ -29,4 +35,11 @@ public String getName() {
public void setName(String name) {
this.name = name;
}

@Override
@JsonIgnore
@AssertFalse(message = "Account must not be empty")
public boolean isEmpty() {
return homePage == null && name == null;
}
}
18 changes: 16 additions & 2 deletions src/main/java/com/yetanalytics/xapi/model/Activity.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package com.yetanalytics.xapi.model;

import java.net.URI;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import jakarta.validation.Valid;
import jakarta.validation.constraints.AssertFalse;
import jakarta.validation.constraints.NotNull;
import java.net.URI;

/**
* Class representation of the Activity Object Type of the
* <a href="https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#2441-when-the-objecttype-is-activity">9274.1.1 xAPI Specification</a>.
*/
@JsonInclude(Include.NON_NULL)
@JsonDeserialize
public class Activity extends AbstractObject {

@NotNull
private URI id;

@Valid
private ActivityDefinition definition;

public URI getId() {
Expand All @@ -30,4 +37,11 @@ public ActivityDefinition getDefinition() {
public void setDefinition(ActivityDefinition definition) {
this.definition = definition;
}

@Override
@JsonIgnore
@AssertFalse(message = "Activity must not be empty")
public boolean isEmpty() {
return id == null && definition == null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
import java.net.URI;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

import jakarta.validation.constraints.AssertFalse;
import jakarta.validation.constraints.AssertTrue;

/**
* Class representation of the Activity Definition Component of the
* <a href="https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#activity-definition">9274.1.1 xAPI Specification</a>.
*/
@JsonInclude(Include.NON_NULL)
public class ActivityDefinition {
public class ActivityDefinition implements JSONObject {

private LangMap name;
private LangMap description;
Expand Down Expand Up @@ -102,4 +106,95 @@ public List<InteractionComponent> getSteps() {
public void setSteps(List<InteractionComponent> steps) {
this.steps = steps;
}

// Validation

private boolean isChoices() {
return (
// choices is allowed
scale == null &&
source == null &&
target == null &&
steps == null
);
}

private boolean isScale() {
return (
// scale is allowed
choices == null &&
source == null &&
target == null &&
steps == null
);
}

private boolean isSourceTarget() {
return (
// source and target are allowed
choices == null &&
scale == null &&
steps == null
);
}

private boolean isSteps() {
return (
// steps is allowed
choices == null &&
scale == null &&
source == null &&
target == null
);
}

private boolean isNoInteractionComponents() {
return (
choices == null &&
scale == null &&
source == null &&
target == null &&
steps == null
);
}

private boolean isNoInteraction() {
return (
isNoInteractionComponents() &&
correctResponsesPattern == null
);
}

@Override
@JsonIgnore
@AssertFalse(message = "ActivityDefinition must not be empty")
public boolean isEmpty() {
return (
name == null && description == null &&
type == null && moreInfo == null && extensions == null &&
interactionType == null && isNoInteraction()
);
}

@JsonIgnore
@AssertTrue(message = "ActivityDefinition must be a valid interaction activity")
public boolean isValidInteractionActivity() {
if (interactionType == null) {
return isNoInteraction();
}
switch (interactionType) {
case CHOICE:
return isChoices();
case SEQUENCING:
return isChoices();
case LIKERT:
return isScale();
case MATCHING:
return isSourceTarget();
case PERFORMANCE:
return isSteps();
default:
return isNoInteractionComponents();
}
}
}
30 changes: 29 additions & 1 deletion src/main/java/com/yetanalytics/xapi/model/Agent.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.yetanalytics.xapi.model;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import jakarta.validation.constraints.AssertFalse;
import jakarta.validation.constraints.AssertTrue;

/**
* A concrete class representation of the Agent Component of the
Expand All @@ -13,4 +17,28 @@
@JsonDeserialize
public class Agent extends AbstractActor {

// Validation

@Override
@JsonIgnore
@AssertFalse(message = "Agent must not be empty")
public boolean isEmpty() {
return super.isEmpty();
}

/**
* Assertion that the Agent has only 1 Inverse Functional Identifier (IFI).
* @return true if the Agent has exactly 1 IFI, false otherwise
*/
@JsonIgnore
@AssertTrue(message = "Agent must have exactly 1 Inverse Functional Identifier (IFI)")
public boolean isIdentifiedAgent() {
return countIFIs() == 1;
}

@Override
@JsonIgnore
public boolean isValidAuthority() {
return true;
}
}
Loading
Loading