# 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](https://docs.inrupt.com/sdk/java-sdk/crud-rdf-data) .
{% 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 %}
