Commit a14ae1aa authored by Jonas Waeber's avatar Jonas Waeber
Browse files

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 @@ ...@@ -19,35 +19,15 @@
package ch.memobase.mapping package ch.memobase.mapping
import ch.memobase.mapping.exceptions.InvalidMappingException 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.DirectMapField
import ch.memobase.mapping.fields.FieldParsers 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.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.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 kotlin.system.exitProcess
import mapping.MapperConfiguration import mapping.MapperConfiguration
import mapping.MapperParsers
import mapping.mappers.AbstractFieldMapper import mapping.mappers.AbstractFieldMapper
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.memobase.rdf.SKOS
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class MappingConfigurationParser(data: ByteArray) { class MappingConfigurationParser(data: ByteArray) {
...@@ -120,15 +100,15 @@ class MappingConfigurationParser(data: ByteArray) { ...@@ -120,15 +100,15 @@ class MappingConfigurationParser(data: ByteArray) {
KEYS.name, KEYS.title, KEYS.descriptiveNote, KEYS.scopeAndContent, KEYS.sameAs, KEYS.name, KEYS.title, KEYS.descriptiveNote, KEYS.scopeAndContent, KEYS.sameAs,
KEYS.abstract, KEYS.source, KEYS.hasFindingAid, KEYS.relation, KEYS.abstract, KEYS.source, KEYS.hasFindingAid, KEYS.relation,
KEYS.conditionsOfUse, KEYS.conditionsOfAccess -> KEYS.conditionsOfUse, KEYS.conditionsOfAccess ->
recordFieldMappers.add(buildAnnotationMappers(entry)) recordFieldMappers.add(MapperParsers.buildAnnotationMappers(entry))
KEYS.rights -> KEYS.rights ->
recordFieldMappers.addAll(buildRuleMappers(entry.value)) recordFieldMappers.addAll(MapperParsers.buildRuleMappers(entry.value))
KEYS.titles, KEYS.identifiers, KEYS.languages -> KEYS.titles, KEYS.identifiers, KEYS.languages ->
recordFieldMappers.addAll(buildRicoConceptMappers(key, entry.value)) recordFieldMappers.addAll(MapperParsers.buildRicoConceptMappers(key, entry.value))
KEYS.subject, KEYS.genre -> KEYS.subject, KEYS.genre ->
recordFieldMappers.addAll(buildSkosConceptMappers(key, entry.value)) recordFieldMappers.addAll(MapperParsers.buildSkosConceptMappers(key, entry.value))
KEYS.placeOfCapture, KEYS.relatedPlaces -> KEYS.placeOfCapture, KEYS.relatedPlaces ->
recordFieldMappers.addAll(buildPlaceMapper(key, entry.value)) recordFieldMappers.addAll(MapperParsers.buildPlaceMapper(key, entry.value))
KEYS.creationDate, KEYS.issuedDate, KEYS.temporal -> KEYS.creationDate, KEYS.issuedDate, KEYS.temporal ->
when (val value = FieldParsers.parseAnnotationField(entry)) { when (val value = FieldParsers.parseAnnotationField(entry)) {
is DirectMapField -> recordFieldMappers.add( is DirectMapField -> recordFieldMappers.add(
...@@ -137,7 +117,7 @@ class MappingConfigurationParser(data: ByteArray) { ...@@ -137,7 +117,7 @@ class MappingConfigurationParser(data: ByteArray) {
else -> throw InvalidMappingException("Dates only allow simple field assignment. Invalid mapping ${entry.key}.") else -> throw InvalidMappingException("Dates only allow simple field assignment. Invalid mapping ${entry.key}.")
} }
KEYS.producer, KEYS.relatedAgents, KEYS.creators, KEYS.contributors, KEYS.publishedBy -> 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.") else -> throw InvalidMappingException("Unknown key '$key' in record mapping.")
} }
} }
...@@ -149,13 +129,13 @@ class MappingConfigurationParser(data: ByteArray) { ...@@ -149,13 +129,13 @@ class MappingConfigurationParser(data: ByteArray) {
KEYS.descriptiveNote, KEYS.medium, KEYS.physicalCharacteristics, KEYS.descriptiveNote, KEYS.medium, KEYS.physicalCharacteristics,
KEYS.colour, KEYS.duration, KEYS.colour, KEYS.duration,
KEYS.conditionsOfUse, KEYS.conditionsOfAccess -> KEYS.conditionsOfUse, KEYS.conditionsOfAccess ->
physicalObjectFieldMappers.add(buildAnnotationMappers(entry)) physicalObjectFieldMappers.add(MapperParsers.buildAnnotationMappers(entry))
KEYS.identifiers -> KEYS.identifiers ->
physicalObjectFieldMappers.addAll(buildRicoConceptMappers(key, entry.value)) physicalObjectFieldMappers.addAll(MapperParsers.buildRicoConceptMappers(key, entry.value))
KEYS.rights -> KEYS.rights ->
physicalObjectFieldMappers.addAll(buildRuleMappers(entry.value)) physicalObjectFieldMappers.addAll(MapperParsers.buildRuleMappers(entry.value))
KEYS.carrierType -> KEYS.carrierType ->
physicalObjectFieldMappers.add(buildCarrierTypeMapper(entry)) physicalObjectFieldMappers.add(MapperParsers.buildCarrierTypeMapper(entry))
else -> throw InvalidMappingException("Unknown key '$key' in physical object mapping.") else -> throw InvalidMappingException("Unknown key '$key' in physical object mapping.")
} }
} }
...@@ -165,180 +145,13 @@ class MappingConfigurationParser(data: ByteArray) { ...@@ -165,180 +145,13 @@ class MappingConfigurationParser(data: ByteArray) {
for (entry in source) { for (entry in source) {
when (val key = entry.key) { when (val key = entry.key) {
KEYS.locator, KEYS.descriptiveNote, KEYS.duration, KEYS.conditionsOfUse, KEYS.conditionsOfAccess -> KEYS.locator, KEYS.descriptiveNote, KEYS.duration, KEYS.conditionsOfUse, KEYS.conditionsOfAccess ->
digitalObjectFieldMappers.add(buildAnnotationMappers(entry)) digitalObjectFieldMappers.add(MapperParsers.buildAnnotationMappers(entry))
KEYS.identifiers -> KEYS.identifiers ->
digitalObjectFieldMappers.addAll(buildRicoConceptMappers(key, entry.value)) digitalObjectFieldMappers.addAll(MapperParsers.buildRicoConceptMappers(key, entry.value))
KEYS.rights -> KEYS.rights ->
digitalObjectFieldMappers.addAll(buildRuleMappers(entry.value)) digitalObjectFieldMappers.addAll(MapperParsers.buildRuleMappers(entry.value))
else -> throw InvalidMappingException("Unknown key '$key' in digital object mapping.") 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 @@ ...@@ -18,13 +18,17 @@
package ch.memobase.mapping.fields 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.Literal
import org.apache.jena.rdf.model.ResourceFactory import org.apache.jena.rdf.model.ResourceFactory
sealed class ConfigField { @TypeFor("fieldType", FieldAdapter::class)
sealed class AnnotationField : ConfigField() { sealed class ConfigField(val fieldType: String) {
sealed class SimpleAnnotationField : AnnotationField() { sealed class AnnotationField(fieldType: String) : ConfigField(fieldType) {
data class ConstantField(val key: String, val constant: String) : SimpleAnnotationField() { sealed class SimpleAnnotationField(fieldType: String) : AnnotationField(fieldType) {
data class ConstantField(val key: String, val constant: String) : SimpleAnnotationField(FieldTypes.constant) {
fun toLiteral(): Literal { fun toLiteral(): Literal {
return literal(constant) return literal(constant)
} }
...@@ -34,12 +38,12 @@ sealed class ConfigField { ...@@ -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( data class PrefixField(
private val prefixKey: String, val prefixKey: String,
val prefix: String, val prefix: String,
private val prefixField: String val prefixField: String
) : MappedAnnotationField(prefixKey, prefixField) { ) : MappedAnnotationField(FieldTypes.prefix, prefixKey, prefixField) {
override fun toLiteral(value: String): Literal { override fun toLiteral(value: String): Literal {
return literal(prefix + value.trim()) return literal(prefix + value.trim())
} }
...@@ -49,8 +53,8 @@ sealed class ConfigField { ...@@ -49,8 +53,8 @@ sealed class ConfigField {
} }
} }
data class DirectMapField(private val directKey: String, private val directField: String) : data class DirectMapField(val directKey: String, val directField: String) :
MappedAnnotationField(directKey, directField) { MappedAnnotationField(FieldTypes.direct, directKey, directField) {
override fun toLiteral(value: String): Literal { override fun toLiteral(value: String): Literal {
return literal(value) return literal(value)
} }
...@@ -65,8 +69,8 @@ sealed class ConfigField { ...@@ -65,8 +69,8 @@ sealed class ConfigField {
} }
} }
sealed class ComplexAnnotationField : AnnotationField() { sealed class ComplexAnnotationField(fieldType: String) : AnnotationField(fieldType) {
data class ListField(val key: String, val fields: List<SimpleAnnotationField>) : ComplexAnnotationField() { data class ListField(val key: String, val fields: List<SimpleAnnotationField>) : ComplexAnnotationField(FieldTypes.list) {
fun toLiterals(source: Map<String, Any>): List<Literal> { fun toLiterals(source: Map<String, Any>): List<Literal> {
return fields.mapNotNull {