This is a blog entry addresses three problems
- Quickly parse a CSV file
- Store the CSV file data in a Postgres SQL DB
- Use the latest Thymeleaf template to display the data
I could not find a few quirks that faced along with the latest version of Boot and the corresponding tweaks that are needed in the Kotlin codebase. Here is the quick rundown of the pom file.
<properties>
<java.version>17</java.version>
<kotlin.version>1.6.21</kotlin.version>
</properties>
Java 17 and the latest changes to the JVM JRE have made quite a bit of stride and Kotlin has become a better choice of language to use for Boot applications. With the latest Spring Boot version, the JSR 305 can be set to strict.
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
The CSV parser for the DMV locations is an open-source file that one can download from the web portal. Here is the CSV parser Kotlin file
@Component
class WIDmvCsvProcessor {
fun readFile(): List<WIDmv> {
val wiDMVDataList = mutableListOf<WIDmv>()
val reader =
Thread.currentThread().contextClassLoader.getResourceAsStream(DMV_SERVICE_CENTER_CSV)
?.let { InputStreamReader(it) }
val csvFormat = CSVFormat.Builder.create(CSVFormat.DEFAULT)
csvFormat.setHeader()
for (csvRecord in csvFormat.build().parse(reader)) {
val wiDMVData = WIDmv(
id = Random.nextDouble(),
objectid = csvRecord.get(OBJECTID),
dmvDesc = csvRecord.get(FLD_SC_NM),
dmvAddress = csvRecord.get(DNV_STN_ADDR_STR),
dmvCnty = csvRecord.get(DMV_WI_CNTY_NM),
dmvZipCode = csvRecord.get(DMV_STN_ZPCD)
)
wiDMVDataList.add(wiDMVData)
}
return wiDMVDataList
}
}
There are several shortcomings still with the Kotlin support for JPA., as documented the main thing for this implementation is the primary key which is a severe shortcoming. Here is a quick rundown of the JPA shortcomings. The data class that we have for this implementation is
@Table("WIDMV")
data class WIDmv(
@Id val id: Double,
val objectid: String,
val dmvDesc: String,
val dmvAddress: String,
val dmvCnty: String,
val dmvZipCode: String
)
Finally the controller.,
@Controller
class WIDMVController(val wiDMVService: WIDMVService) {
@GetMapping("/")
fun listDMVS(model: Model): String {
val wiDmvCsvProcessor = WIDmvCsvProcessor()
val myList = wiDmvCsvProcessor.readFile();
myList.forEach { wiDMVData ->
{
wiDMVService.post(wiDMVData)
}
}
println("The size: ${wiDMVService.findMessages().size}");
model["widmvs"] = myList
return "widmvs";
}
}
The thymeleaf template is something like this
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>All WI DMVs</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12">
<h1> WI DMVs </h1>
</div>
</div>
<div class="row">
<div class="col-md-6">
<table class="table">
<thead>
<tr>
<th> Name</th>
<th> Address</th>
<th> ZipCode</th>
<th> County</th>
</tr>
</thead>
<tbody>
<tr th:if="${widmvs.empty}">
<td colspan="2"> No DMV Data Available</td>
</tr>
<tr th:each="widmv : ${widmvs}">
<td><span th:text="${widmv.dmvDesc}"> Name </span></td>
<td><span th:text="${widmv.dmvAddress}"> Address </span></td>
<td><span th:text="${widmv.dmvZipCode}"> ZipCode </span></td>
<td><span th:text="${widmv.dmvCnty}"> County </span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
The entire code is on GitHub.