mirror of
https://github.com/2martens/tsw-timetable.git
synced 2026-10-17 16:06:51 +02:00
Follow clean code more closely
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
package de.twomartens.timetable
|
package de.twomartens.timetable
|
||||||
|
|
||||||
import de.twomartens.timetable.service.ScheduledTaskService
|
import de.twomartens.timetable.bahnApi.service.ScheduledTaskService
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
import org.springframework.boot.context.event.ApplicationReadyEvent
|
import org.springframework.boot.context.event.ApplicationReadyEvent
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,4 @@
|
|||||||
package de.twomartens.timetable.configuration
|
package de.twomartens.timetable.bahnApi.configuration
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
@@ -8,6 +8,7 @@ private const val CORE_POOL_SIZE = 1
|
|||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
open class ThreadPoolTaskExecutorConfig {
|
open class ThreadPoolTaskExecutorConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
open fun threadPoolTaskExecutor(): ThreadPoolTaskExecutor {
|
open fun threadPoolTaskExecutor(): ThreadPoolTaskExecutor {
|
||||||
val executor = ThreadPoolTaskExecutor()
|
val executor = ThreadPoolTaskExecutor()
|
||||||
+1
-2
@@ -1,11 +1,10 @@
|
|||||||
package de.twomartens.timetable.configuration
|
package de.twomartens.timetable.bahnApi.configuration
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
|
||||||
|
|
||||||
private const val POOL_SIZE = 1
|
private const val POOL_SIZE = 1
|
||||||
|
|
||||||
private const val THREAD_NAME_PREFIX = "ThreadPoolTaskScheduler"
|
private const val THREAD_NAME_PREFIX = "ThreadPoolTaskScheduler"
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
+3
-2
@@ -3,17 +3,18 @@ package de.twomartens.timetable.bahnApi.jaxb
|
|||||||
import jakarta.xml.bind.annotation.adapters.XmlAdapter
|
import jakarta.xml.bind.annotation.adapters.XmlAdapter
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.time.format.DateTimeParseException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
class LocalDateTimeAdapter : XmlAdapter<String?, LocalDateTime>() {
|
class LocalDateTimeAdapter : XmlAdapter<String?, LocalDateTime>() {
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(DateTimeParseException::class)
|
||||||
override fun unmarshal(xmlValue: String?): LocalDateTime {
|
override fun unmarshal(xmlValue: String?): LocalDateTime {
|
||||||
return LocalDateTime.parse(xmlValue, dateFormatter.get())
|
return LocalDateTime.parse(xmlValue, dateFormatter.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(DateTimeParseException::class)
|
||||||
override fun marshal(kotlinValue: LocalDateTime): String? {
|
override fun marshal(kotlinValue: LocalDateTime): String? {
|
||||||
return dateFormatter.get().format(kotlinValue)
|
return dateFormatter.get().format(kotlinValue)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
package de.twomartens.timetable.bahnApi.mapper
|
package de.twomartens.timetable.bahnApi.mapper
|
||||||
|
|
||||||
import de.twomartens.timetable.bahnApi.model.db.BahnTimetable
|
import de.twomartens.timetable.bahnApi.model.db.BahnTimetable
|
||||||
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
import org.mapstruct.*
|
import org.mapstruct.*
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
@@ -16,9 +17,9 @@ interface BahnTimetableMapper {
|
|||||||
fun mapToDB(dto: de.twomartens.timetable.bahnApi.model.dto.BahnTimetable,
|
fun mapToDB(dto: de.twomartens.timetable.bahnApi.model.dto.BahnTimetable,
|
||||||
dateTime: LocalDateTime): BahnTimetable {
|
dateTime: LocalDateTime): BahnTimetable {
|
||||||
return BahnTimetable(
|
return BahnTimetable(
|
||||||
dto.eva!!,
|
dto.eva,
|
||||||
dateTime,
|
dateTime,
|
||||||
dto.station,
|
NonEmptyString(dto.station),
|
||||||
dto.stops
|
dto.stops
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package de.twomartens.timetable.bahnApi.model
|
||||||
|
|
||||||
|
import de.twomartens.timetable.model.db.StationId
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class Eva(val value: Int) {
|
||||||
|
init {
|
||||||
|
if (isKnown()) {
|
||||||
|
require(value in 8000000..8999999) {
|
||||||
|
"Eva number must start with an 8 and have 7 digits"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isKnown() = value != -1
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val UNKNOWN = Eva(-1)
|
||||||
|
|
||||||
|
fun of(stationId: StationId): Eva {
|
||||||
|
return Eva(stationId.stationIdWithinCountry.name.toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package de.twomartens.timetable.bahnApi.model
|
||||||
|
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
data class FetchDates(val previousDay: LocalDate, val fetchDate: LocalDate, val nextDate: LocalDate)
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package de.twomartens.timetable.bahnApi.model
|
||||||
|
|
||||||
|
import de.twomartens.timetable.bahnApi.model.db.ScheduledFetchTask
|
||||||
|
import de.twomartens.timetable.model.base.HourAtDay
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
private const val SECONDS_BETWEEN_TASKS = 2
|
||||||
|
|
||||||
|
private const val ZERO = 0
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class TaskFactory {
|
||||||
|
|
||||||
|
private val _tasksPerHour: MutableMap<HourAtDay, Int> = mutableMapOf()
|
||||||
|
|
||||||
|
fun initializeWithExistingTasks(existingTasks: List<ScheduledFetchTask>) {
|
||||||
|
existingTasks.forEach { countTask(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createTaskAndUpdateCounter(eva: Eva, hourAtDay: HourAtDay): ScheduledFetchTask {
|
||||||
|
val fetchDateTime = calculateNextAvailableFetchDateTime(hourAtDay)
|
||||||
|
incrementTaskCountForHourAtDay(hourAtDay)
|
||||||
|
return ScheduledFetchTask(eva, hourAtDay, fetchDateTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateNextAvailableFetchDateTime(hourAtDay: HourAtDay): LocalDateTime {
|
||||||
|
val secondInHourToExecute = calculateSecondInHourToCalculate(hourAtDay)
|
||||||
|
return calculateFetchDateTime(hourAtDay, secondInHourToExecute)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateSecondInHourToCalculate(hourAtDay: HourAtDay): Int {
|
||||||
|
val numberOfTasksInHour = getNumberOfTasksInHour(hourAtDay)
|
||||||
|
return numberOfTasksInHour * SECONDS_BETWEEN_TASKS
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateFetchDateTime(
|
||||||
|
hourAtDay: HourAtDay,
|
||||||
|
secondInHourToExecute: Int
|
||||||
|
): LocalDateTime {
|
||||||
|
return hourAtDay.dateTime.plusSeconds(secondInHourToExecute.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun countTask(scheduledFetchTask: ScheduledFetchTask) {
|
||||||
|
val scheduledTime = scheduledFetchTask.scheduledExecutionDateTime
|
||||||
|
val hourAtDay = HourAtDay.of(scheduledTime)
|
||||||
|
incrementTaskCountForHourAtDay(hourAtDay)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun incrementTaskCountForHourAtDay(hourAtDay: HourAtDay) {
|
||||||
|
val numberOfTasksInHour = getNumberOfTasksInHour(hourAtDay)
|
||||||
|
_tasksPerHour[hourAtDay] = numberOfTasksInHour + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNumberOfTasksInHour(hourAtDay: HourAtDay) =
|
||||||
|
_tasksPerHour.computeIfAbsent(hourAtDay) { ZERO }
|
||||||
|
|
||||||
|
}
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
package de.twomartens.timetable.bahnApi.model.db
|
package de.twomartens.timetable.bahnApi.model.db
|
||||||
|
|
||||||
import de.twomartens.timetable.model.Eva
|
import de.twomartens.timetable.bahnApi.model.Eva
|
||||||
import de.twomartens.timetable.model.NonEmptyString
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
import org.bson.types.ObjectId
|
import org.bson.types.ObjectId
|
||||||
import org.springframework.data.annotation.CreatedDate
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import org.springframework.data.annotation.Id
|
import org.springframework.data.annotation.Id
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
package de.twomartens.timetable.bahnApi.model.db
|
package de.twomartens.timetable.bahnApi.model.db
|
||||||
|
|
||||||
|
import de.twomartens.timetable.bahnApi.model.Eva
|
||||||
import de.twomartens.timetable.bahnApi.model.dto.BahnStationStop
|
import de.twomartens.timetable.bahnApi.model.dto.BahnStationStop
|
||||||
import de.twomartens.timetable.model.Eva
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
import de.twomartens.timetable.model.NonEmptyString
|
|
||||||
import org.bson.types.ObjectId
|
import org.bson.types.ObjectId
|
||||||
import org.springframework.data.annotation.CreatedDate
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import org.springframework.data.annotation.Id
|
import org.springframework.data.annotation.Id
|
||||||
|
|||||||
+4
-6
@@ -1,7 +1,7 @@
|
|||||||
package de.twomartens.timetable.bahnApi.model.db
|
package de.twomartens.timetable.bahnApi.model.db
|
||||||
|
|
||||||
import de.twomartens.timetable.model.Eva
|
import de.twomartens.timetable.bahnApi.model.Eva
|
||||||
import de.twomartens.timetable.model.Hour
|
import de.twomartens.timetable.model.base.HourAtDay
|
||||||
import org.bson.types.ObjectId
|
import org.bson.types.ObjectId
|
||||||
import org.springframework.data.annotation.CreatedDate
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import org.springframework.data.annotation.Id
|
import org.springframework.data.annotation.Id
|
||||||
@@ -9,15 +9,13 @@ import org.springframework.data.annotation.LastModifiedDate
|
|||||||
import org.springframework.data.mongodb.core.index.CompoundIndex
|
import org.springframework.data.mongodb.core.index.CompoundIndex
|
||||||
import org.springframework.data.mongodb.core.mapping.Document
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
@Document
|
@Document
|
||||||
@CompoundIndex(def = "{'eva': 1, 'fetchedDate': 1, 'fetchedTime': 1", unique = true)
|
@CompoundIndex(def = "{'eva': 1, 'fetchedDateTime': 1", unique = true)
|
||||||
data class ScheduledFetchTask(
|
data class ScheduledFetchTask(
|
||||||
var eva: Eva,
|
var eva: Eva,
|
||||||
var fetchedDate: LocalDate,
|
var fetchedDateTime: HourAtDay,
|
||||||
var fetchedTime: Hour,
|
|
||||||
var scheduledExecutionDateTime: LocalDateTime
|
var scheduledExecutionDateTime: LocalDateTime
|
||||||
) {
|
) {
|
||||||
@Id
|
@Id
|
||||||
|
|||||||
+6
-10
@@ -1,20 +1,16 @@
|
|||||||
package de.twomartens.timetable.bahnApi.model.dto
|
package de.twomartens.timetable.bahnApi.model.dto
|
||||||
|
|
||||||
import de.twomartens.timetable.model.Eva
|
import de.twomartens.timetable.bahnApi.model.Eva
|
||||||
import jakarta.xml.bind.annotation.XmlAccessType
|
import jakarta.xml.bind.annotation.XmlAccessType
|
||||||
import jakarta.xml.bind.annotation.XmlAccessorType
|
import jakarta.xml.bind.annotation.XmlAccessorType
|
||||||
import jakarta.xml.bind.annotation.XmlAttribute
|
import jakarta.xml.bind.annotation.XmlAttribute
|
||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
data class BahnStation(
|
data class BahnStation(
|
||||||
@field:XmlAttribute
|
@field:XmlAttribute var name: String,
|
||||||
var name: String,
|
@field:XmlAttribute var eva: Eva,
|
||||||
@field:XmlAttribute
|
@field:XmlAttribute var ds100: String,
|
||||||
var eva: Eva?,
|
@field:XmlAttribute var db: Boolean
|
||||||
@field:XmlAttribute
|
|
||||||
var ds100: String,
|
|
||||||
@field:XmlAttribute
|
|
||||||
var db: Boolean
|
|
||||||
) {
|
) {
|
||||||
constructor(): this("", null, "", true)
|
constructor() : this("", Eva.UNKNOWN, "", true)
|
||||||
}
|
}
|
||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
package de.twomartens.timetable.bahnApi.model.dto
|
package de.twomartens.timetable.bahnApi.model.dto
|
||||||
|
|
||||||
import de.twomartens.timetable.model.Eva
|
import de.twomartens.timetable.bahnApi.model.Eva
|
||||||
import jakarta.xml.bind.annotation.XmlAccessType
|
import jakarta.xml.bind.annotation.XmlAccessType
|
||||||
import jakarta.xml.bind.annotation.XmlAccessorType
|
import jakarta.xml.bind.annotation.XmlAccessorType
|
||||||
import jakarta.xml.bind.annotation.XmlAttribute
|
import jakarta.xml.bind.annotation.XmlAttribute
|
||||||
@@ -8,11 +8,11 @@ import jakarta.xml.bind.annotation.XmlElement
|
|||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
data class BahnStationStop(
|
data class BahnStationStop(
|
||||||
@field:XmlAttribute var eva: Eva?,
|
@field:XmlAttribute var eva: Eva,
|
||||||
@field:XmlAttribute var id: String,
|
@field:XmlAttribute var id: String,
|
||||||
@field:XmlElement(name = "tl") var tripLabel: BahnTripLabel?,
|
@field:XmlElement(name = "tl") var tripLabel: BahnTripLabel,
|
||||||
@field:XmlElement(name = "ar") var arrival: BahnStopEvent?,
|
@field:XmlElement(name = "ar") var arrival: BahnStopEvent?,
|
||||||
@field:XmlElement(name = "dp") var departure: BahnStopEvent?
|
@field:XmlElement(name = "dp") var departure: BahnStopEvent?
|
||||||
) {
|
) {
|
||||||
constructor(): this(null, "", null, null, null)
|
constructor() : this(Eva.UNKNOWN, "", BahnTripLabel(), null, null)
|
||||||
}
|
}
|
||||||
+1
-1
@@ -10,5 +10,5 @@ import jakarta.xml.bind.annotation.XmlRootElement
|
|||||||
data class BahnStations(
|
data class BahnStations(
|
||||||
@field:XmlElement(name = "station") var stations: List<BahnStation>
|
@field:XmlElement(name = "station") var stations: List<BahnStation>
|
||||||
) {
|
) {
|
||||||
constructor(): this(mutableListOf())
|
constructor() : this(listOf())
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
package de.twomartens.timetable.bahnApi.model.dto
|
package de.twomartens.timetable.bahnApi.model.dto
|
||||||
|
|
||||||
import de.twomartens.timetable.model.Eva
|
import de.twomartens.timetable.bahnApi.model.Eva
|
||||||
import jakarta.xml.bind.annotation.*
|
import jakarta.xml.bind.annotation.*
|
||||||
|
|
||||||
@XmlRootElement(name = "timetable")
|
@XmlRootElement(name = "timetable")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
data class BahnTimetable(
|
data class BahnTimetable(
|
||||||
@field:XmlAttribute var eva: Eva?,
|
@field:XmlAttribute var eva: Eva,
|
||||||
@field:XmlAttribute var station: String,
|
@field:XmlAttribute var station: String,
|
||||||
@field:XmlElement(name = "s") var stops: List<BahnStationStop>
|
@field:XmlElement(name = "s") var stops: List<BahnStationStop>
|
||||||
) {
|
) {
|
||||||
constructor(): this(null, "", mutableListOf())
|
constructor() : this(Eva.UNKNOWN, "", listOf())
|
||||||
}
|
}
|
||||||
+2
-3
@@ -11,8 +11,7 @@ data class BahnTripLabel(
|
|||||||
@field:XmlAttribute(name = "f") var filterFlags: String,
|
@field:XmlAttribute(name = "f") var filterFlags: String,
|
||||||
@field:XmlAttribute(name = "n") var trainNumber: String,
|
@field:XmlAttribute(name = "n") var trainNumber: String,
|
||||||
@field:XmlAttribute(name = "o") var owner: String,
|
@field:XmlAttribute(name = "o") var owner: String,
|
||||||
@field:XmlAttribute(name = "t") var tripType: BahnTripType?
|
@field:XmlAttribute(name = "t") var tripType: BahnTripType
|
||||||
) {
|
) {
|
||||||
constructor(): this("", "", "", "",
|
constructor() : this("", "", "", "", BahnTripType.p)
|
||||||
null)
|
|
||||||
}
|
}
|
||||||
+5
-6
@@ -1,18 +1,17 @@
|
|||||||
package de.twomartens.timetable.bahnApi.service
|
package de.twomartens.timetable.bahnApi.service
|
||||||
|
|
||||||
|
import de.twomartens.timetable.bahnApi.model.Eva
|
||||||
import de.twomartens.timetable.bahnApi.model.dto.BahnStation
|
import de.twomartens.timetable.bahnApi.model.dto.BahnStation
|
||||||
import de.twomartens.timetable.bahnApi.model.dto.BahnStations
|
import de.twomartens.timetable.bahnApi.model.dto.BahnStations
|
||||||
import de.twomartens.timetable.bahnApi.model.dto.BahnTimetable
|
import de.twomartens.timetable.bahnApi.model.dto.BahnTimetable
|
||||||
import de.twomartens.timetable.bahnApi.property.BahnApiProperties
|
import de.twomartens.timetable.bahnApi.property.BahnApiProperties
|
||||||
import de.twomartens.timetable.model.Eva
|
import de.twomartens.timetable.model.base.HourAtDay
|
||||||
import de.twomartens.timetable.model.Hour
|
|
||||||
import org.springframework.http.HttpEntity
|
import org.springframework.http.HttpEntity
|
||||||
import org.springframework.http.HttpHeaders
|
import org.springframework.http.HttpHeaders
|
||||||
import org.springframework.http.HttpMethod
|
import org.springframework.http.HttpMethod
|
||||||
import org.springframework.http.MediaType
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.web.client.RestTemplate
|
import org.springframework.web.client.RestTemplate
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@@ -33,14 +32,14 @@ class BahnApiService(
|
|||||||
return body?.stations ?: listOf()
|
return body?.stations ?: listOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchTimetable(eva: Eva, date: LocalDate, hour: Hour): BahnTimetable {
|
fun fetchTimetable(eva: Eva, hourAtDay: HourAtDay): BahnTimetable {
|
||||||
val requestEntity = buildRequestEntity<BahnTimetable>()
|
val requestEntity = buildRequestEntity<BahnTimetable>()
|
||||||
val dateFormatter = DateTimeFormatter.ofPattern("yyMMdd")
|
val dateFormatter = DateTimeFormatter.ofPattern("yyMMdd")
|
||||||
val timeFormatter = DateTimeFormatter.ofPattern("HH")
|
val timeFormatter = DateTimeFormatter.ofPattern("HH")
|
||||||
val time = LocalTime.of(hour.value, 0)
|
val time = LocalTime.of(hourAtDay.hour.value, 0)
|
||||||
val response = restTemplate.exchange(
|
val response = restTemplate.exchange(
|
||||||
"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/plan/" +
|
"https://apis.deutschebahn.com/db-api-marketplace/apis/timetables/v1/plan/" +
|
||||||
"${eva}/${date.format(dateFormatter)}/${time.format(timeFormatter)}",
|
"${eva}/${hourAtDay.date.format(dateFormatter)}/${time.format(timeFormatter)}",
|
||||||
HttpMethod.GET,
|
HttpMethod.GET,
|
||||||
requestEntity,
|
requestEntity,
|
||||||
BahnTimetable::class.java
|
BahnTimetable::class.java
|
||||||
|
|||||||
+3
-3
@@ -6,9 +6,9 @@ import de.twomartens.timetable.bahnApi.model.dto.BahnStation
|
|||||||
import de.twomartens.timetable.bahnApi.model.dto.BahnTimetable
|
import de.twomartens.timetable.bahnApi.model.dto.BahnTimetable
|
||||||
import de.twomartens.timetable.bahnApi.repository.BahnStationRepository
|
import de.twomartens.timetable.bahnApi.repository.BahnStationRepository
|
||||||
import de.twomartens.timetable.bahnApi.repository.BahnTimetableRepository
|
import de.twomartens.timetable.bahnApi.repository.BahnTimetableRepository
|
||||||
|
import de.twomartens.timetable.model.base.HourAtDay
|
||||||
import org.mapstruct.factory.Mappers
|
import org.mapstruct.factory.Mappers
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.time.LocalDateTime
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class BahnDatabaseService(
|
class BahnDatabaseService(
|
||||||
@@ -23,7 +23,7 @@ class BahnDatabaseService(
|
|||||||
.map { bahnStationMapper.mapToDB(it) })
|
.map { bahnStationMapper.mapToDB(it) })
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeTimetable(timetable: BahnTimetable, dateTime: LocalDateTime) {
|
fun storeTimetable(timetable: BahnTimetable, hourAtDay: HourAtDay) {
|
||||||
bahnTimetableRepository.save(bahnTimetableMapper.mapToDB(timetable, dateTime))
|
bahnTimetableRepository.save(bahnTimetableMapper.mapToDB(timetable, hourAtDay.dateTime))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
package de.twomartens.timetable.bahnApi.service
|
||||||
|
|
||||||
|
import de.twomartens.timetable.bahnApi.model.Eva
|
||||||
|
import de.twomartens.timetable.bahnApi.model.FetchDates
|
||||||
|
import de.twomartens.timetable.bahnApi.model.TaskFactory
|
||||||
|
import de.twomartens.timetable.bahnApi.model.db.ScheduledFetchTask
|
||||||
|
import de.twomartens.timetable.bahnApi.repository.ScheduledFetchTaskRepository
|
||||||
|
import de.twomartens.timetable.model.base.Hour
|
||||||
|
import de.twomartens.timetable.model.base.HourAtDay
|
||||||
|
import de.twomartens.timetable.model.db.TswRoute
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class ScheduledTaskService(
|
||||||
|
private val scheduledFetchTaskRepository: ScheduledFetchTaskRepository,
|
||||||
|
private val taskFactory: TaskFactory,
|
||||||
|
private val taskScheduler: TaskScheduler
|
||||||
|
) {
|
||||||
|
fun initializeScheduledTasks() {
|
||||||
|
log.info { "Initialize persisted scheduled fetch tasks" }
|
||||||
|
val scheduledTasks = scheduledFetchTaskRepository.findAll()
|
||||||
|
taskFactory.initializeWithExistingTasks(scheduledTasks)
|
||||||
|
taskScheduler.scheduleFetchTasks(scheduledTasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun scheduleTimetableFetch(tswRoute: TswRoute, fetchedDate: LocalDate) {
|
||||||
|
log.info {
|
||||||
|
"Schedule timetable fetch: [route ${tswRoute.name}]"
|
||||||
|
}
|
||||||
|
val fetchDates = calculateDatesToFetch(fetchedDate)
|
||||||
|
val newTasks = buildScheduledTasks(tswRoute, fetchDates)
|
||||||
|
|
||||||
|
storeTasksInDatabase(newTasks)
|
||||||
|
taskScheduler.scheduleFetchTasks(newTasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateDatesToFetch(fetchedDate: LocalDate): FetchDates {
|
||||||
|
val previousDay = fetchedDate.minusDays(1)
|
||||||
|
val nextDay = fetchedDate.plusDays(1)
|
||||||
|
return FetchDates(previousDay, fetchedDate, nextDay)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildScheduledTasks(
|
||||||
|
tswRoute: TswRoute,
|
||||||
|
fetchDates: FetchDates
|
||||||
|
): List<ScheduledFetchTask> {
|
||||||
|
val newTasks = mutableListOf<ScheduledFetchTask>()
|
||||||
|
tswRoute.stationIds.forEach {
|
||||||
|
val stationId = it
|
||||||
|
val eva = Eva.of(stationId)
|
||||||
|
var hourAtDay = HourAtDay.of(Hour.of(23), fetchDates.previousDay)
|
||||||
|
var newTask = taskFactory.createTaskAndUpdateCounter(eva, hourAtDay)
|
||||||
|
newTasks.add(newTask)
|
||||||
|
for (hour in 0..23) {
|
||||||
|
hourAtDay = HourAtDay.of(Hour.of(hour), fetchDates.fetchDate)
|
||||||
|
newTask = taskFactory.createTaskAndUpdateCounter(eva, hourAtDay)
|
||||||
|
newTasks.add(newTask)
|
||||||
|
}
|
||||||
|
for (hour in 0..3) {
|
||||||
|
hourAtDay = HourAtDay.of(Hour.of(hour), fetchDates.nextDate)
|
||||||
|
newTask = taskFactory.createTaskAndUpdateCounter(eva, hourAtDay)
|
||||||
|
newTasks.add(newTask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newTasks
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun storeTasksInDatabase(newTasks: List<ScheduledFetchTask>) {
|
||||||
|
scheduledFetchTaskRepository.saveAll(newTasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
package de.twomartens.timetable.bahnApi.service
|
||||||
|
|
||||||
|
import de.twomartens.timetable.bahnApi.model.db.ScheduledFetchTask
|
||||||
|
import de.twomartens.timetable.bahnApi.model.dto.BahnTimetable
|
||||||
|
import de.twomartens.timetable.bahnApi.repository.ScheduledFetchTaskRepository
|
||||||
|
import de.twomartens.timetable.bahnApi.tasks.DeleteScheduledTask
|
||||||
|
import de.twomartens.timetable.bahnApi.tasks.FetchTimetableTask
|
||||||
|
import de.twomartens.timetable.bahnApi.tasks.StoreTimetableTask
|
||||||
|
import de.twomartens.timetable.model.base.HourAtDay
|
||||||
|
import de.twomartens.timetable.property.TimeProperties
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class TaskScheduler(
|
||||||
|
private val timeProperties: TimeProperties,
|
||||||
|
private val threadPoolTaskScheduler: ThreadPoolTaskScheduler,
|
||||||
|
private val threadPoolTaskExecutor: ThreadPoolTaskExecutor,
|
||||||
|
private val bahnApiService: BahnApiService,
|
||||||
|
private val bahnDatabaseService: BahnDatabaseService,
|
||||||
|
private val scheduledFetchTaskRepository: ScheduledFetchTaskRepository
|
||||||
|
) {
|
||||||
|
fun scheduleFetchTasks(scheduledTasks: List<ScheduledFetchTask>) {
|
||||||
|
scheduledTasks.forEach {
|
||||||
|
val zonedExecutionTime = it.scheduledExecutionDateTime
|
||||||
|
.atZone(timeProperties.defaultTimeZone)
|
||||||
|
scheduleDeleteTask(it, zonedExecutionTime)
|
||||||
|
scheduleFetchTimetableTask(it, zonedExecutionTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun scheduleStoreTask(timetable: BahnTimetable, hourAtDay: HourAtDay) {
|
||||||
|
val storeTask = StoreTimetableTask(timetable, hourAtDay, bahnDatabaseService)
|
||||||
|
threadPoolTaskExecutor.execute(storeTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scheduleDeleteTask(
|
||||||
|
scheduledFetchTask: ScheduledFetchTask,
|
||||||
|
zonedExecutionTime: ZonedDateTime
|
||||||
|
) {
|
||||||
|
val deleteTask = DeleteScheduledTask(scheduledFetchTaskRepository, scheduledFetchTask)
|
||||||
|
threadPoolTaskScheduler.schedule(deleteTask, zonedExecutionTime.toInstant())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scheduleFetchTimetableTask(
|
||||||
|
scheduledFetchTask: ScheduledFetchTask,
|
||||||
|
zonedExecutionTime: ZonedDateTime
|
||||||
|
) {
|
||||||
|
val timetableTask = FetchTimetableTask(scheduledFetchTask.eva,
|
||||||
|
scheduledFetchTask.fetchedDateTime,
|
||||||
|
bahnApiService, this)
|
||||||
|
threadPoolTaskScheduler.schedule(timetableTask, zonedExecutionTime.toInstant())
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -14,8 +14,8 @@ open class DeleteScheduledTask(
|
|||||||
log.info {
|
log.info {
|
||||||
"Delete scheduled fetch task after the fetch occurred: " +
|
"Delete scheduled fetch task after the fetch occurred: " +
|
||||||
"[eva: ${scheduledFetchTask.eva}], " +
|
"[eva: ${scheduledFetchTask.eva}], " +
|
||||||
"[date: ${scheduledFetchTask.fetchedDate}], " +
|
"[date: ${scheduledFetchTask.fetchedDateTime.date}], " +
|
||||||
"[time: ${scheduledFetchTask.fetchedTime}]"
|
"[time: ${scheduledFetchTask.fetchedDateTime.hour}]"
|
||||||
}
|
}
|
||||||
scheduledFetchTaskRepository.delete(scheduledFetchTask)
|
scheduledFetchTaskRepository.delete(scheduledFetchTask)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-14
@@ -1,30 +1,24 @@
|
|||||||
package de.twomartens.timetable.bahnApi.tasks
|
package de.twomartens.timetable.bahnApi.tasks
|
||||||
|
|
||||||
|
import de.twomartens.timetable.bahnApi.model.Eva
|
||||||
import de.twomartens.timetable.bahnApi.service.BahnApiService
|
import de.twomartens.timetable.bahnApi.service.BahnApiService
|
||||||
import de.twomartens.timetable.bahnApi.service.BahnDatabaseService
|
import de.twomartens.timetable.bahnApi.service.TaskScheduler
|
||||||
import de.twomartens.timetable.model.Eva
|
import de.twomartens.timetable.model.base.HourAtDay
|
||||||
import de.twomartens.timetable.model.Hour
|
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.springframework.scheduling.annotation.Async
|
import org.springframework.scheduling.annotation.Async
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
|
|
||||||
import java.time.LocalDate
|
|
||||||
|
|
||||||
@Async
|
@Async
|
||||||
open class FetchTimetableTask(
|
open class FetchTimetableTask(
|
||||||
private val eva: Eva,
|
private val eva: Eva,
|
||||||
private val date: LocalDate,
|
private val hourAtDay: HourAtDay,
|
||||||
private val hour: Hour,
|
|
||||||
private val bahnApiService: BahnApiService,
|
private val bahnApiService: BahnApiService,
|
||||||
private val bahnDatabaseService: BahnDatabaseService,
|
private val scheduler: TaskScheduler) : Runnable {
|
||||||
private val executor: ThreadPoolTaskExecutor) : Runnable {
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
log.info {
|
log.info {
|
||||||
"Fetch timetable: [eva: $eva], [date: $date], [hour: $hour]"
|
"Fetch timetable: [eva: $eva], [date: ${hourAtDay.date}], [hour: ${hourAtDay.hour}]"
|
||||||
}
|
}
|
||||||
val timetable = bahnApiService.fetchTimetable(eva, date, hour)
|
val timetable = bahnApiService.fetchTimetable(eva, hourAtDay)
|
||||||
val storeTask = StoreTimetableTask(timetable, date.atTime(hour.value, 0),
|
scheduler.scheduleStoreTask(timetable, hourAtDay)
|
||||||
bahnDatabaseService)
|
|
||||||
executor.execute(storeTask)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
+3
-3
@@ -2,19 +2,19 @@ package de.twomartens.timetable.bahnApi.tasks
|
|||||||
|
|
||||||
import de.twomartens.timetable.bahnApi.model.dto.BahnTimetable
|
import de.twomartens.timetable.bahnApi.model.dto.BahnTimetable
|
||||||
import de.twomartens.timetable.bahnApi.service.BahnDatabaseService
|
import de.twomartens.timetable.bahnApi.service.BahnDatabaseService
|
||||||
|
import de.twomartens.timetable.model.base.HourAtDay
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
import org.springframework.scheduling.annotation.Async
|
import org.springframework.scheduling.annotation.Async
|
||||||
import java.time.LocalDateTime
|
|
||||||
|
|
||||||
@Async
|
@Async
|
||||||
open class StoreTimetableTask(
|
open class StoreTimetableTask(
|
||||||
private val timetable: BahnTimetable,
|
private val timetable: BahnTimetable,
|
||||||
private val dateTime: LocalDateTime,
|
private val hourAtDay: HourAtDay,
|
||||||
private val bahnDatabaseService: BahnDatabaseService
|
private val bahnDatabaseService: BahnDatabaseService
|
||||||
) : Runnable {
|
) : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
log.info { "Store timetable: [eva: ${timetable.eva}], station: ${timetable.station}]" }
|
log.info { "Store timetable: [eva: ${timetable.eva}], station: ${timetable.station}]" }
|
||||||
bahnDatabaseService.storeTimetable(timetable, dateTime)
|
bahnDatabaseService.storeTimetable(timetable, hourAtDay)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
+8
-4
@@ -1,8 +1,9 @@
|
|||||||
package de.twomartens.timetable.controller
|
package de.twomartens.timetable.controller
|
||||||
|
|
||||||
import de.twomartens.timetable.model.NonEmptyString
|
import de.twomartens.timetable.bahnApi.service.ScheduledTaskService
|
||||||
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
|
import de.twomartens.timetable.model.db.UserId
|
||||||
import de.twomartens.timetable.repository.TswRouteRepository
|
import de.twomartens.timetable.repository.TswRouteRepository
|
||||||
import de.twomartens.timetable.service.ScheduledTaskService
|
|
||||||
import io.swagger.v3.oas.annotations.Operation
|
import io.swagger.v3.oas.annotations.Operation
|
||||||
import io.swagger.v3.oas.annotations.Parameter
|
import io.swagger.v3.oas.annotations.Parameter
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||||
@@ -34,8 +35,11 @@ class TimetableController(
|
|||||||
description = "Request does not follow specification"
|
description = "Request does not follow specification"
|
||||||
)]
|
)]
|
||||||
)
|
)
|
||||||
@PostMapping("/{routeName}/fetchTimetable/{date}")
|
@PostMapping("/{userId}/{routeName}/fetchTimetable/{date}")
|
||||||
fun scheduleFetch(
|
fun scheduleFetch(
|
||||||
|
@PathVariable @Parameter(description = "The id of the user",
|
||||||
|
example = "1",
|
||||||
|
required = true) userId: UserId,
|
||||||
@PathVariable @Parameter(description = "The name of the TSW route.",
|
@PathVariable @Parameter(description = "The name of the TSW route.",
|
||||||
example = "CologneAachen",
|
example = "CologneAachen",
|
||||||
required = true) routeName: NonEmptyString,
|
required = true) routeName: NonEmptyString,
|
||||||
@@ -47,7 +51,7 @@ class TimetableController(
|
|||||||
if (!date.isAfter(LocalDate.now(clock))) {
|
if (!date.isAfter(LocalDate.now(clock))) {
|
||||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Date must be in the future")
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Date must be in the future")
|
||||||
}
|
}
|
||||||
val route = routeRepository.findByName(routeName)
|
val route = routeRepository.findByUserIdAndName(userId, routeName)
|
||||||
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST,
|
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST,
|
||||||
"Route name must belong to existing route")
|
"Route name must belong to existing route")
|
||||||
scheduledTaskService.scheduleTimetableFetch(route, date)
|
scheduledTaskService.scheduleTimetableFetch(route, date)
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
package de.twomartens.timetable.model
|
|
||||||
|
|
||||||
@JvmInline
|
|
||||||
value class Eva(val value: Int) {
|
|
||||||
init {
|
|
||||||
require(value in 8000000..8999999) {
|
|
||||||
"Eva number must start with an 8 and have 7 digits"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package de.twomartens.timetable.model
|
|
||||||
|
|
||||||
@JvmInline
|
|
||||||
value class Hour(val value: Int) {
|
|
||||||
init {
|
|
||||||
require(value in 0..23) {
|
|
||||||
"Hour must be between 0 and 23"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package de.twomartens.timetable.model
|
|
||||||
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
|
|
||||||
@JvmInline
|
|
||||||
value class HourAtDay(val value: LocalDateTime) {
|
|
||||||
init {
|
|
||||||
require(value.minute == 0 && value.second == 0 && value.nano == 0) {
|
|
||||||
"DateTime must only include an hour and the date."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package de.twomartens.timetable.model
|
|
||||||
|
|
||||||
import de.twomartens.timetable.bahnApi.model.db.ScheduledFetchTask
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.temporal.ChronoUnit
|
|
||||||
|
|
||||||
private const val SECONDS_BETWEEN_TASKS = 2
|
|
||||||
|
|
||||||
class Tasks {
|
|
||||||
private val _tasks: MutableMap<HourAtDay, MutableList<ScheduledFetchTask>> = mutableMapOf()
|
|
||||||
var tasks: List<ScheduledFetchTask> = listOf()
|
|
||||||
get() {
|
|
||||||
return _tasks.flatMap {
|
|
||||||
it.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set(newTasks) {
|
|
||||||
_tasks.clear()
|
|
||||||
newTasks.forEach { addTask(it) }
|
|
||||||
field = newTasks
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createAndAddTask(eva: Eva, date: LocalDate, hour: Hour): ScheduledFetchTask {
|
|
||||||
val hourAtDay = HourAtDay(date.atTime(hour.value, 0))
|
|
||||||
val tasksForHourAtDay = _tasks.computeIfAbsent(hourAtDay) { mutableListOf() }
|
|
||||||
val secondInHourToExecute = tasksForHourAtDay.size * SECONDS_BETWEEN_TASKS
|
|
||||||
val fetchDateTime = hourAtDay.value.plusSeconds(secondInHourToExecute.toLong())
|
|
||||||
val task = ScheduledFetchTask(eva, date, hour, fetchDateTime)
|
|
||||||
|
|
||||||
tasksForHourAtDay.add(task)
|
|
||||||
|
|
||||||
return task
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addTask(scheduledFetchTask: ScheduledFetchTask) {
|
|
||||||
val scheduledTime = scheduledFetchTask.scheduledExecutionDateTime
|
|
||||||
val hourAtDay = HourAtDay(scheduledTime.truncatedTo(ChronoUnit.HOURS))
|
|
||||||
val tasksForHourAtDay = _tasks.computeIfAbsent(hourAtDay) { mutableListOf() }
|
|
||||||
|
|
||||||
tasksForHourAtDay.add(scheduledFetchTask)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package de.twomartens.timetable.model.base
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class Hour private constructor(val value: Int) {
|
||||||
|
init {
|
||||||
|
require(value in 0..23) {
|
||||||
|
"Hour must be between 0 and 23"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun of(hour: Int): Hour {
|
||||||
|
return Hour(hour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package de.twomartens.timetable.model.base
|
||||||
|
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class HourAtDay private constructor(private val value: LocalDateTime) {
|
||||||
|
init {
|
||||||
|
require(value.minute == 0 && value.second == 0 && value.nano == 0) {
|
||||||
|
"DateTime must only include an hour and the date."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val date: LocalDate
|
||||||
|
get() {
|
||||||
|
return value.toLocalDate()
|
||||||
|
}
|
||||||
|
|
||||||
|
val hour: Hour
|
||||||
|
get() {
|
||||||
|
return Hour.of(value.hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dateTime: LocalDateTime
|
||||||
|
get() {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun of(dateTime: LocalDateTime): HourAtDay {
|
||||||
|
return HourAtDay(dateTime.truncatedTo(ChronoUnit.HOURS))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun of(hour: Hour, date: LocalDate): HourAtDay {
|
||||||
|
return HourAtDay(date.atTime(hour.value, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package de.twomartens.timetable.model
|
package de.twomartens.timetable.model.base
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class NonEmptyString(val name: String) {
|
value class NonEmptyString(val name: String) {
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package de.twomartens.timetable.model.base
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class PositiveInteger(val value: Int) {
|
||||||
|
init {
|
||||||
|
require(value > 0) {
|
||||||
|
"Value must be positive integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package de.twomartens.timetable.model.db
|
||||||
|
|
||||||
|
import de.twomartens.timetable.model.base.PositiveInteger
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class CoachCapacity(val capacity: PositiveInteger)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package de.twomartens.timetable.model.db
|
||||||
|
|
||||||
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class CountryCode(val countryCode: NonEmptyString) {
|
||||||
|
init {
|
||||||
|
require(countryCode.name.length == 2) {
|
||||||
|
"CountryCode must be only two characters long to conform to ISO 3166-1 alpha-2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package de.twomartens.timetable.model.db
|
||||||
|
|
||||||
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
data class Depot(
|
||||||
|
var name: NonEmptyString,
|
||||||
|
var nearestStationId: StationId,
|
||||||
|
var tracks: Map<TrackId, CoachCapacity>,
|
||||||
|
var formationTimes: Map<FormationId, Duration>
|
||||||
|
)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package de.twomartens.timetable.model.db
|
||||||
|
|
||||||
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
|
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.CompoundIndex
|
||||||
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Document
|
||||||
|
@CompoundIndex(def = "{'userId': 1, 'formationId': 1}", unique = true)
|
||||||
|
data class Formation(
|
||||||
|
var userId: UserId,
|
||||||
|
var positiveInteger: FormationId,
|
||||||
|
var name: NonEmptyString
|
||||||
|
) {
|
||||||
|
@Id
|
||||||
|
var id: ObjectId = ObjectId()
|
||||||
|
|
||||||
|
@CreatedDate
|
||||||
|
lateinit var created: Instant
|
||||||
|
|
||||||
|
@LastModifiedDate
|
||||||
|
lateinit var lastModified: Instant
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package de.twomartens.timetable.model.db
|
||||||
|
|
||||||
|
import de.twomartens.timetable.model.base.PositiveInteger
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class FormationId(val id: PositiveInteger)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package de.twomartens.timetable.model.db
|
||||||
|
|
||||||
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
data class Portal(
|
||||||
|
var name: NonEmptyString,
|
||||||
|
var nearestStationId: StationId,
|
||||||
|
var formationTimes: Map<FormationId, Duration>
|
||||||
|
)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.twomartens.timetable.model.db
|
package de.twomartens.timetable.model.db
|
||||||
|
|
||||||
data class Section(
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
var name: String
|
|
||||||
)
|
@JvmInline
|
||||||
|
value class Section(val name: NonEmptyString)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package de.twomartens.timetable.model.db
|
package de.twomartens.timetable.model.db
|
||||||
|
|
||||||
import de.twomartens.timetable.model.Eva
|
|
||||||
import org.bson.types.ObjectId
|
import org.bson.types.ObjectId
|
||||||
import org.springframework.data.annotation.CreatedDate
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import org.springframework.data.annotation.Id
|
import org.springframework.data.annotation.Id
|
||||||
@@ -11,7 +10,7 @@ import java.time.Instant
|
|||||||
|
|
||||||
@Document
|
@Document
|
||||||
data class Station(
|
data class Station(
|
||||||
@Indexed(unique = true) var eva: Eva,
|
@Indexed(unique = true) var stationId: StationId,
|
||||||
var name: String,
|
var name: String,
|
||||||
var platforms: List<Platform>
|
var platforms: List<Platform>
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package de.twomartens.timetable.model.db
|
||||||
|
|
||||||
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class StationId(val id: NonEmptyString) {
|
||||||
|
init {
|
||||||
|
require(idPattern.matches(id.name)) {
|
||||||
|
"StationId must start with two character country code, followed by a dash, " +
|
||||||
|
"and at least one word character"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val idPattern = Regex("^\\w{2}-(?<countryStationId>\\w.*)")
|
||||||
|
}
|
||||||
|
|
||||||
|
val stationIdWithinCountry: NonEmptyString
|
||||||
|
get() {
|
||||||
|
val matchResult = idPattern.matchEntire(id.name)
|
||||||
|
val groupMatch = matchResult?.groups?.get("countryStationId")?.value
|
||||||
|
return when {
|
||||||
|
matchResult == null || groupMatch == null ->
|
||||||
|
throw IllegalArgumentException("id wasn't matched by pattern")
|
||||||
|
|
||||||
|
else -> NonEmptyString(groupMatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package de.twomartens.timetable.model.db
|
||||||
|
|
||||||
|
import de.twomartens.timetable.model.base.PositiveInteger
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class TrackId(val id: PositiveInteger)
|
||||||
@@ -1,30 +1,23 @@
|
|||||||
package de.twomartens.timetable.model.db
|
package de.twomartens.timetable.model.db
|
||||||
|
|
||||||
import de.twomartens.timetable.model.Eva
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
import de.twomartens.timetable.model.NonEmptyString
|
|
||||||
import org.bson.types.ObjectId
|
import org.bson.types.ObjectId
|
||||||
import org.springframework.data.annotation.CreatedDate
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import org.springframework.data.annotation.Id
|
import org.springframework.data.annotation.Id
|
||||||
import org.springframework.data.annotation.LastModifiedDate
|
import org.springframework.data.annotation.LastModifiedDate
|
||||||
import org.springframework.data.mongodb.core.index.Indexed
|
import org.springframework.data.mongodb.core.index.CompoundIndex
|
||||||
import org.springframework.data.mongodb.core.mapping.Document
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@Document
|
@Document
|
||||||
|
@CompoundIndex(def = "{'userId': 1, 'name': 1}", unique = true)
|
||||||
data class TswRoute(
|
data class TswRoute(
|
||||||
@Indexed(unique = true) var name: NonEmptyString,
|
var userId: UserId,
|
||||||
/**
|
var name: NonEmptyString,
|
||||||
* Ordered list of stations that represent the order of stations on the route.
|
var countryCode: CountryCode,
|
||||||
*
|
var stationIds: List<StationId>,
|
||||||
* Not every stop is necessarily called at by each train. There may be multiple
|
var portals: List<Portal>,
|
||||||
* parallel paths through a route (like S-Bahn track and long-distance track).
|
var depots: List<Depot>
|
||||||
* In such cases, the S-Bahn stops are listed as part of the route as they appear
|
|
||||||
* geographically.
|
|
||||||
*
|
|
||||||
* Only the station ids are used here as the duplication would be heavy and
|
|
||||||
* consistency hard to ensure when potentially repeated across various routes.
|
|
||||||
*/
|
|
||||||
var stations: List<Eva>
|
|
||||||
) {
|
) {
|
||||||
@Id
|
@Id
|
||||||
var id: ObjectId = ObjectId()
|
var id: ObjectId = ObjectId()
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package de.twomartens.timetable.model.db
|
||||||
|
|
||||||
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
|
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.mapping.Document
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Document
|
||||||
|
data class User(
|
||||||
|
var userId: UserId,
|
||||||
|
var name: NonEmptyString
|
||||||
|
) {
|
||||||
|
@Id
|
||||||
|
var id: ObjectId = ObjectId()
|
||||||
|
|
||||||
|
@CreatedDate
|
||||||
|
lateinit var created: Instant
|
||||||
|
|
||||||
|
@LastModifiedDate
|
||||||
|
lateinit var lastModified: Instant
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package de.twomartens.timetable.model.db
|
||||||
|
|
||||||
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class UserId(val id: NonEmptyString)
|
||||||
+3
-2
@@ -1,10 +1,11 @@
|
|||||||
package de.twomartens.timetable.repository
|
package de.twomartens.timetable.repository
|
||||||
|
|
||||||
import de.twomartens.timetable.model.NonEmptyString
|
import de.twomartens.timetable.model.base.NonEmptyString
|
||||||
import de.twomartens.timetable.model.db.TswRoute
|
import de.twomartens.timetable.model.db.TswRoute
|
||||||
|
import de.twomartens.timetable.model.db.UserId
|
||||||
import org.bson.types.ObjectId
|
import org.bson.types.ObjectId
|
||||||
import org.springframework.data.mongodb.repository.MongoRepository
|
import org.springframework.data.mongodb.repository.MongoRepository
|
||||||
|
|
||||||
interface TswRouteRepository : MongoRepository<TswRoute, ObjectId> {
|
interface TswRouteRepository : MongoRepository<TswRoute, ObjectId> {
|
||||||
fun findByName(name: NonEmptyString): TswRoute?
|
fun findByUserIdAndName(userId: UserId, name: NonEmptyString): TswRoute?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
package de.twomartens.timetable.service
|
|
||||||
|
|
||||||
import de.twomartens.timetable.bahnApi.model.db.ScheduledFetchTask
|
|
||||||
import de.twomartens.timetable.bahnApi.repository.ScheduledFetchTaskRepository
|
|
||||||
import de.twomartens.timetable.bahnApi.service.BahnApiService
|
|
||||||
import de.twomartens.timetable.bahnApi.service.BahnDatabaseService
|
|
||||||
import de.twomartens.timetable.bahnApi.tasks.DeleteScheduledTask
|
|
||||||
import de.twomartens.timetable.bahnApi.tasks.FetchTimetableTask
|
|
||||||
import de.twomartens.timetable.model.Hour
|
|
||||||
import de.twomartens.timetable.model.Tasks
|
|
||||||
import de.twomartens.timetable.model.db.TswRoute
|
|
||||||
import de.twomartens.timetable.property.TimeProperties
|
|
||||||
import mu.KotlinLogging
|
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
|
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import java.time.LocalDate
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class ScheduledTaskService(
|
|
||||||
private val timeProperties: TimeProperties,
|
|
||||||
private val scheduledFetchTaskRepository: ScheduledFetchTaskRepository,
|
|
||||||
private val threadPoolTaskScheduler: ThreadPoolTaskScheduler,
|
|
||||||
private val bahnApiService: BahnApiService,
|
|
||||||
private val bahnDatabaseService: BahnDatabaseService,
|
|
||||||
private val threadPoolTaskExecutor: ThreadPoolTaskExecutor
|
|
||||||
) {
|
|
||||||
private val tasks: Tasks = Tasks()
|
|
||||||
|
|
||||||
fun initializeScheduledTasks() {
|
|
||||||
log.info { "Initialize persisted scheduled fetch tasks" }
|
|
||||||
val scheduledTasks = scheduledFetchTaskRepository.findAll()
|
|
||||||
tasks.tasks = scheduledTasks
|
|
||||||
scheduleTasks(scheduledTasks)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun scheduleTimetableFetch(tswRoute: TswRoute, fetchedDate: LocalDate) {
|
|
||||||
log.info {
|
|
||||||
"Schedule timetable fetch: [route ${tswRoute.name}]"
|
|
||||||
}
|
|
||||||
val previousDay = fetchedDate.minusDays(1)
|
|
||||||
val nextDay = fetchedDate.plusDays(1)
|
|
||||||
val newTasks = mutableListOf<ScheduledFetchTask>()
|
|
||||||
tswRoute.stations.forEach {
|
|
||||||
val eva = it
|
|
||||||
var newTask = tasks.createAndAddTask(eva, date = previousDay, hour = Hour(23))
|
|
||||||
newTasks.add(newTask)
|
|
||||||
for (hour in 0..23) {
|
|
||||||
newTask = tasks.createAndAddTask(eva, date = fetchedDate, hour = Hour(hour))
|
|
||||||
newTasks.add(newTask)
|
|
||||||
}
|
|
||||||
for (hour in 0..3) {
|
|
||||||
newTask = tasks.createAndAddTask(eva, date = nextDay, hour = Hour(hour))
|
|
||||||
newTasks.add(newTask)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduledFetchTaskRepository.saveAll(newTasks)
|
|
||||||
scheduleTasks(newTasks)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun scheduleTasks(scheduledTasks: List<ScheduledFetchTask>) {
|
|
||||||
scheduledTasks.forEach {
|
|
||||||
val zonedExecutionTime = it.scheduledExecutionDateTime
|
|
||||||
.atZone(timeProperties.defaultTimeZone)
|
|
||||||
val deleteTask = DeleteScheduledTask(scheduledFetchTaskRepository, it)
|
|
||||||
val timetableTask = FetchTimetableTask(it.eva, it.fetchedDate, it.fetchedTime,
|
|
||||||
bahnApiService, bahnDatabaseService, threadPoolTaskExecutor)
|
|
||||||
threadPoolTaskScheduler.schedule(timetableTask, zonedExecutionTime.toInstant())
|
|
||||||
threadPoolTaskScheduler.schedule(deleteTask, zonedExecutionTime.toInstant())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val log = KotlinLogging.logger {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user