Commit 63531f7c authored by Jonas Waeber's avatar Jonas Waeber
Browse files

finished prototype

parent a0834270
/*
* mapper-service
* Copyright (C) 2020 Memoriav
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.memobase
import org.apache.jena.rdf.model.Model
import org.apache.logging.log4j.LogManager
import org.memobase.settings.CustomEnvConfig
import org.memobase.settings.MissingSettingException
import org.snakeyaml.engine.v2.api.Load
import org.snakeyaml.engine.v2.api.LoadSettings
import org.snakeyaml.engine.v2.exceptions.MissingEnvironmentVariableException
import java.nio.charset.Charset
import java.nio.file.Files
import java.nio.file.Path
import java.util.Optional
import kotlin.system.exitProcess
class MappingConfig(path: Path) {
val fieldMappers = mutableListOf<FieldMapper>()
private val log = LogManager.getLogger("MappingConfigParser")
init {
val result = Files.list(path).map { file -> file.toFile().readText(Charset.defaultCharset()) }
.reduce("") { s, s2 -> s + "\n" + s2 }.trim()
if (result.isNotEmpty()) {
try {
val rawYaml = loadYaml(result)
val iterable = rawYaml as Iterable<*>
for (item in iterable) {
when (item) {
is Map<*, *> ->
for (key in item.keys) {
when (key as String) {
"record" -> parseRecordConfig(item["record"] as Map<String, Any>)
"physical" -> parsePhysicalInstantiationConfig(item["physical"] as Map<String, Any>)
"digital" -> parseDigitalInstantiationConfig(item["digital"] as Map<String, Any>)
else -> log.error("Unknown key for top level entity: $key")
}
}
else -> log.error("null")
}
}
} catch (ex: ClassCastException) {
log.error(ex.message)
exitProcess(1)
}
} else {
exitProcess(1)
}
}
private fun loadYaml(data: String): Any {
val settings =
LoadSettings.builder().setAllowDuplicateKeys(true).setEnvConfig(Optional.of(CustomEnvConfig())).build()
val load = Load(settings)
return try {
load.loadAllFromString(data)
} catch (ex: MissingEnvironmentVariableException) {
throw MissingSettingException("env", ex.localizedMessage)
}
}
private fun parseRecordConfig(source: Map<String, Any>) {
for (entry in source) {
when (entry.key) {
// literal properties
"name", "descriptiveNote", "scopeAndContent",
"abstract", "source", "hasSponsoringAgentOfResource" -> {
when (val value = parseField(entry)) {
is SimpleField -> fieldMappers.add(StringFieldMapper(value))
is ConstField -> fieldMappers.add(ConstantFieldMapper(value))
is ListField -> fieldMappers.add(ListFieldMapper(value))
is LanguageField -> fieldMappers.add(LanguageTagFieldMapper(value))
}
}
"rights" ->
when (val value = entry.value) {
is Map<*, *> -> {
for (rightsEntry in value.entries) {
val configField =
parseFieldWithKeyValidationForRules(rightsEntry as Map.Entry<String, Any>)
fieldMappers.add(RuleFieldMapper(configField))
}
}
else -> throw InvalidMappingException("Expected key value map under rights label.")
}
"title" ->
when (val value = entry.value) {
is Map<*, *> -> {
for (titleEntry in value.entries) {
val configField =
parseFieldWithKeyValidationForTitles(titleEntry as Map.Entry<String, Any>)
if (configField is LanguageField) {
fieldMappers.add(TitleFieldMapper(configField))
}
else
throw InvalidMappingException("Title mapping requires language tags!")
}
}
else -> throw InvalidMappingException("Expected key value map under title label.")
}
}
}
}
private fun parseField(entry: Map.Entry<String, Any>): ConfigField {
when (val value = entry.value) {
is String -> return SimpleField(entry.key, value)
is Map<*, *> -> {
return if (value.containsKey("const")) {
ConstField(entry.key, value["const"] as String)
} else {
val mutableList = mutableListOf<Pair<String, List<String>>>()
for (k in value.keys) {
if (k is String && listOf("de", "it", "fr").contains(k)) {
when (val source = value[k]) {
is String -> mutableList.add(Pair(k, listOf(source)))
is List<*> -> mutableList.add(Pair(k, source as List<String>))
else -> throw InvalidMappingException("Could not parse content of language tag definition. Must be list or string.")
}
}
}
if (mutableList.isNotEmpty()) {
LanguageField(entry.key, mutableList)
} else {
throw InvalidMappingException("Unknown key values in field mapping: $value. Use 'const' or 'de', 'fr', 'it' language tags!")
}
}
}
is List<*> -> return ListField(entry.key, value as List<String>)
else -> throw InvalidMappingException("Unknown structure for field mapping: $entry")
}
}
private fun parsePhysicalInstantiationConfig(source: Map<String, Any>) {
}
private fun parseDigitalInstantiationConfig(source: Map<String, Any>) {
}
private fun parseFieldWithKeyValidationForRules(entry: Map.Entry<String, Any>): ConfigField {
if (listOf("rights", "access", "holder", "usage").contains(entry.key)) {
return parseField(entry)
} else {
throw InvalidMappingException("Rights section does not allow type: ${entry.key}. Use 'rights', 'access', 'holder' or 'usage'.")
}
}
private fun parseFieldWithKeyValidationForTitles(entry: Map.Entry<String, Any>): ConfigField {
if (listOf("main", "serial", "broadcast").contains(entry.key)) {
return parseField(entry)
} else {
throw InvalidMappingException("Title section does not allow type: ${entry.key}. Use 'main', 'serial', 'broadcast'.")
}
}
}
interface FieldMapper {
fun apply(source: Map<String, String>, subject: RecordResource)
}
class StringFieldMapper(private val simpleField: SimpleField) : FieldMapper {
override fun apply(source: Map<String, String>, subject: RecordResource) {
source[simpleField.field].let {
if (it != null) {
subject.addStringLiteral(simpleField.key, it)
}
}
}
}
class ConstantFieldMapper(private val constantField: ConstField) : FieldMapper {
override fun apply(source: Map<String, String>, subject: RecordResource) {
subject.addStringLiteral(constantField.key, constantField.constant)
}
}
class ListFieldMapper(private val listField: ListField): FieldMapper {
override fun apply(source: Map<String, String>, subject: RecordResource) {
for (sourceField in listField.fields) {
source[sourceField].let {
if (it != null) {
subject.addStringLiteral(listField.key, it)
}
}
}
}
}
class LanguageTagFieldMapper(
private val languageField: LanguageField
) : FieldMapper {
override fun apply(source: Map<String, String>, subject: RecordResource) {
for (pair in languageField.fields) {
for (field in pair.second) {
if (source.containsKey(field)) {
subject.addLangLiteral(languageField.key, source[field] as String, pair.first)
}
}
}
}
}
class RuleFieldMapper(private val configField: ConfigField) : FieldMapper {
override fun apply(source: Map<String, String>, subject: RecordResource) {
//TODO: Implement rules. Need to know more first!
}
}
class TitleFieldMapper(private val languageField: LanguageField): FieldMapper {
override fun apply(source: Map<String, String>, subject: RecordResource) {
for (pair in languageField.fields) {
for (field in pair.second) {
if (source.containsKey(field)) {
subject.addTitle(languageField.key, source[field] as String, pair.first)
}
}
}
}
}
open class ConfigField
data class SimpleField(val key: String, val field: String) : ConfigField()
data class ConstField(val key: String, val constant: String) : ConfigField()
data class ListField(val key: String, val fields: List<String>) : ConfigField()
data class LanguageField(val key: String, val fields: List<Pair<String, List<String>>>) : ConfigField()
class InvalidMappingException(message: String) : Exception(message)
/*
* mapper-service
* Copyright (C) 2020 Memoriav
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.memobase
import org.apache.jena.rdf.model.Literal
import org.apache.jena.rdf.model.ModelFactory
import org.apache.jena.rdf.model.Property
import org.apache.jena.rdf.model.Resource
import org.apache.jena.rdf.model.ResourceFactory
class RecordResource(id: String) {
val model = ModelFactory.createDefaultModel()
init {
model.setNsPrefix("rico", NS.rico)
model.setNsPrefix("rdf", NS.rdf)
model.setNsPrefix("dct", NS.dct)
model.setNsPrefix("ebucore", NS.ebucore)
}
val record = model.createResource(NS.memr + id)
// Classes
private val ricoRecord = model.createResource(NS.rico + "Record")
private val ricoTitleClass = model.createResource(NS.rico + "Title")
private val ricoSingleDate = model.createResource(NS.rico + "SingleDate")
private val ricoDateRange = model.createResource(NS.rico + "DateRange")
private val ricoDateSet = model.createResource(NS.rico + "DateSet")
// Properties
// Datatype Properties
private val ricoScopeAndContent: Property = ResourceFactory.createProperty(NS.rico, "scopeAndContent")
private val dctAbstract: Property = ResourceFactory.createProperty(NS.dct, "abstract")
private val dctCreated: Property = ResourceFactory.createProperty(NS.dct, "created")
private val rdfType: Property = ResourceFactory.createProperty(NS.rdf, "type")
private val ricoTitle: Property = ResourceFactory.createProperty(NS.rico, "title")
private val ricoDescriptiveNote: Property = ResourceFactory.createProperty(NS.rico, "descriptiveNote")
private val ricoName: Property = ResourceFactory.createProperty(NS.rico, "name")
private val ricoType: Property = ResourceFactory.createProperty(NS.rico, "type")
private val ricoExpressedDate: Property = ResourceFactory.createProperty(NS.rico, "expressedDate")
private val ricoNormalizedDate: Property = ResourceFactory.createProperty(NS.rico, "normalizedDateValue")
// Object Properties
private val ricoProvidedBy: Property = ResourceFactory.createProperty(NS.rico, "providedBy")
private val ricoHasTitle: Property = ResourceFactory.createProperty(NS.rico, "hasTitle")
private val ricoIsPartOf: Property = ResourceFactory.createProperty(NS.rico, "isPartOf")
private val propertyMap = mapOf(
Pair("abstract", dctAbstract),
Pair("name", ricoName),
Pair("title", ricoTitle),
Pair("descriptiveNote", ricoDescriptiveNote),
Pair("scopeAndContent", ricoScopeAndContent)
)
fun rdfType() {
record.addProperty(rdfType, ricoRecord)
}
fun addStringLiteral(property: String, value: String) {
record.addLiteral(propertyMap[property], value)
}
fun addLangLiteral(property: String, value: String, tag: String) {
record.addLiteral(propertyMap[property], model.createLiteral(value, tag))
}
fun addTitle(type: String, text: String, language: String) {
val blank = model.createResource()
blank.addProperty(ricoTitle, langLiteral(text, language))
blank.addProperty(ricoType, type)
blank.addProperty(rdfType, ricoTitleClass)
record.addProperty(ricoHasTitle, blank)
}
fun isPartOf(recordSetId: String) {
record.addProperty(ricoIsPartOf, recordSetUri(recordSetId))
}
fun providedBy(institutionId: String) {
record.addProperty(ricoProvidedBy, institutionUri(institutionId))
}
private fun uri(ns: String, name: String): String = ns + name
private fun recordSetUri(id: String): Resource = model.createResource(uri(NS.memrs, id))
private fun institutionUri(id: String): Resource = model.createResource(uri(NS.memint, id))
private fun langLiteral(text: String, language: String): Literal = model.createLiteral(text, language)
}
\ No newline at end of file
/*
* mapper-service
* Copyright (C) 2020 Memoriav
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.memobase.builder
import org.apache.jena.rdf.model.Resource
import org.memobase.builder.Instantiation
import org.memobase.rdf.NS
import org.memobase.rdf.RICO
class DigitalObject(id: String, institutionId: String, count: Int) : Instantiation(institutionId) {
override val resource: Resource = model.createResource(NS.mempo + institutionId + "-" + id + "-" + count)
init {
addRdfType(RICO.Instantiation)
resource.addProperty(RICO.type, "digitalObject")
resource.addProperty(RICO.heldBy, institutionUri)
resource.addProperty(RICO.hasProvenance, institutionUri)
addIdentifier("main", resource.uri, model.createResource(uri(NS.memint, "Memoriav")))
}
}
\ No newline at end of file
/*
* mapper-service
* Copyright (C) 2020 Memoriav
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.memobase.builder
import org.apache.jena.rdf.model.Literal
import org.apache.jena.rdf.model.Resource
interface IResource {
fun addStringLiteral(property: String, value: String)
fun addLangLiteral(property: String, value: String, tag: String)
fun addLanguage(type: String, text: String)
fun addTitle(type: String, text: String, language: String)
fun addIdentifier(type: String, identifier: String)
fun addSkosConcept(type: String, properties: List<Pair<String, Literal>>)
fun addPlace(type: String, properties: List<Pair<String, Literal>>)
fun addDate(property: String, value: String)
fun addCreationRelation(type: String, agentType: String, properties: List<Pair<String, Literal>>)
fun langLiteral(text: String, language: String): Literal
fun literal(text: String): Literal
}
\ No newline at end of file
/*
* mapper-service
* Copyright (C) 2020 Memoriav
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.memobase.builder
import org.memobase.rdf.RICO
abstract class Instantiation(institutionId: String) : RecordResource(institutionId) {
fun addRecord(record: Record) {
resource.addProperty(RICO.instantiates, record.resource)
}
fun addDerivedInstantiation(instantiation: Instantiation) {
resource.addProperty(RICO.hasDerivedInstantiation, instantiation.resource)
}
}
/*
* mapper-service
* Copyright (C) 2020 Memoriav
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.memobase.builder
import org.apache.jena.rdf.model.Resource
import org.memobase.rdf.NS
import org.memobase.rdf.RICO
class PhysicalObject(id: String, institutionId: String, count: Int) : Instantiation(institutionId) {
override val resource: Resource = model.createResource(NS.mempo + institutionId + "-" + id + "-" + count)
init {
addRdfType(RICO.Instantiation)
resource.addProperty(RICO.type, "physicalObject")
resource.addProperty(RICO.heldBy, institutionUri)
resource.addProperty(RICO.hasProvenance, institutionUri)
addIdentifier("main", resource.uri, model.createResource(uri(NS.memint, "Memoriav")))
}
}
\ No newline at end of file
/*
* mapper-service
* Copyright (C) 2020 Memoriav
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.memobase.builder
import org.apache.jena.rdf.model.Resource
import org.memobase.rdf.NS
import org.memobase.rdf.RICO
class Record(id: String, recordSetId: String, institutionId: String) :
RecordResource(institutionId) {
override val resource: Resource = model.createResource(NS.memr + institutionId + "-" + id)
init {
addRdfType(RICO.Record)
resource.addProperty(RICO.isPartOf, recordSetUri(recordSetId))
resource.addProperty(RICO.heldBy, institutionUri)
resource.addProperty(RICO.hasProvenance, institutionUri)
addIdentifier("main", resource.uri, model.createResource(uri(NS.memint, "Memoriav")))
}
fun addInstantiation(instantiation: Instantiation) {
resource.addProperty(RICO.hasInstantiation, instantiation.resource)
}
private fun recordSetUri(id: String): Resource = model.createResource(uri(NS.memrs, id))
}
\ No newline at end of file
/*
* mapper-service
* Copyright (C) 2020 Memoriav
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/