Skip to content

Commit

Permalink
Merge pull request #491 from boudicca-events/abl/structured-data-in-e…
Browse files Browse the repository at this point in the history
…ventcollectors

Abl/structured data in eventcollectors + enricher
  • Loading branch information
kadhonn authored Sep 6, 2024
2 parents 07af267 + 51815fd commit c404378
Show file tree
Hide file tree
Showing 57 changed files with 701 additions and 158 deletions.
119 changes: 119 additions & 0 deletions boudicca.base/common-model/src/main/kotlin/base/boudicca/Properties.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package base.boudicca

import base.boudicca.format.DateFormat
import base.boudicca.format.ListFormat
import base.boudicca.format.NumberFormat
import base.boudicca.model.structured.Key
import base.boudicca.model.structured.VariantConstants
import java.lang.reflect.InvocationTargetException
import java.net.URI
import java.time.OffsetDateTime

interface Property<T> {
@Throws(IllegalArgumentException::class)
fun parseToString(value: T): String

@Throws(IllegalArgumentException::class)
fun parseFromString(string: String): T

fun getKey(language: String? = null): Key
}

abstract class AbstractProperty<T>(private val propertyName: String, private val formatValue: String) : Property<T> {
override fun getKey(language: String?): Key {
val builder = Key.builder(propertyName)
if (!language.isNullOrEmpty()) {
builder.withVariant(VariantConstants.LANGUAGE_VARIANT_NAME, language)
}
if (formatValue.isNotEmpty()) {
builder.withVariant(VariantConstants.FORMAT_VARIANT_NAME, formatValue)
}
return builder.build()
}
}

class TextProperty(propertyName: String) : AbstractProperty<String>(propertyName, VariantConstants.FormatVariantConstants.TEXT_FORMAT_NAME) {
override fun parseToString(value: String): String {
return value
}

override fun parseFromString(string: String): String {
return string
}
}

class MarkdownProperty(propertyName: String) : AbstractProperty<String>(propertyName, VariantConstants.FormatVariantConstants.MARKDOWN_FORMAT_NAME) {
override fun parseToString(value: String): String {
return value
}

override fun parseFromString(string: String): String {
return string
}
}

class UrlProperty(propertyName: String) : AbstractProperty<URI>(propertyName, VariantConstants.FormatVariantConstants.TEXT_FORMAT_NAME) {
override fun parseToString(value: URI): String {
return value.toString()
}

override fun parseFromString(string: String): URI {
return URI.create(string)
}
}

class DateProperty(propertyName: String) : AbstractProperty<OffsetDateTime>(propertyName, VariantConstants.FormatVariantConstants.DATE_FORMAT_NAME) {
override fun parseToString(value: OffsetDateTime): String {
return DateFormat.parseToString(value)
}

override fun parseFromString(string: String): OffsetDateTime {
return DateFormat.parseFromString(string)
}
}

class ListProperty(propertyName: String) : AbstractProperty<List<String>>(propertyName, VariantConstants.FormatVariantConstants.LIST_FORMAT_NAME) {
override fun parseToString(value: List<String>): String {
return ListFormat.parseToString(value)
}

override fun parseFromString(string: String): List<String> {
return ListFormat.parseFromString(string)
}
}

class NumberProperty(propertyName: String) : AbstractProperty<Number>(propertyName, VariantConstants.FormatVariantConstants.NUMBER_FORMAT_NAME) {
override fun parseToString(value: Number): String {
return NumberFormat.parseToString(value)
}

override fun parseFromString(string: String): Number {
return NumberFormat.parseFromString(string)
}
}

@Suppress("UNCHECKED_CAST")
class EnumProperty<E : Enum<E>>(propertyName: String, private val enumClass: Class<E>) :
AbstractProperty<E>(propertyName, VariantConstants.FormatVariantConstants.TEXT_FORMAT_NAME) {
override fun parseToString(value: E): String {
return value.name
}

override fun parseFromString(string: String): E {
try {
return enumClass.getMethod("valueOf", String::class.java).invoke(null, string.uppercase()) as E
} catch (e: InvocationTargetException) {
throw IllegalArgumentException("error getting enum constant", e)
}
}
}

class BooleanProperty(propertyName: String) : AbstractProperty<Boolean>(propertyName, VariantConstants.FormatVariantConstants.TEXT_FORMAT_NAME) {
override fun parseToString(value: Boolean): String {
return value.toString()
}

override fun parseFromString(string: String): Boolean {
return string.toBoolean()
}
}
Original file line number Diff line number Diff line change
@@ -1,51 +1,106 @@
package base.boudicca

import base.boudicca.model.EventCategory
import base.boudicca.model.RecurrenceType
import base.boudicca.model.Registration

object SemanticKeys {

// general properties

const val NAME = "name"
val NAME_PROPERTY = TextProperty(NAME)

const val STARTDATE = "startDate"
val STARTDATE_PROPERTY = DateProperty(STARTDATE)

const val ENDDATE = "endDate"
val ENDDATE_PROPERTY = DateProperty(ENDDATE)

const val URL = "url"
val URL_PROPERTY = UrlProperty(URL)

const val TYPE = "type"
val TYPE_PROPERTY = TextProperty(TYPE)

const val CATEGORY = "category"
val CATEGORY_PROPERTY = EnumProperty(CATEGORY, EventCategory::class.java)

const val DESCRIPTION = "description"
val DESCRIPTION_TEXT_PROPERTY = TextProperty(DESCRIPTION)
val DESCRIPTION_MARKDOWN_PROPERTY = MarkdownProperty(DESCRIPTION)

const val RECURRENCE_TYPE = "recurrence.type"
val RECURRENCE_TYPE_PROPERTY = EnumProperty(RECURRENCE_TYPE, RecurrenceType::class.java)

const val RECURRENCE_INTERVAL = "recurrence.interval"
val RECURRENCE_INTERVAL_PROPERTY = TextProperty(RECURRENCE_INTERVAL)

const val TAGS = "tags"
val TAGS_PROPERTY = ListProperty(TAGS)

const val REGISTRATION = "registration"
val REGISTRATION_PROPERTY = EnumProperty(REGISTRATION, Registration::class.java)

const val PICTURE_URL = "pictureUrl"
val PICTURE_URL_PROPERTY = UrlProperty(PICTURE_URL)

@Deprecated("use PICTURE_URL instead")
const val PICTUREURL = PICTURE_URL
const val PICTURE_ALT_TEXT = "pictureAltText"
val PICTURE_ALT_TEXT_PROPERTY = TextProperty(PICTURE_ALT_TEXT)

const val PICTURE_COPYRIGHT = "pictureCopyright"
val PICTURE_COPYRIGHT_PROPERTY = TextProperty(PICTURE_COPYRIGHT)

const val COLLECTORNAME = "collectorName"
val COLLECTORNAME_PROPERTY = TextProperty(COLLECTORNAME)

const val SOURCES = "sources"
val SOURCES_PROPERTY = ListProperty(SOURCES)

const val ADDITIONAL_EVENTS_URL = "additionalEventsFromSourceUrl"
val ADDITIONAL_EVENTS_URL_PROPERTY = UrlProperty(ADDITIONAL_EVENTS_URL)

// location properties

const val LOCATION_NAME = "location.name"
val LOCATION_NAME_PROPERTY = TextProperty(LOCATION_NAME)

const val LOCATION_URL = "location.url"
val LOCATION_URL_PROPERTY = UrlProperty(LOCATION_URL)

//TODO split coordinates
const val LOCATION_COORDINATES = "location.coordinates"
val LOCATION_COORDINATES_PROPERTY = TextProperty(LOCATION_COORDINATES)

const val LOCATION_CITY = "location.city"
val LOCATION_CITY_PROPERTY = TextProperty(LOCATION_CITY)

const val LOCATION_ADDRESS = "location.address"
val LOCATION_ADDRESS_PROPERTY = TextProperty(LOCATION_ADDRESS)

// accessibility properties

const val ACCESSIBILITY_ACCESSIBLEENTRY = "accessibility.accessibleEntry"
val ACCESSIBILITY_ACCESSIBLEENTRY_PROPERTY = BooleanProperty(ACCESSIBILITY_ACCESSIBLEENTRY)

const val ACCESSIBILITY_ACCESSIBLESEATS = "accessibility.accessibleSeats"
val ACCESSIBILITY_ACCESSIBLESEATS_PROPERTY = BooleanProperty(ACCESSIBILITY_ACCESSIBLESEATS)

const val ACCESSIBILITY_ACCESSIBLETOILETS = "accessibility.accessibleToilets"
val ACCESSIBILITY_ACCESSIBLETOILETS_PROPERTY = BooleanProperty(ACCESSIBILITY_ACCESSIBLETOILETS)

const val ACCESSIBILITY_AKTIVPASSLINZ = "accessibility.accessibleAktivpassLinz"
val ACCESSIBILITY_AKTIVPASSLINZ_PROPERTY = BooleanProperty(ACCESSIBILITY_AKTIVPASSLINZ)

const val ACCESSIBILITY_KULTURPASS = "accessibility.accessibleKulturpass"
val ACCESSIBILITY_KULTURPASS_PROPERTY = BooleanProperty(ACCESSIBILITY_KULTURPASS)

// concert properties

const val CONCERT_GENRE = "concert.genre"
val CONCERT_GENRE_PROPERTY = TextProperty(CONCERT_GENRE)

const val CONCERT_BANDLIST = "concert.bandlist"
val CONCERT_BANDLIST_PROPERTY = ListProperty(CONCERT_BANDLIST)

object Image {
const val URL = "url"
const val ALT_TEXT = "altText"
const val COPYRIGHT = "copyright"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package base.boudicca.format
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException
import kotlin.jvm.Throws

object DateFormat {
@Throws(IllegalArgumentException::class)
fun parseFromString(value: String): OffsetDateTime {
try {
return OffsetDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ object ListFormat {
return result
}

fun parseToString(value: List<String>): String? {
@Throws(IllegalArgumentException::class)
fun parseToString(value: List<String>): String {
if (value.isEmpty()) {
return null
throw IllegalArgumentException("and empty list cannot be parsed to a string value")
}
return value.joinToString(",") { it.replace("\\", "\\\\").replace(",", "\\,") }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package base.boudicca.format

import java.math.BigDecimal

object NumberFormat {

@Throws(IllegalArgumentException::class)
fun parseFromString(value: String): Number {
try {
return BigDecimal(value)
} catch (e: NumberFormatException) {
throw IllegalArgumentException("could not parse string $value to number")
}
}

fun parseToString(value: Number): String {
return value.toString()
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
package base.boudicca.keyfilters

import base.boudicca.model.Event
import base.boudicca.model.structured.Key
import base.boudicca.model.structured.StructuredEntry
import base.boudicca.model.structured.StructuredEvent
import base.boudicca.model.structured.Variant
import base.boudicca.model.toStructuredEntry


object KeyFilters {

fun filterKeys(keyFilter: Key, event: StructuredEvent): List<Pair<Key, String>> {
return filterKeys(keyFilter, Event.toEntry(event.toFlatEvent()).toStructuredEntry())
return filterKeys(keyFilter, event.toEntry())
}

fun filterKeys(keyFilter: Key, data: StructuredEntry): List<Pair<Key, String>> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package base.boudicca.model

import base.boudicca.SemanticKeys
import base.boudicca.format.DateFormat
import base.boudicca.keyfilters.KeySelector
import base.boudicca.model.structured.StructuredEvent
import base.boudicca.model.structured.toEvent
import base.boudicca.model.structured.toFlatEntry
import java.time.OffsetDateTime
import java.util.Optional
import java.util.*

data class Event(
val name: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package base.boudicca.model

enum class RecurrenceType {
ONCE, RARELY, REGULARLY
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package base.boudicca.model

enum class Registration {
TICKET, PRE_SALES_ONLY, REGISTRATION, FREE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package base.boudicca.model.structured

import base.boudicca.Property

abstract class AbstractStructuredBuilder<T>(protected val data: MutableMap<Key, String> = mutableMapOf()) {

@Throws(IllegalArgumentException::class)
fun <P> withProperty(property: Property<P>, value: P?): AbstractStructuredBuilder<T> {
return withProperty(property, null, value)
}

@Throws(IllegalArgumentException::class)
fun <P> withProperty(
property: Property<P>,
language: String?,
value: P?
): AbstractStructuredBuilder<T> {
if (value == null) {
return this
}
return withKeyValuePair(
property.getKey(language),
property.parseToString(value)
)
}

fun withKeyValuePair(
key: Key,
value: String?
): AbstractStructuredBuilder<T> {
if (value.isNullOrEmpty()) {
return this
}

data[key] = value
return this
}

abstract fun build(): T
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package base.boudicca.model.structured

import kotlin.math.min

data class Key(val name: String, val variants: List<Variant>) : Comparable<Key> {
fun toKeyString(): String {
if (variants.isEmpty()) {
Expand All @@ -9,7 +11,16 @@ data class Key(val name: String, val variants: List<Variant>) : Comparable<Key>
}

companion object {
val COMPARATOR = compareBy<Key> { it.toKeyString() } //TODO want better compare here?
val COMPARATOR = compareBy<Key> { it.name }
.thenComparing { o1, o2 ->
for (i in 0..<min(o1.variants.size, o2.variants.size)) {
val result = o1.variants[i].compareTo(o2.variants[i])
if (result != 0) {
return@thenComparing result
}
}
return@thenComparing o1.variants.size.compareTo(o2.variants.size)
}

fun parse(keyFilter: String): Key {
val keyVariantPair = KeyUtils.parseKey(keyFilter)
Expand Down
Loading

0 comments on commit c404378

Please sign in to comment.