Step 5: Add receipt.png
#
You can use the Java Client Libraries to save non-RDF resources (e.g.,
.png
, .pdf
) to your Pod.
For this part of the tutorial, the getting started app uses Inrupt’s Java client library to:
Store a copy of the expense receipt(s) to your Pod.
Update the associated expense to link to the URL(s) of the receipt(s).
Tip
You can find the complete code at Complete Code.
Update application.properties
#
Add the following properties to the
src/main/resources/application.properties
file:
spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB
Modify Expense
Class#
Open
Expense
class file:Open
src/main/java/com/example/gettingstarted/Expense.java
.Open
src/main/kotlin/com/example/gettingstarted/Expense.kt
.Add the
java.util.*
import statement:import java.util.*;
import java.util.*
Add the predicate definition for the receipts in the
Expense
class:static IRI SCHEMA_ORG_IMAGE = rdf.createIRI("https://schema.org/image");
var SCHEMA_ORG_IMAGE: IRI = rdf.createIRI("https://schema.org/image")
Update the
Expense
class constructor to include the receipts:@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, @JsonProperty("receipts") String[] receipts) { 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); this.setReceipts(receipts); }
@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?, @JsonProperty("receipts") receipts: Set<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 this.receipts = receipts }
Add the getter and setters for the receipts in the
Expense
class:public Set<String> getReceipts() { return subject.getReceipts(); } // Note:: The setters first uses the getter, which returns a Set, and adds the receipt to the set. public void addReceipt(String receipt) { subject.getReceipts().add(receipt); } public void setReceipts(String[] receipts) { subject.getReceipts().addAll(List.of(receipts)); }
// Note:: The setters first uses the getter, which returns a Set, and adds the receipt to the set. var receipts: Set<String>? get() = subject.receipts set(receipts) { if (receipts != null) { subject.receipts.addAll(receipts) } } fun addReceipt(receipt: String) { subject.receipts.add(receipt) }
Add getter for the receipts in the inner
Node
class:public Set<String> getReceipts() { return objects(SCHEMA_ORG_IMAGE, TermMappings::asIri, ValueMappings::iriAsString); }
val receipts: MutableSet<String> get() = objects(SCHEMA_ORG_IMAGE, TermMappings::asIri, ValueMappings::iriAsString)
Note
For more detail with regards to handling lists, see Data Modeling (RDF).
Modify ExpenseController
Class#
Open
ExpenseController
class file.Open
src/main/java/com/example/gettingstarted/ExpenseController.java
.Open
src/main/kotlin/com/example/gettingstarted/ExpenseController.kt
.Add the following import statements:
import com.inrupt.client.solid.SolidNonRDFSource; import org.springframework.web.multipart.MultipartFile;
import com.inrupt.client.solid.SolidNonRDFSource import org.springframework.web.multipart.MultipartFile
Add the following method to handle the upload of non-RDF files:
/** * Note 9: Stores a non-RDF resource to a Pod * * Using SolidNonRDFSource and the SolidSyncClient .create() method, * - Saves a non-RDF resource at the destinationURL. */ @PutMapping("/resource/nonRDF/add") public String addNonRDFFile(@RequestParam(value = "destinationURL") String destinationURL, @RequestParam(value = "file") MultipartFile file) { printWriter.println("In addNonRDFFile:: Save Non-RDF File to Pod."); try (final var fileStream = file.getInputStream()) { SolidNonRDFSource myNonRDFFile = new SolidNonRDFSource(URI.create(destinationURL), file.getContentType(), fileStream); return client.create(myNonRDFFile).getIdentifier().toString(); } catch(PreconditionFailedException e1) { // Errors if the resource already exists printWriter.println(String.format("[%s] com.inrupt.client.solid.PreconditionFailedException in addNonRDFFile:: %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 in addNonRDFFile:: %s", e2.getStatusCode(), e2.getMessage())); } catch(Exception e) { e.printStackTrace(); } return null; }
/** * Note 9: Stores a non-RDF resource to a Pod * * Using SolidNonRDFSource and the SolidSyncClient's .create() method, * - Saves a non-RDF resource at the destinationURL. */ @PutMapping("/resource/nonRDF/add") fun addNonRDFFile( @RequestParam(value = "destinationURL") destinationURL: String, @RequestParam(value = "file") file: MultipartFile ): String? { printWriter.println("In addNonRDFFile: Save Non-RDF File to Pod.") try { file.inputStream.use { fileStream -> val myNonRDFFile: SolidNonRDFSource = SolidNonRDFSource(URI.create(destinationURL), file.contentType, fileStream) return client.create(myNonRDFFile).identifier.toString() } } catch(e1: PreconditionFailedException) { // Errors if the resource already exists printWriter.println(String.format("[%s] com.inrupt.client.solid.PreconditionFailedException in addNonRDFFile:: %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 in addNonRDFFile:: %s", e2.statusCode, e2.localizedMessage)) } catch (e: Exception) { e.printStackTrace() } return "" }
Add the following method that uploads a receipt file and links it to the associated Expense.
/** * Note 10: Stores a non-RDF resource (image of the receipt) to a Pod and Attach to an Expense * Using methods defined as part of getting started, addReceiptToExpense: * - Calls addNonRDFFile() to store the receipt to a Pod * - Calls getExpense() to fetch the associated Expense RDF resource. * - Calls the Expense's setter `addReceipt` to add the link to the saved receipt. * - Calls updateExpense() to save the updated Expense. */ @PutMapping("/expenses/receipts/add") public Expense addReceiptToExpense(@RequestParam(value = "destinationURL") String destinationURL, @RequestParam(value = "file") MultipartFile file, @RequestParam(value = "expenseURL") String expenseURL) { printWriter.println("In addReceiptToExpense: Save Receipt File to Pod and Update Associated Expense."); try { String receiptLocation = addNonRDFFile(destinationURL, file); if (receiptLocation != null) { Expense expense = getExpense(expenseURL); expense.addReceipt(receiptLocation); return updateExpense(expense); } else { printWriter.println("Error adding receipt"); return null; } } catch(ForbiddenException e2) { // Errors if user does not have access to read or update the Expense resource printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException in addReceiptToExpense:: %s", e2.getStatusCode(), e2.getMessage())); } catch(Exception e) { e.printStackTrace(); } return null; }
/** * Note 10: Stores a non-RDF resource (image of the receipt) to a Pod and Attach to an Expense * Using methods defined as part of getting started, addReceiptToExpense: * - Calls addNonRDFFile() to store the receipt to a Pod * - Calls getExpense() to fetch the associated Expense RDF resource. * - Calls the Expense's setter `addReceipt` to add the link to the saved receipt. * - Calls updateExpense() to save the updated Expense. */ @PutMapping("/expenses/receipts/add") fun addReceiptToExpense( @RequestParam(value = "destinationURL") destinationURL: String, @RequestParam(value = "file") file: MultipartFile, @RequestParam(value = "expenseURL") expenseURL: String ): Expense? { printWriter.println("In AddReceiptToExpense: Save Receipt File to Pod and Update Associated Expense.") val receiptLocation = addNonRDFFile(destinationURL, file) return if (!receiptLocation.isNullOrEmpty()) { val expense = getExpense(expenseURL) if (expense != null) { expense.addReceipt(receiptLocation) updateExpense(expense) } else { null } } else { printWriter.println("Error adding receipt") null } }