Step 2: Expense Class
In this tutorial, an expense record (with date, description, provider, amount, currency, category information) is stored as an RDF (Resource Description Framework) file. For example, a saved expense RDF file may contain the following content (shown in Turtle format):
<https://storage.example.com/{rootContainer}/expenses/20230306/teamLunchExpense>
<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schema.org/Invoice> ;
<https://schema.org/purchaseDate> "2023-03-07T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> ;
<https://schema.org/provider> "Example Restaurant" ;
<https://schema.org/description> "Team Lunch";
<https://schema.org/category> "Travel and Entertainment" ;
<https://schema.org/priceCurrency> "USD" ;
<https://schema.org/totalPrice> "120"^^<http://www.w3.org/2001/XMLSchema#decimal> .
Create the Expense Class
In the src/main/java/com/example/gettingstarted/
directory, create an Expense.java
class with the following content:
package com.example.gettingstarted;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.inrupt.client.Headers;
import com.inrupt.client.solid.SolidRDFSource;
import com.inrupt.rdf.wrapping.commons.RDFFactory;
import com.inrupt.rdf.wrapping.commons.TermMappings;
import com.inrupt.rdf.wrapping.commons.ValueMappings;
import com.inrupt.rdf.wrapping.commons.WrapperIRI;
import org.apache.commons.rdf.api.Dataset;
import org.apache.commons.rdf.api.Graph;
import org.apache.commons.rdf.api.IRI;
import org.apache.commons.rdf.api.RDFTerm;
import java.math.BigDecimal;
import java.net.URI;
import java.time.Instant;
import java.util.Date;
import java.util.Objects;
/**
* Part 1
* Note: extends SolidRDFSource
* To model the Expense class as an RDF resource, the Expense class extends SolidRDFSource.
* <p>
* The @JsonIgnoreProperties annotation is added to ignore non-class-member fields
* when serializing Expense data as JSON.
*/
@JsonIgnoreProperties(value = { "metadata", "headers", "graph", "graphNames", "entity", "contentType" })
public class Expense extends SolidRDFSource {
/**
* Note 2a: Predicate Definitions
* The following constants define the Predicates used in our triple statements.
*/
static IRI RDF_TYPE = rdf.createIRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
static IRI SCHEMA_ORG_PURCHASE_DATE = rdf.createIRI("https://schema.org/purchaseDate");
static IRI SCHEMA_ORG_PROVIDER = rdf.createIRI("https://schema.org/provider");
static IRI SCHEMA_ORG_DESCRIPTION = rdf.createIRI("https://schema.org/description");
static IRI SCHEMA_ORG_TOTAL_PRICE = rdf.createIRI("https://schema.org/totalPrice");
static IRI SCHEMA_ORG_PRICE_CURRENCY = rdf.createIRI("https://schema.org/priceCurrency");
static IRI SCHEMA_ORG_CATEGORY = rdf.createIRI("https://schema.org/category");
/**
* Note 2b: Value Definition
* The following constant define the value for the predicate RDF_TYPE.
*/
static URI MY_RDF_TYPE_VALUE = URI.create("https://schema.org/Invoice");
/**
* Note 3: Node class
* The Node class is an inner class (defined below) that handles the mapping between expense data and RDF triples.
* The subject contains the expense data.
*/
private final Node subject;
/**
* Note 4: Constructors
* Expense constructors to handle SolidResource fields:
* - identifier: The destination URI of the resource; e.g., https://myPod.example.com/myPod/expense1
* - dataset: The org.apache.commons.rdf.api.Dataset that corresponding to the resource.
* - headers: The com.inrupt.client.Headers that contains HTTP header information.
* <p>
* In addition, the subject field is initialized.
*/
public Expense(final URI identifier, final Dataset dataset, final Headers headers) {
super(identifier, dataset, headers);
this.subject = new Node(rdf.createIRI(identifier.toString()), getGraph());
}
public Expense(final URI identifier) {
this(identifier, null, null);
}
@JsonCreator
public Expense(@JsonProperty("identifier") final URI identifier,
@JsonProperty("merchantProvider") String merchantProvider,
@JsonProperty("expenseDate") Date expenseDate,
@JsonProperty("description") String description,
@JsonProperty("amount") BigDecimal amount,
@JsonProperty("currency") String currency,
@JsonProperty("category") String category) {
this(identifier);
this.setRDFType(MY_RDF_TYPE_VALUE);
this.setMerchantProvider(merchantProvider);
this.setExpenseDate(expenseDate);
this.setDescription(description);
this.setAmount(amount);
this.setCurrency(currency);
this.setCategory(category);
}
/**
* Note 5: Various getters/setters.
* The getters and setters reference the subject's methods.
*/
public URI getRDFType() {
return subject.getRDFType();
}
public void setRDFType(URI rdfType) {
subject.setRDFType(rdfType);
}
public String getMerchantProvider() {
return subject.getMerchantProvider();
}
public void setMerchantProvider(String merchantProvider) {
subject.setMerchantProvider(merchantProvider);
}
public Date getExpenseDate() {
return subject.getExpenseDate();
}
public void setExpenseDate(Date expenseDate) {
subject.setExpenseDate(expenseDate);
}
public String getDescription() {
return subject.getDescription();
}
public void setDescription(String description) {
subject.setDescription(description);
}
public BigDecimal getAmount() {
return subject.getAmount();
}
public void setAmount(BigDecimal amount) {
subject.setAmount(amount);
}
public String getCurrency() {
return subject.getCurrency();
}
public void setCurrency(String currency) {
subject.setCurrency(currency);
}
public String getCategory() {
return subject.getCategory();
}
public void setCategory(String category) {
subject.setCategory(category);
}
/**
* Note 6: Inner class ``Node`` that extends WrapperIRI
* Node class handles the mapping of the expense data (date, provider,
* description, category, priceCurrency, total) to RDF triples
* <subject> <predicate> <object>.
* <p>
* Nomenclature Background: A set of RDF triples is called a Graph.
*/
class Node extends WrapperIRI {
Node(final RDFTerm original, final Graph graph) {
super(original, graph);
}
URI getRDFType() {
return anyOrNull(RDF_TYPE, ValueMappings::iriAsUri);
}
/**
* Note 7: In its getters, the ``Node`` class calls WrapperBlankNodeOrIRI
* method ``anyOrNull`` to return either 0 or 1 value mapped to the predicate.
* You can use ValueMappings method to convert the value to a specified type.
* <p>
* In its setters, the ``Node`` class calls WrapperBlankNodeOrIRI
* method ``overwriteNullable`` to return either 0 or 1 value mapped to the predicate.
* You can use TermMappings method to store the value with the specified type information.
*/
void setRDFType(URI type) {
overwriteNullable(RDF_TYPE, type, TermMappings::asIri);
}
String getMerchantProvider() {
return anyOrNull(SCHEMA_ORG_PROVIDER, ValueMappings::literalAsString);
}
void setMerchantProvider(String provider) {
overwriteNullable(SCHEMA_ORG_PROVIDER, provider, TermMappings::asStringLiteral);
}
public Date getExpenseDate() {
Instant expenseInstant = anyOrNull(SCHEMA_ORG_PURCHASE_DATE, ValueMappings::literalAsInstant);
if (expenseInstant != null) return Date.from(expenseInstant);
else return null;
}
public void setExpenseDate(Date expenseDate) {
overwriteNullable(SCHEMA_ORG_PURCHASE_DATE, expenseDate.toInstant(), TermMappings::asTypedLiteral);
}
String getDescription() {
return anyOrNull(SCHEMA_ORG_DESCRIPTION, ValueMappings::literalAsString);
}
void setDescription(String description) {
overwriteNullable(SCHEMA_ORG_DESCRIPTION, description, TermMappings::asStringLiteral);
}
public BigDecimal getAmount() {
String priceString = anyOrNull(SCHEMA_ORG_TOTAL_PRICE, ValueMappings::literalAsString);
if (priceString != null) return new BigDecimal(priceString);
else return null;
}
/**
* Note 8: You can write your own TermMapping helper.
*/
public void setAmount(BigDecimal totalPrice) {
overwriteNullable(SCHEMA_ORG_TOTAL_PRICE, totalPrice, (final BigDecimal value, final Graph graph) -> {
Objects.requireNonNull(value, "Value must not be null");
Objects.requireNonNull(graph, "Graph must not be null");
return RDFFactory.getInstance().
createLiteral(
value.toString(),
RDFFactory.getInstance().createIRI("http://www.w3.org/2001/XMLSchema#decimal")
);
});
}
public String getCurrency() {
return anyOrNull(SCHEMA_ORG_PRICE_CURRENCY, ValueMappings::literalAsString);
}
public void setCurrency(String currency) {
overwriteNullable(SCHEMA_ORG_PRICE_CURRENCY, currency, TermMappings::asStringLiteral);
}
public String getCategory() {
return anyOrNull(SCHEMA_ORG_CATEGORY, ValueMappings::literalAsString);
}
public void setCategory(String category) {
overwriteNullable(SCHEMA_ORG_CATEGORY, category, TermMappings::asStringLiteral);
}
}
}
Additional Information
For more information, see:
Last updated