Implemented BahnAPI to fetch stations and timetables

This commit is contained in:
2023-09-23 20:47:48 +02:00
parent d377cbd886
commit 0b8c602384
14 changed files with 273 additions and 1 deletions
@@ -22,7 +22,7 @@ open class MainApplication(
fun ready() { fun ready() {
val result = bahnApiService.fetchStations("Köln Hbf") val result = bahnApiService.fetchStations("Köln Hbf")
val koeln = result[0] val koeln = result[0]
val stops = bahnApiService.fetchTimetable(koeln.eva, val timetable = bahnApiService.fetchTimetable(koeln.eva,
LocalDate.of(2023, Month.SEPTEMBER, 23), LocalDate.of(2023, Month.SEPTEMBER, 23),
LocalTime.of(17, 0)) LocalTime.of(17, 0))
log.info("stations: $result") log.info("stations: $result")
@@ -0,0 +1,29 @@
package de.twomartens.timetable.bahnApi.jaxb
import jakarta.xml.bind.annotation.adapters.XmlAdapter
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*
class LocalDateTimeAdapter : XmlAdapter<String?, LocalDateTime>() {
@Throws(Exception::class)
override fun unmarshal(xmlValue: String?): LocalDateTime {
return LocalDateTime.parse(xmlValue, dateFormatter.get())
}
@Throws(Exception::class)
override fun marshal(kotlinValue: LocalDateTime): String? {
return dateFormatter.get().format(kotlinValue)
}
companion object {
private val dateFormatter: ThreadLocal<DateTimeFormatter> = object : ThreadLocal<DateTimeFormatter>() {
override fun initialValue(): DateTimeFormatter {
return DateTimeFormatter.ofPattern("yyMMddHHmm", Locale.GERMANY)
}
}
}
}
@@ -0,0 +1,17 @@
package de.twomartens.timetable.bahnApi.mapper
import de.twomartens.timetable.bahnApi.model.db.BahnStation
import org.mapstruct.*
@Mapper(
collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
unmappedTargetPolicy = ReportingPolicy.IGNORE
)
interface BahnStationMapper {
@Mapping(target = "id", ignore = true)
@Mapping(target = "created", ignore = true)
@Mapping(target = "lastModified", ignore = true)
fun mapToDB(dto: de.twomartens.timetable.bahnApi.model.dto.BahnStation): BahnStation
}
@@ -0,0 +1,26 @@
package de.twomartens.timetable.bahnApi.model.db
import org.bson.types.ObjectId
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.Id
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.mongodb.core.index.Indexed
import org.springframework.data.mongodb.core.mapping.Document
import java.time.Instant
@Document
data class BahnStation(
var name: String,
@Indexed(unique = true) var eva: String,
var ds100: String,
var db: Boolean
) {
@Id
var id: ObjectId = ObjectId()
@CreatedDate
lateinit var created: Instant
@LastModifiedDate
lateinit var lastModified: Instant
}
@@ -0,0 +1,19 @@
package de.twomartens.timetable.bahnApi.model.dto
import jakarta.xml.bind.annotation.XmlAccessType
import jakarta.xml.bind.annotation.XmlAccessorType
import jakarta.xml.bind.annotation.XmlAttribute
@XmlAccessorType(XmlAccessType.FIELD)
data class BahnStation(
@field:XmlAttribute
var name: String,
@field:XmlAttribute
var eva: String,
@field:XmlAttribute
var ds100: String,
@field:XmlAttribute
var db: Boolean
) {
constructor(): this("", "", "", true)
}
@@ -0,0 +1,17 @@
package de.twomartens.timetable.bahnApi.model.dto
import jakarta.xml.bind.annotation.XmlAccessType
import jakarta.xml.bind.annotation.XmlAccessorType
import jakarta.xml.bind.annotation.XmlAttribute
import jakarta.xml.bind.annotation.XmlElement
@XmlAccessorType(XmlAccessType.FIELD)
data class BahnStationStop(
@field:XmlAttribute var eva: Int?,
@field:XmlAttribute var id: String,
@field:XmlElement(name = "tl") var tripLabel: BahnTripLabel?,
@field:XmlElement(name = "ar") var arrival: BahnStopEvent?,
@field:XmlElement(name = "dp") var departure: BahnStopEvent?
) {
constructor(): this(null, "", null, null, null)
}
@@ -0,0 +1,14 @@
package de.twomartens.timetable.bahnApi.model.dto
import jakarta.xml.bind.annotation.XmlAccessType
import jakarta.xml.bind.annotation.XmlAccessorType
import jakarta.xml.bind.annotation.XmlElement
import jakarta.xml.bind.annotation.XmlRootElement
@XmlRootElement(name = "stations")
@XmlAccessorType(XmlAccessType.FIELD)
data class BahnStations(
@field:XmlElement(name = "station") var stations: List<BahnStation>
) {
constructor(): this(mutableListOf())
}
@@ -0,0 +1,32 @@
package de.twomartens.timetable.bahnApi.model.dto
import de.twomartens.timetable.bahnApi.jaxb.LocalDateTimeAdapter
import jakarta.xml.bind.annotation.XmlAccessType
import jakarta.xml.bind.annotation.XmlAccessorType
import jakarta.xml.bind.annotation.XmlAttribute
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter
import java.time.LocalDateTime
@XmlAccessorType(XmlAccessType.FIELD)
data class BahnStopEvent(
@field:XmlAttribute(name = "pde") var plannedDistantEndpoint: String,
@field:XmlAttribute(name = "cde") var changedDistantEndpoint: String,
@field:XmlAttribute(name = "pt")
@field:XmlJavaTypeAdapter(value = LocalDateTimeAdapter::class)
var plannedTime: LocalDateTime?,
@field:XmlAttribute(name = "ct")
@field:XmlJavaTypeAdapter(value = LocalDateTimeAdapter::class)
var changedTime: LocalDateTime?,
@field:XmlAttribute(name = "ppth") var plannedPath: String,
@field:XmlAttribute(name = "cpth") var changedPath: String,
@field:XmlAttribute(name = "pp") var plannedPlatform: String,
@field:XmlAttribute(name = "cp") var changedPlatform: String,
@field:XmlAttribute(name = "wings") var wings: String,
@field:XmlAttribute(name = "l") var line: String
) {
constructor(): this("", "",
null,
null,
"", "", "", "",
"", "")
}
@@ -0,0 +1,13 @@
package de.twomartens.timetable.bahnApi.model.dto
import jakarta.xml.bind.annotation.*
@XmlRootElement(name = "timetable")
@XmlAccessorType(XmlAccessType.FIELD)
data class BahnTimetable(
@field:XmlAttribute var eva: Int?,
@field:XmlAttribute var station: String,
@field:XmlElement(name = "s") var stops: List<BahnStationStop>
) {
constructor(): this(null, "", mutableListOf())
}
@@ -0,0 +1,18 @@
package de.twomartens.timetable.bahnApi.model.dto
import jakarta.xml.bind.annotation.XmlAccessType
import jakarta.xml.bind.annotation.XmlAccessorType
import jakarta.xml.bind.annotation.XmlAttribute
@XmlAccessorType(XmlAccessType.FIELD)
data class BahnTripLabel(
@field:XmlAttribute(name = "c") var category: String,
// S: S-Bahn, N: Nahverkehr, F: Fernverkehr
@field:XmlAttribute(name = "f") var filterFlags: String,
@field:XmlAttribute(name = "n") var trainNumber: String,
@field:XmlAttribute(name = "o") var owner: String,
@field:XmlAttribute(name = "t") var tripType: BahnTripType?
) {
constructor(): this("", "", "", "",
null)
}
@@ -0,0 +1,10 @@
package de.twomartens.timetable.bahnApi.model.dto
enum class BahnTripType {
p, // passenger?
e,
z,
s,
h,
n
}
@@ -0,0 +1,14 @@
package de.twomartens.timetable.bahnApi.property
import io.swagger.v3.oas.annotations.media.Schema
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.ConstructorBinding
import org.springframework.cloud.context.config.annotation.RefreshScope
@RefreshScope
@ConfigurationProperties(prefix = "de.twomartens.timetable.bahn-api")
@Schema(description = "Properties to configure the BahnAPI")
data class BahnApiProperties @ConstructorBinding constructor(
val clientId: String,
val clientSecret: String
)
@@ -0,0 +1,6 @@
package de.twomartens.timetable.bahnApi.repository
import de.twomartens.timetable.bahnApi.model.db.BahnStation
import org.springframework.data.mongodb.repository.MongoRepository
interface BahnStationRepository : MongoRepository<BahnStation, String>
@@ -0,0 +1,57 @@
package de.twomartens.timetable.bahnApi.service
import de.twomartens.timetable.bahnApi.model.dto.BahnStation
import de.twomartens.timetable.bahnApi.model.dto.BahnStationStop
import de.twomartens.timetable.bahnApi.model.dto.BahnStations
import de.twomartens.timetable.bahnApi.model.dto.BahnTimetable
import de.twomartens.timetable.bahnApi.property.BahnApiProperties
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod
import org.springframework.http.MediaType
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter
@Service
class BahnApiService(
private val restTemplate: RestTemplate,
private val properties: BahnApiProperties
) {
fun fetchStations(pattern: String): List<BahnStation> {
val requestEntity = buildRequestEntity<BahnStations>()
val response = restTemplate.exchange(
"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/station/${pattern}",
HttpMethod.GET,
requestEntity,
BahnStations::class.java
)
val body = response.body
return body?.stations ?: listOf()
}
fun fetchTimetable(eva: String, date: LocalDate, hour: LocalTime): BahnTimetable {
val requestEntity = buildRequestEntity<BahnTimetable>()
val dateFormatter = DateTimeFormatter.ofPattern("yyMMdd")
val timeFormatter = DateTimeFormatter.ofPattern("HH")
val response = restTemplate.exchange(
"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/plan/" +
"${eva}/${date.format(dateFormatter)}/${hour.format(timeFormatter)}",
HttpMethod.GET,
requestEntity,
BahnTimetable::class.java
)
return response.body!!
}
private fun <T> buildRequestEntity(): HttpEntity<T> {
val headers = HttpHeaders()
headers.accept = mutableListOf(MediaType.APPLICATION_XML)
headers.contentType = MediaType.APPLICATION_XML
headers.set("DB-Client-Id", properties.clientId)
headers.set("DB-Api-Key", properties.clientSecret)
return HttpEntity<T>(headers)
}
}