Commit a14ae1aa authored by Jonas Waeber's avatar Jonas Waeber

Make fields serializable

parent 96e697b1
Pipeline #15800 passed with stage
in 3 minutes and 9 seconds
package mapping
import ch.memobase.mapping.KEYS
import ch.memobase.mapping.exceptions.InvalidMappingException
import ch.memobase.mapping.fields.ConfigField
import ch.memobase.mapping.fields.ConstantField
import ch.memobase.mapping.fields.DirectMapField
import ch.memobase.mapping.fields.FieldParsers
import ch.memobase.mapping.fields.LanguageField
import ch.memobase.mapping.fields.ListField
import ch.memobase.mapping.fields.PrefixField
import ch.memobase.mapping.mappers.AgentFieldMapper
import ch.memobase.mapping.mappers.CarrierTypeMapper
import ch.memobase.mapping.mappers.ConstantFieldMapper
import ch.memobase.mapping.mappers.DirectFieldMapper
import ch.memobase.mapping.mappers.ExpandedRuleFieldMapper
import ch.memobase.mapping.mappers.LanguageFieldMapper
import ch.memobase.mapping.mappers.ListFieldMapper
import ch.memobase.mapping.mappers.PlaceFieldMapper
import ch.memobase.mapping.mappers.PrefixFieldMapper
import ch.memobase.mapping.mappers.RicoConceptMapper
import ch.memobase.mapping.mappers.RuleFieldMapper
import ch.memobase.mapping.mappers.SkosConceptFieldMapper
import ch.memobase.mapping.mappers.TypeFieldMapper
import kotlin.reflect.full.createInstance
import mapping.mappers.AbstractFieldMapper
import org.memobase.rdf.SKOS
@Suppress("UNCHECKED_CAST")
object MapperParsers {
fun buildCarrierTypeMapper(value: Map.Entry<String, Any>): AbstractFieldMapper {
return CarrierTypeMapper(FieldParsers.parseAnnotationField(value))
}
fun buildAnnotationMappers(value: Any?): AbstractFieldMapper {
return when (val field = FieldParsers.parseAnnotationField(value as Map.Entry<String, Any>)) {
is DirectMapField -> DirectFieldMapper(field)
is ConstantField -> ConstantFieldMapper(field)
is ListField -> ListFieldMapper(field)
is LanguageField -> LanguageFieldMapper(field)
is PrefixField -> PrefixFieldMapper(field)
}
}
fun buildRicoConceptMappers(key: String, value: Any?): List<AbstractFieldMapper> {
return when (value) {
is Map<*, *> -> {
extractRicoConceptFields(key, value as Map<String, Any>)
}
is List<*> -> {
value.flatMap {
if (it is Map<*, *>) {
extractRicoConceptFields(key, it as Map<String, Any>)
} else {
throw InvalidMappingException("Expected a key-value map in section $key.")
}
}
}
else -> throw InvalidMappingException("Expected a key-value map in section $key.")
}
}
private fun extractRicoConceptFields(key: String, map: Map<String, Any>): List<AbstractFieldMapper> {
return map.entries.map { entry ->
KEYS.ricoConceptCategoryToTypesMap[key].let { typeList ->
if (typeList != null)
FieldParsers.parseFieldWithKeyValidation(key, entry, typeList)
else
throw InvalidMappingException("Could not find a types configuration for field $key.")
}
}.map { field ->
RicoConceptMapper(key, field)
}
}
fun buildAgentMapper(key: String, value: Any?): List<AbstractFieldMapper> {
return when (value) {
is Map<*, *> -> {
extractAgentTypeMapper(key, value as Map<String, Any>)
}
is List<*> -> {
value.flatMap {
extractAgentTypeMapper(key, it as Map<String, Any>)
}
}
else -> throw InvalidMappingException("The section ${KEYS.creators} expects a map or list.")
}
}
fun buildRuleMappers(value: Any?): List<AbstractFieldMapper> {
when (value) {
is Map<*, *> -> {
return value.entries.map {
when (it.key) {
KEYS.usage ->
ExpandedRuleFieldMapper(
it.key as String,
FieldParsers.parseSubFieldProperties(
it.key as String,
(it.value as Map<String, Any>).entries,
KEYS.usageSubPropertiesList
)
)
KEYS.access ->
return@map RuleFieldMapper(FieldParsers.parseAnnotationField(it as Map.Entry<String, Any>))
KEYS.holder ->
return@map RuleFieldMapper(FieldParsers.parseAnnotationField(it as Map.Entry<String, Any>))
else -> throw InvalidMappingException("Rights section has a fixed set of valid types: ${KEYS.rights}.")
}
}
}
else -> throw InvalidMappingException("Expected key value map under rights label.")
}
}
fun buildSkosConceptMappers(key: String, value: Any?): List<AbstractFieldMapper> {
return when (value) {
is Map<*, *> -> {
listOf(
extractEntityFields<SkosConceptFieldMapper>(
key,
value as Map<String, Any>,
SKOS.acceptedPropertiesList
)
)
}
is List<*> -> {
value.map {
try {
extractEntityFields<SkosConceptFieldMapper>(
key,
it as Map<String, Any>,
SKOS.acceptedPropertiesList
)
} catch (ex: ClassCastException) {
throw InvalidMappingException("Expected a map inside of the list entry in the $key section.")
}
}
}
else -> throw InvalidMappingException("Expected a mapping or a list inside of the $key section!")
}
}
fun extractAgentTypeMapper(key: String, value: Map<String, Any>): List<AbstractFieldMapper> {
return value.entries.map {
extractEntityFields<AgentFieldMapper>(it.key, it.value as Map<String, Any>, KEYS.allowedAgentProperties)
}.map {
it as AgentFieldMapper
it.sourceKey = key
it
}
}
fun buildPlaceMapper(key: String, value: Any?): List<AbstractFieldMapper> {
return when (value) {
is Map<*, *> -> {
listOf(
extractEntityFields<PlaceFieldMapper>(
key, value as Map<String, Any>, KEYS.allowedPlaceProperties
)
)
}
is List<*> -> {
return value.map {
try {
extractEntityFields<PlaceFieldMapper>(
key, it as Map<String, Any>, KEYS.allowedPlaceProperties
)
} catch (ex: ClassCastException) {
throw InvalidMappingException("Expected a map inside of the list entry in the $key section.")
}
}
}
else -> throw InvalidMappingException("Expected a mapping or a list inside of the $key section!")
}
}
inline fun <reified T : TypeFieldMapper> extractEntityFields(
key: String,
value: Map<String, Any>,
validationList: List<String>
): TypeFieldMapper {
val configFields = mutableListOf<ConfigField>()
for (entry in value.entries) {
configFields.add(
FieldParsers.parseFieldWithKeyValidation(
key,
entry,
validationList
)
)
}
val instance = T::class.createInstance() as TypeFieldMapper
instance.setFields(key, configFields)
return instance
}
}
......@@ -19,35 +19,15 @@
package ch.memobase.mapping
import ch.memobase.mapping.exceptions.InvalidMappingException
import ch.memobase.mapping.fields.ConfigField
import ch.memobase.mapping.fields.ConstantField
import ch.memobase.mapping.fields.DirectMapField
import ch.memobase.mapping.fields.FieldParsers
import ch.memobase.mapping.fields.LanguageField
import ch.memobase.mapping.fields.ListField
import ch.memobase.mapping.fields.PrefixField
import ch.memobase.mapping.fields.SimpleAnnotationField
import ch.memobase.mapping.mappers.AgentFieldMapper
import ch.memobase.mapping.mappers.CarrierTypeMapper
import ch.memobase.mapping.mappers.ConstantFieldMapper
import ch.memobase.mapping.mappers.DateFieldMapper
import ch.memobase.mapping.mappers.DirectFieldMapper
import ch.memobase.mapping.mappers.ExpandedRuleFieldMapper
import ch.memobase.mapping.mappers.LanguageFieldMapper
import ch.memobase.mapping.mappers.ListFieldMapper
import ch.memobase.mapping.mappers.PlaceFieldMapper
import ch.memobase.mapping.mappers.PrefixFieldMapper
import ch.memobase.mapping.mappers.RicoConceptMapper
import ch.memobase.mapping.mappers.RuleFieldMapper
import ch.memobase.mapping.mappers.SkosConceptFieldMapper
import ch.memobase.mapping.mappers.TypeFieldMapper
import kotlin.collections.Map.Entry
import kotlin.reflect.full.createInstance
import kotlin.system.exitProcess
import mapping.MapperConfiguration
import mapping.MapperParsers
import mapping.mappers.AbstractFieldMapper
import org.apache.logging.log4j.LogManager
import org.memobase.rdf.SKOS
@Suppress("UNCHECKED_CAST")
class MappingConfigurationParser(data: ByteArray) {
......@@ -120,15 +100,15 @@ class MappingConfigurationParser(data: ByteArray) {
KEYS.name, KEYS.title, KEYS.descriptiveNote, KEYS.scopeAndContent, KEYS.sameAs,
KEYS.abstract, KEYS.source, KEYS.hasFindingAid, KEYS.relation,
KEYS.conditionsOfUse, KEYS.conditionsOfAccess ->
recordFieldMappers.add(buildAnnotationMappers(entry))
recordFieldMappers.add(MapperParsers.buildAnnotationMappers(entry))
KEYS.rights ->
recordFieldMappers.addAll(buildRuleMappers(entry.value))
recordFieldMappers.addAll(MapperParsers.buildRuleMappers(entry.value))
KEYS.titles, KEYS.identifiers, KEYS.languages ->
recordFieldMappers.addAll(buildRicoConceptMappers(key, entry.value))
recordFieldMappers.addAll(MapperParsers.buildRicoConceptMappers(key, entry.value))
KEYS.subject, KEYS.genre ->
recordFieldMappers.addAll(buildSkosConceptMappers(key, entry.value))
recordFieldMappers.addAll(MapperParsers.buildSkosConceptMappers(key, entry.value))
KEYS.placeOfCapture, KEYS.relatedPlaces ->
recordFieldMappers.addAll(buildPlaceMapper(key, entry.value))
recordFieldMappers.addAll(MapperParsers.buildPlaceMapper(key, entry.value))
KEYS.creationDate, KEYS.issuedDate, KEYS.temporal ->
when (val value = FieldParsers.parseAnnotationField(entry)) {
is DirectMapField -> recordFieldMappers.add(
......@@ -137,7 +117,7 @@ class MappingConfigurationParser(data: ByteArray) {
else -> throw InvalidMappingException("Dates only allow simple field assignment. Invalid mapping ${entry.key}.")
}
KEYS.producer, KEYS.relatedAgents, KEYS.creators, KEYS.contributors, KEYS.publishedBy ->
recordFieldMappers.addAll(buildAgentMapper(key, entry.value))
recordFieldMappers.addAll(MapperParsers.buildAgentMapper(key, entry.value))
else -> throw InvalidMappingException("Unknown key '$key' in record mapping.")
}
}
......@@ -149,13 +129,13 @@ class MappingConfigurationParser(data: ByteArray) {
KEYS.descriptiveNote, KEYS.medium, KEYS.physicalCharacteristics,
KEYS.colour, KEYS.duration,
KEYS.conditionsOfUse, KEYS.conditionsOfAccess ->
physicalObjectFieldMappers.add(buildAnnotationMappers(entry))
physicalObjectFieldMappers.add(MapperParsers.buildAnnotationMappers(entry))
KEYS.identifiers ->
physicalObjectFieldMappers.addAll(buildRicoConceptMappers(key, entry.value))
physicalObjectFieldMappers.addAll(MapperParsers.buildRicoConceptMappers(key, entry.value))
KEYS.rights ->
physicalObjectFieldMappers.addAll(buildRuleMappers(entry.value))
physicalObjectFieldMappers.addAll(MapperParsers.buildRuleMappers(entry.value))
KEYS.carrierType ->
physicalObjectFieldMappers.add(buildCarrierTypeMapper(entry))
physicalObjectFieldMappers.add(MapperParsers.buildCarrierTypeMapper(entry))
else -> throw InvalidMappingException("Unknown key '$key' in physical object mapping.")
}
}
......@@ -165,180 +145,13 @@ class MappingConfigurationParser(data: ByteArray) {
for (entry in source) {
when (val key = entry.key) {
KEYS.locator, KEYS.descriptiveNote, KEYS.duration, KEYS.conditionsOfUse, KEYS.conditionsOfAccess ->
digitalObjectFieldMappers.add(buildAnnotationMappers(entry))
digitalObjectFieldMappers.add(MapperParsers.buildAnnotationMappers(entry))
KEYS.identifiers ->
digitalObjectFieldMappers.addAll(buildRicoConceptMappers(key, entry.value))
digitalObjectFieldMappers.addAll(MapperParsers.buildRicoConceptMappers(key, entry.value))
KEYS.rights ->
digitalObjectFieldMappers.addAll(buildRuleMappers(entry.value))
digitalObjectFieldMappers.addAll(MapperParsers.buildRuleMappers(entry.value))
else -> throw InvalidMappingException("Unknown key '$key' in digital object mapping.")
}
}
}
private fun buildCarrierTypeMapper(value: Map.Entry<String, Any>): AbstractFieldMapper {
return CarrierTypeMapper(FieldParsers.parseAnnotationField(value))
}
private fun buildAnnotationMappers(value: Any?): AbstractFieldMapper {
return when (val field = FieldParsers.parseAnnotationField(value as Map.Entry<String, Any>)) {
is DirectMapField -> DirectFieldMapper(field)
is ConstantField -> ConstantFieldMapper(field)
is ListField -> ListFieldMapper(field)
is LanguageField -> LanguageFieldMapper(field)
is PrefixField -> PrefixFieldMapper(field)
}
}
private fun buildRicoConceptMappers(key: String, value: Any?): List<AbstractFieldMapper> {
return when (value) {
is Map<*, *> -> {
extractRicoConceptFields(key, value as Map<String, Any>)
}
is List<*> -> {
value.flatMap {
if (it is Map<*, *>) {
extractRicoConceptFields(key, it as Map<String, Any>)
} else {
throw InvalidMappingException("Expected a key-value map in section $key.")
}
}
}
else -> throw InvalidMappingException("Expected a key-value map in section $key.")
}
}
private fun extractRicoConceptFields(key: String, map: Map<String, Any>): List<AbstractFieldMapper> {
return map.entries.map { entry ->
KEYS.ricoConceptCategoryToTypesMap[key].let { typeList ->
if (typeList != null)
FieldParsers.parseFieldWithKeyValidation(key, entry, typeList)
else
throw InvalidMappingException("Could not find a types configuration for field $key.")
}
}.map { field ->
RicoConceptMapper(key, field)
}
}
private fun buildAgentMapper(key: String, value: Any?): List<AbstractFieldMapper> {
return when (value) {
is Map<*, *> -> {
extractAgentTypeMapper(key, value as Map<String, Any>)
}
is List<*> -> {
value.flatMap {
extractAgentTypeMapper(key, it as Map<String, Any>)
}
}
else -> throw InvalidMappingException("The section ${KEYS.creators} expects a map or list.")
}
}
private fun buildRuleMappers(value: Any?): List<AbstractFieldMapper> {
when (value) {
is Map<*, *> -> {
return value.entries.map {
when (it.key) {
KEYS.usage ->
ExpandedRuleFieldMapper(
it.key as String,
FieldParsers.parseSubFieldProperties(
it.key as String,
(it.value as Map<String, Any>).entries,
KEYS.usageSubPropertiesList
)
)
KEYS.access ->
return@map RuleFieldMapper(FieldParsers.parseAnnotationField(it as Entry<String, Any>))
KEYS.holder ->
return@map RuleFieldMapper(FieldParsers.parseAnnotationField(it as Map.Entry<String, Any>))
else -> throw InvalidMappingException("Rights section has a fixed set of valid types: ${KEYS.rights}.")
}
}
}
else -> throw InvalidMappingException("Expected key value map under rights label.")
}
}
private fun buildSkosConceptMappers(key: String, value: Any?): List<AbstractFieldMapper> {
return when (value) {
is Map<*, *> -> {
listOf(
extractEntityFields<SkosConceptFieldMapper>(
key,
value as Map<String, Any>,
SKOS.acceptedPropertiesList
)
)
}
is List<*> -> {
value.map {
try {
extractEntityFields<SkosConceptFieldMapper>(
key,
it as Map<String, Any>,
SKOS.acceptedPropertiesList
)
} catch (ex: ClassCastException) {
throw InvalidMappingException("Expected a map inside of the list entry in the $key section.")
}
}
}
else -> throw InvalidMappingException("Expected a mapping or a list inside of the $key section!")
}
}
private fun extractAgentTypeMapper(key: String, value: Map<String, Any>): List<AbstractFieldMapper> {
return value.entries.map {
extractEntityFields<AgentFieldMapper>(it.key, it.value as Map<String, Any>, KEYS.allowedAgentProperties)
}.map {
it as AgentFieldMapper
it.sourceKey = key
it
}
}
private fun buildPlaceMapper(key: String, value: Any?): List<AbstractFieldMapper> {
return when (value) {
is Map<*, *> -> {
listOf(
extractEntityFields<PlaceFieldMapper>(
key, value as Map<String, Any>, KEYS.allowedPlaceProperties
)
)
}
is List<*> -> {
return value.map {
try {
extractEntityFields<PlaceFieldMapper>(
key, it as Map<String, Any>, KEYS.allowedPlaceProperties
)
} catch (ex: ClassCastException) {
throw InvalidMappingException("Expected a map inside of the list entry in the $key section.")
}
}
}
else -> throw InvalidMappingException("Expected a mapping or a list inside of the $key section!")
}
}
private inline fun <reified T : TypeFieldMapper> extractEntityFields(
key: String,
value: Map<String, Any>,
validationList: List<String>
): TypeFieldMapper {
val configFields = mutableListOf<ConfigField>()
for (entry in value.entries) {
configFields.add(
FieldParsers.parseFieldWithKeyValidation(
key,
entry,
validationList
)
)
}
val instance = T::class.createInstance() as TypeFieldMapper
instance.setFields(key, configFields)
return instance
}
}
......@@ -18,13 +18,17 @@
package ch.memobase.mapping.fields
import com.beust.klaxon.TypeFor
import mapping.fields.FieldAdapter
import mapping.fields.FieldTypes
import org.apache.jena.rdf.model.Literal
import org.apache.jena.rdf.model.ResourceFactory
sealed class ConfigField {
sealed class AnnotationField : ConfigField() {
sealed class SimpleAnnotationField : AnnotationField() {
data class ConstantField(val key: String, val constant: String) : SimpleAnnotationField() {
@TypeFor("fieldType", FieldAdapter::class)
sealed class ConfigField(val fieldType: String) {
sealed class AnnotationField(fieldType: String) : ConfigField(fieldType) {
sealed class SimpleAnnotationField(fieldType: String) : AnnotationField(fieldType) {
data class ConstantField(val key: String, val constant: String) : SimpleAnnotationField(FieldTypes.constant) {
fun toLiteral(): Literal {
return literal(constant)
}
......@@ -34,12 +38,12 @@ sealed class ConfigField {
}
}
sealed class MappedAnnotationField(val key: String, val field: String) : SimpleAnnotationField() {
sealed class MappedAnnotationField(fieldType: String, val key: String, val field: String) : SimpleAnnotationField(fieldType) {
data class PrefixField(
private val prefixKey: String,
val prefixKey: String,
val prefix: String,
private val prefixField: String
) : MappedAnnotationField(prefixKey, prefixField) {
val prefixField: String
) : MappedAnnotationField(FieldTypes.prefix, prefixKey, prefixField) {
override fun toLiteral(value: String): Literal {
return literal(prefix + value.trim())
}
......@@ -49,8 +53,8 @@ sealed class ConfigField {
}
}
data class DirectMapField(private val directKey: String, private val directField: String) :
MappedAnnotationField(directKey, directField) {
data class DirectMapField(val directKey: String, val directField: String) :
MappedAnnotationField(FieldTypes.direct, directKey, directField) {
override fun toLiteral(value: String): Literal {
return literal(value)
}
......@@ -65,8 +69,8 @@ sealed class ConfigField {
}
}
sealed class ComplexAnnotationField : AnnotationField() {
data class ListField(val key: String, val fields: List<SimpleAnnotationField>) : ComplexAnnotationField() {
sealed class ComplexAnnotationField(fieldType: String) : AnnotationField(fieldType) {
data class ListField(val key: String, val fields: List<SimpleAnnotationField>) : ComplexAnnotationField(FieldTypes.list) {
fun toLiterals(source: Map<String, Any>): List<Literal> {
return fields.mapNotNull {
when (it) {
......@@ -85,7 +89,7 @@ sealed class ConfigField {
}
data class LanguageField(val key: String, val fields: List<Pair<String, List<SimpleAnnotationField>>>) :
ComplexAnnotationField() {
ComplexAnnotationField(FieldTypes.language) {
fun toLangLiterals(source: Map<String, Any>): List<Literal> {
return fields.flatMap { pairs ->
pairs.second.mapNotNull {
......
package mapping.fields
import ch.memobase.mapping.fields.ConfigField
import ch.memobase.mapping.fields.ConstantField
import ch.memobase.mapping.fields.DirectMapField
import ch.memobase.mapping.fields.LanguageField
import ch.memobase.mapping.fields.ListField
import ch.memobase.mapping.fields.PrefixField
import com.beust.klaxon.TypeAdapter
import kotlin.reflect.KClass
class FieldAdapter : TypeAdapter<ConfigField> {
override fun classFor(type: Any): KClass<out ConfigField> {
return when (type) {
FieldTypes.constant -> ConstantField::class
FieldTypes.direct -> DirectMapField::class
FieldTypes.prefix -> PrefixField::class
FieldTypes.list -> ListField::class
FieldTypes.language -> LanguageField::class
else -> throw Exception("Could not parse config field instance with type $type.")
}
}
}
package mapping.fields
object FieldTypes {
const val constant = "constant"
const val direct = "direct"
const val prefix = "prefix"
const val language = "language"
const val list = "list"
}
......@@ -2,7 +2,9 @@ package ch.memobase.test
import ch.memobase.mapping.fields.ConstantField
import ch.memobase.mapping.mappers.ConstantFieldMapper
import ch.memobase.mapping.mappers.SkosConceptFieldMapper
import com.beust.klaxon.Klaxon
import mapping.MapperParsers
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
......@@ -17,7 +19,21 @@ class TestFieldMapperSerde {
val json = klaxon.toJsonString(ConstantFieldMapper(ConstantField("key", "value")))