# 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)](https://docs.inrupt.com/reference/glossary#rdf-resource) file. For example, a saved expense RDF file may contain the following content (shown in Turtle format):

```turtle
<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> .
```

{% hint style="info" %}
In addition to the expense data, the triples also include an RDF type statement, which acts to describe the resource as a whole; in this example, an <https://schema.org/Invoice> .
{% endhint %}

## Create the Expense Class

{% hint style="info" %}
Tip\
Various aspects related to modeling a Solid RDF Resource are noted as comments in the code. For more details, see [modeling-rdf-data](https://docs.inrupt.com/sdk/java-sdk/crud-rdf-data/modeling-rdf-data "mention").
{% endhint %}

{% tabs %}
{% tab title="Java" %}
In the **`src/main/java/com/example/gettingstarted/`** directory, create an **`Expense.java`** class with the following content:

```java
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);
        }
    }
}
```

{% endtab %}

{% tab title="Kotlin" %}
In the **`src/main/kotlin/com/example/gettingstarted/`** directory, create an **`Expense.kt`** Kotlin class with the following content:

```kotlin
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.*
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.*
/**
 * Part 1
 * Note: extends SolidRDFSource
 * To model the Expense class as an RDF resource, the Expense class extends SolidRDFSource.
 *
 *
 * The @JsonIgnoreProperties annotation is added to ignore the non-class-member fields
 * when serializing Expense data as JSON.
 */
@JsonIgnoreProperties(value = [ "metadata", "headers", "graph", "graphNames", "entity", "contentType" ])
class Expense(
    identifier: URI,
    dataset: Dataset = rdf.createDataset(),
    headers: Headers = Headers.empty(),
) : SolidRDFSource(identifier, dataset, headers) {
    companion object {
        /**
         * Note 2a: Predicate Definitions
         * The following constants define the Predicates used in our triple statements.
         */
        var RDF_TYPE: IRI = rdf.createIRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
        var SCHEMA_ORG_PURCHASE_DATE: IRI = rdf.createIRI("https://schema.org/purchaseDate")
        var SCHEMA_ORG_PROVIDER: IRI = rdf.createIRI("https://schema.org/provider")
        var SCHEMA_ORG_DESCRIPTION: IRI = rdf.createIRI("https://schema.org/description")
        var SCHEMA_ORG_TOTAL_PRICE: IRI = rdf.createIRI("https://schema.org/totalPrice")
        var SCHEMA_ORG_PRICE_CURRENCY: IRI = rdf.createIRI("https://schema.org/priceCurrency")
        var SCHEMA_ORG_CATEGORY: IRI = rdf.createIRI("https://schema.org/category")
        /**
         * Note 2b: Value Definition
         * The following constant defines the value for the predicate RDF_TYPE.
         */
        var MY_RDF_TYPE_VALUE: URI = 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 val subject: Node = Node(rdf.createIRI(identifier.toString()), dataset.graph)
    /**
     * 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 corresponds to the resource.
     * - headers:  The com.inrupt.client.Headers that contains header information.
     *
     *
     * In addition, the subject field is initialized.
     */
    @JsonCreator
    constructor(
        @JsonProperty("identifier") identifier: URI,
        @JsonProperty("merchantProvider") merchantProvider: String?,
        @JsonProperty("expenseDate") expenseDate: Date,
        @JsonProperty("description") description: String?,
        @JsonProperty("amount") amount: BigDecimal?,
        @JsonProperty("currency") currency: String?,
        @JsonProperty("category") category: String?
    ) : this(identifier, rdf.createDataset(), Headers.empty()) {
        this.rdfType = MY_RDF_TYPE_VALUE
        this.merchantProvider = merchantProvider
        this.expenseDate = expenseDate
        this.description = description
        this.amount = amount
        this.currency = currency
        this.category = category
    }
    constructor(
        identifier: URI
    ) : this(identifier, rdf.createDataset(), Headers.empty())
    /**
     * Note 5: Various getters/setters.
     * The getters and setters reference the subject's methods.
     */
    var rdfType: URI?
        get() = subject.rdfType
        set(rdfType) {
            subject.rdfType = rdfType
        }
    var merchantProvider: String?
        get() = subject.merchantProvider
        set(merchantProvider) {
            subject.merchantProvider = merchantProvider
        }
    var expenseDate: Date?
        get() = subject.expenseDate
        set(expenseDate) {
            subject.expenseDate = expenseDate
        }
    var description: String?
        get() = subject.description
        set(description) {
            subject.description = description
        }
    var amount: BigDecimal?
        get() = subject.amount
        set(amount) {
            subject.amount = amount
        }
    var currency: String?
        get() = subject.currency
        set(currency) {
            subject.currency = currency
        }
    var category: String?
        get() = subject.category
        set(category) {
            subject.category = 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>.
     *
     * Nomenclature Background: A set of RDF triples is called a Graph.
     */
    internal class Node(original: RDFTerm, graph: Graph) :
        WrapperIRI(original, graph) {
        /**
         * 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.
         *
         * In its setters, the ``Node`` class calls WrapperBlankNodeOrIRI
         * method ``overwriteNullable`` to return either 0 or 1 value mapped to the predicate.
         * You can use the TermMappings method to store the value with the specified type information.
         */
        var rdfType: URI?
            get() = anyOrNull(RDF_TYPE) { term: RDFTerm, graph: Graph ->
                ValueMappings.iriAsUri(term, graph)
            }
            set(type) {
                overwriteNullable(RDF_TYPE, type) { value: URI?, graph: Graph ->
                    TermMappings.asIri(value, graph)
                }
            }
        var merchantProvider: String?
            get() = anyOrNull(SCHEMA_ORG_PROVIDER) { term: RDFTerm, graph: Graph ->
                ValueMappings.literalAsString(term, graph)
            }
            set(provider) {
                overwriteNullable(SCHEMA_ORG_PROVIDER, provider) { value: String?, graph: Graph ->
                    TermMappings.asStringLiteral(value, graph)
                }
            }
        var expenseDate: Date?
            get() {
                val expenseInstant: Instant? = anyOrNull(SCHEMA_ORG_PURCHASE_DATE) { term: RDFTerm, graph: Graph ->
                    ValueMappings.literalAsInstant(term, graph)
                }
                return if (expenseInstant != null) Date.from(expenseInstant) else null
            }
            set(expenseDate) {
                val expenseInstant: Instant? = if (expenseDate != null) expenseDate.toInstant() else null
                overwriteNullable(SCHEMA_ORG_PURCHASE_DATE, expenseInstant) { value: Instant?, graph: Graph ->
                    TermMappings.asTypedLiteral(value, graph)
                }
            }
        var description: String?
            get() = anyOrNull(SCHEMA_ORG_DESCRIPTION) { term: RDFTerm?, graph: Graph ->
                ValueMappings.literalAsString(term, graph)
            }
            set(description) {
                overwriteNullable(SCHEMA_ORG_DESCRIPTION, description) { value: String?, graph: Graph ->
                    TermMappings.asStringLiteral(value, graph)
                }
            }
        /**
         * Note 8: You can write your own TermMapping helper.
         */
        var amount: BigDecimal?
            get() {
                val priceString: String? = anyOrNull(SCHEMA_ORG_TOTAL_PRICE) { term: RDFTerm?, graph: Graph ->
                    ValueMappings.literalAsString(term, graph)
                }
                return if (priceString != null) BigDecimal(priceString) else null
            }
            set(totalPrice) {
                overwriteNullable(SCHEMA_ORG_TOTAL_PRICE, totalPrice) { value, _ ->
                    RDFFactory.getInstance().createLiteral(value.toString(),
                        RDFFactory.getInstance().createIRI("http://www.w3.org/2001/XMLSchema#decimal"))
                }
            }
        var currency: String?
            get() = anyOrNull(SCHEMA_ORG_PRICE_CURRENCY) { term: RDFTerm?, graph: Graph ->
                ValueMappings.literalAsString(term, graph)
            }
            set(currency) {
                overwriteNullable(SCHEMA_ORG_PRICE_CURRENCY, currency) { value: String?, graph: Graph ->
                    TermMappings.asStringLiteral(value, graph)
                }
            }
        var category: String?
            get() = anyOrNull(SCHEMA_ORG_CATEGORY) { term: RDFTerm?, graph: Graph ->
                ValueMappings.literalAsString(term, graph)
            }
            set(category) {
                overwriteNullable(SCHEMA_ORG_CATEGORY, category) { value: String?, graph: Graph ->
                    TermMappings.asStringLiteral(value, graph)
                }
            }
    }
}
```

{% endtab %}
{% endtabs %}

## Additional Information

For more information, see:

* [CRUD RDF Data](https://docs.inrupt.com/sdk/javascript-sdk/read-and-write-rdf-data)
* [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html)
* [WrapperIRI](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/rdf/wrapping/commons/WrapperIRI.html)
* [WrapperBlankNodeOrIRI](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/rdf/wrapping/commons/WrapperBlankNodeOrIRI.html)
* [ValueMappings](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/rdf/wrapping/commons/ValueMappings.html)
* [TermMappings](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/rdf/wrapping/commons/TermMappings.html)
