# 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)](/reference/glossary.md#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 an RDF Resource](/sdk/java-sdk/crud-rdf-data/modeling-rdf-data.md).
{% 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](/sdk/javascript-sdk/read-and-write-rdf-data.md)
* [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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.inrupt.com/sdk/java-sdk/tutorial/step2.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
