Skip to content

Commit ffce5f0

Browse files
9.1 Internal Prefixes (#20)
* Add vocab reference object * Add classes for composing RDF model * Add RDF model test utils and tests * Add binary array to model conversion API impl * Add NetCDF binary array factory method impl, tests * Add CLI impl, test, docs * Fix Java version in maven compiler plugin * Add prefix mapping concept on binary array * Add prefix mapping to model composition, tests * Add NetCDF impl of prefix mapping concept * Add tests, upgrade test format to NetCDF4 to support prefix group. * Add logback config on CLI to suppress debug logs * NetCDF4 considerations in tests * Add file container, refactor * Add file container to tests * Fix tests * Changed test data to CDL format * Remove NCML references * Fix Java version on demo * ADD name and subgroups to container concept * Enhance binary array converter to describe subgroups * Fix test consistency * Add prefix var support * Remove prefix group / var from graph * Replace BALD prefix mapping concept with Jena prefix mapping * Fix merge issue * Use common test method * Add prefix mapping validation
1 parent 1303a92 commit ffce5f0

File tree

21 files changed

+424
-25
lines changed

21 files changed

+424
-25
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<configuration>
2+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
3+
<encoder>
4+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
5+
</encoder>
6+
</appender>
7+
8+
<root level="info">
9+
<appender-ref ref="STDOUT" />
10+
</root>
11+
</configuration>

binary-array-ld-cli/src/test/kotlin/net/bald/BinaryArrayConvertCliTest.kt

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
package net.bald
22

33
import bald.model.ModelVerifier
4-
import bald.netcdf.CdlConverter.convertToNetCdf
4+
import bald.netcdf.CdlConverter.writeToNetCdf
55
import net.bald.vocab.BALD
66
import org.apache.jena.rdf.model.ModelFactory.createDefaultModel
77
import org.apache.jena.vocabulary.RDF
8+
import org.apache.jena.vocabulary.SKOS
9+
import org.junit.Assume
10+
import org.junit.jupiter.api.BeforeEach
811
import org.junit.jupiter.api.Test
912
import org.junit.jupiter.api.assertThrows
13+
import ucar.nc2.jni.netcdf.Nc4Iosp
1014
import kotlin.test.assertEquals
1115

1216
/**
1317
* Integration test for [BinaryArrayConvertCli].
18+
*
19+
* Test resources are stored in CDL format and converted to temporary NetCDF 4 files.
20+
* In order to write the NetCDF 4 files, the ncgen command line utility must be available.
1421
*/
1522
class BinaryArrayConvertCliTest {
1623
private fun run(vararg args: String) {
@@ -32,7 +39,7 @@ class BinaryArrayConvertCliTest {
3239

3340
@Test
3441
fun run_withoutUri_outputsToFileWithInputFileUri() {
35-
val inputFile = convertToNetCdf("/netcdf/identity.cdl")
42+
val inputFile = writeToNetCdf("/netcdf/identity.cdl")
3643
val inputFileUri = inputFile.toPath().toUri().toString()
3744
val outputFile = createTempFile()
3845
run(inputFile.absolutePath, outputFile.absolutePath)
@@ -56,12 +63,33 @@ class BinaryArrayConvertCliTest {
5663

5764
@Test
5865
fun run_withUri_withOutputFile_outputsToFile() {
59-
val inputFile = convertToNetCdf("/netcdf/identity.cdl")
66+
val inputFile = writeToNetCdf("/netcdf/identity.cdl")
6067
val outputFile = createTempFile()
6168
run("--uri", "http://test.binary-array-ld.net/example", inputFile.absolutePath, outputFile.absolutePath)
6269

6370
val model = createDefaultModel().read(outputFile.toURI().toString(), "ttl")
6471
ModelVerifier(model).apply {
72+
resource("http://test.binary-array-ld.net/example/") {
73+
statement(RDF.type, BALD.Container)
74+
statement(BALD.contains, model.createResource("http://test.binary-array-ld.net/example/var0")) {
75+
statement(RDF.type, BALD.Resource)
76+
}
77+
statement(BALD.contains, model.createResource("http://test.binary-array-ld.net/example/var1")) {
78+
statement(RDF.type, BALD.Resource)
79+
}
80+
}
81+
}
82+
}
83+
84+
private fun run_withPrefixMapping_outputsPrefixMapping(cdlLoc: String) {
85+
val inputFile = writeToNetCdf(cdlLoc)
86+
val outputFile = createTempFile()
87+
run("--uri", "http://test.binary-array-ld.net/example", inputFile.absolutePath, outputFile.absolutePath)
88+
89+
val model = createDefaultModel().read(outputFile.toURI().toString(), "ttl")
90+
ModelVerifier(model).apply {
91+
prefix("bald", BALD.prefix)
92+
prefix("skos", SKOS.uri)
6593
resource("http://test.binary-array-ld.net/example") {
6694
statement(RDF.type, BALD.Container)
6795
statement(BALD.contains, model.createResource("http://test.binary-array-ld.net/example/")) {
@@ -77,9 +105,19 @@ class BinaryArrayConvertCliTest {
77105
}
78106
}
79107

108+
@Test
109+
fun run_withPrefixMappingGroup_outputsPrefixMapping() {
110+
run_withPrefixMapping_outputsPrefixMapping("/netcdf/prefix.cdl")
111+
}
112+
113+
@Test
114+
fun run_withPrefixMappingVar_outputsPrefixMapping() {
115+
run_withPrefixMapping_outputsPrefixMapping("/netcdf/prefix-var.cdl")
116+
}
117+
80118
@Test
81119
fun run_withSubgroups_outputsWithSubgroups() {
82-
val inputFile = convertToNetCdf("/netcdf/identity-subgroups.cdl")
120+
val inputFile = writeToNetCdf("/netcdf/identity-subgroups.cdl")
83121
val outputFile = createTempFile()
84122
run("--uri", "http://test.binary-array-ld.net/example", inputFile.absolutePath, outputFile.absolutePath)
85123

binary-array-ld-lib/src/main/kotlin/net/bald/BinaryArray.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package net.bald
22

3+
import org.apache.jena.shared.PrefixMapping
4+
35
/**
46
* Represents the metadata of a binary array dataset.
57
* See https://www.opengis.net/def/binary-array-ld/Array
@@ -10,6 +12,11 @@ interface BinaryArray {
1012
*/
1113
val uri: String
1214

15+
/**
16+
* The prefix mapping to apply to the RDF graph.
17+
*/
18+
val prefixMapping: PrefixMapping
19+
1320
/**
1421
* The root container.
1522
*/

binary-array-ld-lib/src/main/kotlin/net/bald/model/ModelBinaryArrayBuilder.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,46 @@ package net.bald.model
33
import net.bald.BinaryArray
44
import net.bald.vocab.BALD
55
import org.apache.jena.rdf.model.Model
6+
import org.apache.jena.shared.PrefixMapping
7+
import java.net.URI
68

79
class ModelBinaryArrayBuilder(
810
private val model: Model,
911
private val containerFct: ModelContainerBuilder.Factory
1012
) {
1113
fun addBinaryArray(ba: BinaryArray) {
14+
addPrefixMapping(ba.prefixMapping)
1215
val baRes = model.createResource(ba.uri, BALD.Container)
1316
containerFct.forParent(baRes).addContainer(ba.root)
1417
}
1518

19+
private fun addPrefixMapping(prefixMapping: PrefixMapping) {
20+
prefixMapping.nsPrefixMap.onEach { (prefix, uri) ->
21+
validatePrefixMapping(prefix, uri)
22+
}.let(model::setNsPrefixes)
23+
}
24+
25+
private fun validatePrefixMapping(prefix: String, uri: String) {
26+
try {
27+
if (!Prefix.pattern.matches(prefix)) {
28+
throw IllegalArgumentException("Prefix must match pattern ${Prefix.pattern}.")
29+
} else if (!uri.endsWith('/') && !uri.endsWith('#')) {
30+
throw IllegalArgumentException("URI must end with / or #.")
31+
} else {
32+
val scheme = URI.create(uri).scheme
33+
if (scheme != "http" && scheme != "https") {
34+
throw IllegalArgumentException("URI must have HTTP or HTTPS scheme.")
35+
}
36+
}
37+
} catch (e: Exception) {
38+
throw IllegalArgumentException("Unable to add prefix mapping $prefix to model: ${e.message}")
39+
}
40+
}
41+
42+
private object Prefix {
43+
val pattern = Regex("[A-Za-z_]+")
44+
}
45+
1646
class Factory(
1747
private val containerFct: ModelContainerBuilder.Factory
1848
) {

binary-array-ld-lib/src/main/kotlin/net/bald/vocab/BALD.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import org.apache.jena.rdf.model.ResourceFactory.createResource
1111
* See https://www.opengis.net/def/binary-array-ld/.
1212
*/
1313
object BALD {
14-
private const val prefix = "https://www.opengis.net/def/binary-array-ld/"
14+
const val prefix = "https://www.opengis.net/def/binary-array-ld/"
1515

1616
/**
1717
* Resources

binary-array-ld-lib/src/test/kotlin/net/bald/model/ModelBinaryArrayBuilderTest.kt

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ import net.bald.BinaryArray
99
import net.bald.Container
1010
import net.bald.vocab.BALD
1111
import org.apache.jena.rdf.model.ModelFactory
12+
import org.apache.jena.shared.PrefixMapping
1213
import org.apache.jena.vocabulary.RDF
13-
import org.junit.jupiter.api.*
14+
import org.apache.jena.vocabulary.SKOS
15+
import org.junit.jupiter.api.Test
16+
import org.junit.jupiter.api.assertThrows
17+
import java.lang.IllegalArgumentException
18+
import kotlin.test.assertEquals
1419

1520
class ModelBinaryArrayBuilderTest {
1621
private val containerBuilder = mock<ModelContainerBuilder>()
@@ -20,9 +25,13 @@ class ModelBinaryArrayBuilderTest {
2025
private val model = ModelFactory.createDefaultModel()
2126
private val builder = ModelBinaryArrayBuilder.Factory(containerFct).forModel(model)
2227
private val root = mock<Container>()
28+
private val prefix = PrefixMapping.Factory.create()
29+
.setNsPrefix("bald", BALD.prefix)
30+
.setNsPrefix("skos", SKOS.uri)
2331
private val ba = mock<BinaryArray> {
2432
on { uri } doReturn "http://test.binary-array-ld.net/example"
2533
on { this.root } doReturn root
34+
on { prefixMapping } doReturn prefix
2635
}
2736

2837
@Test
@@ -39,4 +48,40 @@ class ModelBinaryArrayBuilderTest {
3948
verify(containerFct).forParent(model.getResource("http://test.binary-array-ld.net/example"))
4049
verify(containerBuilder).addContainer(root)
4150
}
51+
52+
@Test
53+
fun addBinaryArray_addsPrefixMapping() {
54+
builder.addBinaryArray(ba)
55+
ModelVerifier(model).apply {
56+
prefix("bald", BALD.prefix)
57+
prefix("skos", SKOS.uri)
58+
}
59+
}
60+
61+
@Test
62+
fun addBinaryArray_prefixMappingWithInvalidChar_throwsException() {
63+
prefix.setNsPrefix("bald-eg", "http://example.org/prefix/")
64+
val iae = assertThrows<IllegalArgumentException> {
65+
builder.addBinaryArray(ba)
66+
}
67+
assertEquals("Unable to add prefix mapping bald-eg to model: Prefix must match pattern [A-Za-z_]+.", iae.message)
68+
}
69+
70+
@Test
71+
fun addBinaryArray_prefixMappingWithInvalidScheme_throwsException() {
72+
prefix.setNsPrefix("eg", "file:///example/prefix/")
73+
val iae = assertThrows<IllegalArgumentException> {
74+
builder.addBinaryArray(ba)
75+
}
76+
assertEquals("Unable to add prefix mapping eg to model: URI must have HTTP or HTTPS scheme.", iae.message)
77+
}
78+
79+
@Test
80+
fun addBinaryArray_prefixMappingWithoutTrailingChar_throwsException() {
81+
prefix.setNsPrefix("eg", "http://example.org/prefix")
82+
val iae = assertThrows<IllegalArgumentException> {
83+
builder.addBinaryArray(ba)
84+
}
85+
assertEquals("Unable to add prefix mapping eg to model: URI must end with / or #.", iae.message)
86+
}
4287
}

binary-array-ld-lib/src/test/kotlin/net/bald/model/ModelBinaryArrayConverterTest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import net.bald.Container
88
import net.bald.Var
99
import net.bald.vocab.BALD
1010
import org.apache.jena.rdf.model.Model
11+
import org.apache.jena.shared.PrefixMapping
1112
import org.apache.jena.vocabulary.RDF
13+
import org.apache.jena.vocabulary.SKOS
1214
import org.junit.jupiter.api.*
1315

1416
/**
@@ -33,13 +35,19 @@ class ModelBinaryArrayConverterTest {
3335
on { vars() } doReturn vars.asSequence()
3436
on { subContainers() } doReturn emptySequence()
3537
}
38+
val prefix = PrefixMapping.Factory.create()
39+
.setNsPrefix("bald", BALD.prefix)
40+
.setNsPrefix("skos", SKOS.uri)
3641
val ba = mock<BinaryArray> {
3742
on { uri } doReturn "http://test.binary-array-ld.net/example"
3843
on { this.root } doReturn root
44+
on { prefixMapping } doReturn prefix
3945
}
4046
val model = convert(ba)
4147

4248
ModelVerifier(model).apply {
49+
prefix("bald", BALD.prefix)
50+
prefix("skos", SKOS.uri)
4351
resource("http://test.binary-array-ld.net/example") {
4452
statement(RDF.type, BALD.Container)
4553
statement(BALD.contains, model.createResource("http://test.binary-array-ld.net/example/")) {

binary-array-ld-netcdf/src/main/kotlin/net/bald/netcdf/NetCdfBinaryArray.kt

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package net.bald.netcdf
22

33
import net.bald.BinaryArray
44
import net.bald.Container
5-
import ucar.nc2.NetcdfFile
6-
import ucar.nc2.NetcdfFiles
5+
import org.apache.jena.shared.PrefixMapping
6+
import ucar.nc2.*
77
import java.io.Closeable
88
import java.io.File
9+
import java.lang.IllegalStateException
910

1011
/**
1112
* NetCDF implementation of [BinaryArray].
@@ -15,12 +16,41 @@ class NetCdfBinaryArray(
1516
override val uri: String,
1617
private val file: NetcdfFile
1718
): BinaryArray, Closeable {
18-
override val root: Container get() = NetCdfContainer(file.rootGroup)
19+
override val root: Container get() = container(file.rootGroup)
20+
override val prefixMapping: PrefixMapping get() = prefixMapping() ?: PrefixMapping.Factory.create()
1921

2022
override fun close() {
2123
file.close()
2224
}
2325

26+
private fun container(group: Group): Container {
27+
val prefixSrc = prefixSourceName()
28+
return NetCdfContainer(group, prefixSrc)
29+
}
30+
31+
private fun prefixMapping(): PrefixMapping? {
32+
return prefixSource()?.let(::NetCdfPrefixMappingBuilder)?.build()
33+
}
34+
35+
private fun prefixSource(): AttributeContainer? {
36+
return prefixSourceName()?.let { name ->
37+
file.findGroup(name)
38+
?: file.findVariable(name)
39+
?: throw IllegalStateException("Prefix group or variable $name not found.")
40+
}
41+
}
42+
43+
private fun prefixSourceName(): String? {
44+
return file.findGlobalAttribute(Attribute.prefix)?.let { attr ->
45+
attr.stringValue
46+
?: throw IllegalStateException("Global prefix attribute ${Attribute.prefix} has a non-string value.")
47+
}
48+
}
49+
50+
private object Attribute {
51+
const val prefix = "bald__isPrefixedBy"
52+
}
53+
2454
companion object {
2555
/**
2656
* Instantiate a [BinaryArray] representation of the given NetCDF file and identifying URI.
@@ -31,9 +61,17 @@ class NetCdfBinaryArray(
3161
*/
3262
@JvmStatic
3363
fun create(fileLoc: String, uri: String? = null): NetCdfBinaryArray {
34-
val requiredUri = uri ?: uri(fileLoc)
3564
val file = NetcdfFiles.open(fileLoc)
36-
return NetCdfBinaryArray(requiredUri, file)
65+
val requiredUri = uri ?: uri(fileLoc)
66+
return create(file, requiredUri)
67+
}
68+
69+
/**
70+
* @see [create].
71+
*/
72+
@JvmStatic
73+
fun create(file: NetcdfFile, uri: String): NetCdfBinaryArray {
74+
return NetCdfBinaryArray(uri, file)
3775
}
3876

3977
private fun uri(fileLoc: String): String {

binary-array-ld-netcdf/src/main/kotlin/net/bald/netcdf/NetCdfContainer.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,30 @@ package net.bald.netcdf
33
import net.bald.Container
44
import net.bald.Var
55
import ucar.nc2.Group
6+
import ucar.nc2.Variable
67

78
/**
89
* NetCDF implementation of [Container].
910
*/
1011
class NetCdfContainer(
11-
private val group: Group
12+
private val group: Group,
13+
private val prefixSrc: String? = null
1214
): Container {
1315
override val name: String? get() = group.shortName
1416

1517
override fun vars(): Sequence<Var> {
16-
return group.variables.asSequence().map(::NetCdfVar)
18+
return group.variables.asSequence().filter(::acceptVar).map(::NetCdfVar)
1719
}
1820

1921
override fun subContainers(): Sequence<Container> {
20-
return group.groups.asSequence().map(::NetCdfContainer)
22+
return group.groups.asSequence().filter(::acceptGroup).map(::NetCdfContainer)
23+
}
24+
25+
private fun acceptVar(v: Variable): Boolean {
26+
return prefixSrc != v.shortName
27+
}
28+
29+
private fun acceptGroup(group: Group): Boolean {
30+
return prefixSrc != group.shortName
2131
}
2232
}

0 commit comments

Comments
 (0)