# Step 3: ExpenseController Class

For the WebService in this tutorial, the **`ExpenseController`** defines the endpoints for the various Read and Write (CRUD) operations.

## Create the ExpenseController Class

{% hint style="info" %}
Tip\
Various aspects related to CRUD operations with the Inrupt Client Libraries are noted as comments in the code. For more details, see [CRUD](/sdk/java-sdk/crud-rdf-data.md) .
{% endhint %}

{% tabs %}
{% tab title="Java" %}
In the **`src/main/java/com/example/gettingstarted/`** directory, create **`ExpenseController.java`** file with the content below:

```java
package com.example.gettingstarted;
import com.inrupt.client.auth.Session;
import com.inrupt.client.openid.OpenIdSession;
import com.inrupt.client.solid.SolidSyncClient;
import com.inrupt.client.webid.WebIdProfile;
import com.inrupt.client.solid.PreconditionFailedException;
import com.inrupt.client.solid.ForbiddenException;
import com.inrupt.client.solid.NotFoundException;
import org.springframework.web.bind.annotation.*;
import org.apache.commons.rdf.api.RDFSyntax;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URI;
import java.util.Set;
@RequestMapping("/api")
@RestController
public class ExpenseController {
    /**
     * Note 1: Authenticated Session
     * Using the client credentials, create an authenticated session.
     */
    final Session session = OpenIdSession.ofClientCredentials(
            URI.create(System.getenv("MY_SOLID_IDP")),
            System.getenv("MY_SOLID_CLIENT_ID"),
            System.getenv("MY_SOLID_CLIENT_SECRET"),
            System.getenv("MY_AUTH_FLOW"));
    /**
     * Note 2: SolidSyncClient
     * Instantiates a synchronous client for the authenticated session.
     * The client has methods to perform CRUD operations.
     */
    final SolidSyncClient client = SolidSyncClient.getClient().session(session);
    private final PrintWriter printWriter = new PrintWriter(System.out, true);
    /**
     * Note 3: SolidSyncClient.read()
     * Using the SolidSyncClient client.read() method, reads the user's WebID Profile document and returns the Pod URI(s).
     */
    @GetMapping("/pods")
    public Set<URI> getPods(@RequestParam(value = "webid", defaultValue = "") String webID) {
        printWriter.println("ExpenseController:: getPods");
        try (final var profile = client.read(URI.create(webID), WebIdProfile.class)) {
            return profile.getStorages();
        }
    }
    /**
     * Note 4: SolidSyncClient.create()
     * Using the SolidSyncClient client.create() method,
     * - Saves the Expense as an RDF resource to the location specified in the Expense.identifier field.
     */
    @PostMapping(path = "/expenses/create")
    public Expense createExpense(@RequestBody Expense newExpense) {
        printWriter.println("ExpenseController:: createExpense");
        try (var createdExpense = client.create(newExpense)) {
            printExpenseAsTurtle(createdExpense);
            return createdExpense;
        } catch(PreconditionFailedException e1) {
            // Errors if the resource already exists
            printWriter.println(String.format("[%s] com.inrupt.client.solid.PreconditionFailedException:: %s", e1.getStatusCode(), e1.getMessage()));
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to create
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * Note 5: SolidSyncClient.read()
     * Using the SolidSyncClient client.read() method,
     * - Reads the RDF resource into the Expense class.
     */
    @GetMapping("/expenses/get")
    public Expense getExpense(@RequestParam(value = "resourceURL", defaultValue = "") String resourceURL) {
        printWriter.println("ExpenseController:: getExpense");
        try (var resource = client.read(URI.create(resourceURL), Expense.class)) {
            return resource;
        } catch (NotFoundException e1) {
            // Errors if resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.getStatusCode(), e1.getMessage()));
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * Note 6: SolidSyncClient.update()
     * Using the SolidSyncClient client.update() method,
     * - Updates the Expense resource.
     */
    @PutMapping("/expenses/update")
    public Expense updateExpense(@RequestBody Expense expense) {
        printWriter.println("ExpenseController:: updateExpense");
        try(var updatedExpense = client.update(expense)) {
            printExpenseAsTurtle(updatedExpense);
            return updatedExpense;
        } catch (NotFoundException e1) {
            // Errors if resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.getStatusCode(), e1.getMessage()));
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * Note 7: SolidSyncClient.delete()
     * Using the SolidSyncClient client.delete() method,
     * - Deletes the resource located at the resourceURL.
     */
    @DeleteMapping("/expenses/delete")
    public void deleteExpense(@RequestParam(value = "resourceURL") String resourceURL) {
        printWriter.println("ExpenseController:: deleteExpense");
        try {
            client.delete(URI.create(resourceURL));
            // Alternatively, you can specify an Expense object to the delete method.
            // The delete method deletes  the Expense recorde located in the Expense.identifier field. 
            // For example: client.delete(new Expense(URI.create(resourceURL)));
        } catch (NotFoundException e1) {
            // Errors if resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.getStatusCode(), e1.getMessage()));
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * Note 8: Prints the expense resource in Turtle.
     */
    private void printExpenseAsTurtle(Expense expense) {
        printWriter.println("ExpenseController:: printExpenseAsTurtle");
        ByteArrayOutputStream content = new ByteArrayOutputStream();
        try  {
            expense.serialize(RDFSyntax.TURTLE, content);
            printWriter.println(content.toString("UTF-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```

{% endtab %}

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

```kotlin
package com.example.gettingstarted
import com.inrupt.client.openid.OpenIdSession
import com.inrupt.client.solid.SolidSyncClient
import com.inrupt.client.webid.WebIdProfile
import com.inrupt.client.solid.PreconditionFailedException
import com.inrupt.client.solid.ForbiddenException
import com.inrupt.client.solid.NotFoundException
import org.springframework.web.bind.annotation.*
import java.io.PrintWriter
import java.net.URI
import org.apache.commons.rdf.api.RDFSyntax
import java.io.ByteArrayOutputStream
import java.io.IOException
@RequestMapping("/api")
@RestController
class ExpenseController {
    /**
     * Note 1: Authenticated Session
     * Using the client credentials, create an authenticated session.
     */
    val session = OpenIdSession.ofClientCredentials(
        URI.create(System.getenv("MY_SOLID_IDP")),
        System.getenv("MY_SOLID_CLIENT_ID"),
        System.getenv("MY_SOLID_CLIENT_SECRET"),
        System.getenv("MY_AUTH_FLOW")
    )
    /**
     * Note 2: SolidSyncClient
     * Instantiates a synchronous client for the authenticated session.
     * The client has methods to perform CRUD operations.
     */
    val client = SolidSyncClient.getClient().session(session)
    private val printWriter = PrintWriter(System.out, true)
    /**
     * Note 3: SolidSyncClient.read()
     * Using the SolidSyncClient client.read() method, reads the user's WebID Profile document and returns the Pod URI(s).
     */
    @GetMapping("/pods")
    fun getPods(@RequestParam(value = "webid", defaultValue = "") webID: String): Set<URI> {
        printWriter.println("ExpenseController:: getPods")
        client.read(URI.create(webID), WebIdProfile::class.java).use { profile -> return profile.storages }
    }
    /**
     * Note 4: SolidSyncClient.create()
     * Using the SolidSyncClient client.create() method,
     * - Saves the Expense as an RDF resource to the location specified in the Expense.identifier field.
     */
    @PostMapping("/expenses/create")
    fun createExpense(@RequestBody newExpense: Expense): Expense? {
        printWriter.println("ExpenseController:: createExpense")
        try {
           val createdExpense = client.create(newExpense)
           printExpenseAsTurtle(createdExpense)
           return createdExpense
        } catch(e1: PreconditionFailedException) {
            // Errors if the resource already exists
            printWriter.println(String.format("[%s] com.inrupt.client.solid.PreconditionFailedException:: %s", e1.statusCode, e1.localizedMessage))
        } catch(e2: ForbiddenException) {
            // Errors if user does not have access to create
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.statusCode, e2.localizedMessage))
        } catch(e: Exception) {
            e.printStackTrace()
        }
        return null
    }
    /**
     * Note 5: SolidSyncClient.read()
     * Using the SolidSyncClient client.read() method,
     * - Reads the RDF resource into the Expense class.
     */
    @GetMapping("/expenses/get")
    fun getExpense(
        @RequestParam(
            value = "resourceURL", defaultValue = ""
        ) resourceURL: String
    ): Expense? {
        printWriter.println("ExpenseController:: getExpense")
        try {
            client.read(
                URI.create(resourceURL), Expense::class.java
            ).use { resource -> return resource }
        } catch(e1: NotFoundException) {
            // Errors if the resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.statusCode, e1.localizedMessage))
        } catch(e2: ForbiddenException) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.statusCode, e2.localizedMessage))
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }
    /**
     * Note 6: SolidSyncClient.update()
     * Using the SolidSyncClient client.update() method,
     * - Updates the Expense resource.
     */
    @PutMapping("/expenses/update")
    fun updateExpense(@RequestBody expense: Expense): Expense? {
        printWriter.println("ExpenseController:: updateExpense")
        try {
           val updatedExpense = client.update(expense)
           printExpenseAsTurtle(updatedExpense)
           return updatedExpense
        } catch(e1: NotFoundException) {
            // Errors if the resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.statusCode, e1.localizedMessage))
        } catch(e2: ForbiddenException) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.statusCode, e2.localizedMessage))
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }
    /**
     * Note 7: SolidSyncClient.delete()
     * Using the SolidSyncClient client.delete() method,
     * - Deletes the resource located at the resourceURL.
     */
    @DeleteMapping("/expenses/delete")
    fun deleteExpense(@RequestParam(value = "resourceURL") resourceURL: String) {
        printWriter.println("ExpenseController:: deleteExpense")
        try {
            client.delete(URI.create(resourceURL))
            // Alternatively, you can specify an Expense object to the delete method.
            // The delete method deletes  the Expense recorde located in the Expense.identifier field.
            // For example: client.delete(Expense(URI.create(resourceURL)))
        } catch(e1: NotFoundException) {
            // Errors if the resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.statusCode, e1.localizedMessage))
        } catch(e2: ForbiddenException) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.statusCode, e2.localizedMessage))
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
    /**
     * Note 8: Prints the expense resource in Turtle.
     */
    fun printExpenseAsTurtle(
        expense: Expense
    ) {
        printWriter.println("ExpenseController:: printExpenseAsTurtle")
        val content = ByteArrayOutputStream()
        try  {
            expense.serialize(RDFSyntax.TURTLE, content)
            printWriter.println(content.toString("UTF-8"))
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
}
```

{% endtab %}
{% endtabs %}


---

# 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/step3.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.
