Skip to content

Commit 527fcb9

Browse files
committed
Fix bug causing parallel diagram generations to fail.
1 parent 7eb4259 commit 527fcb9

File tree

5 files changed

+189
-55
lines changed

5 files changed

+189
-55
lines changed

core/sds-aspect-model-document-generators/src/main/java/io/openmanufacturing/sds/aspectmodel/generator/diagram/AspectModelDiagramGenerator.java

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,14 @@
3535
import org.apache.commons.text.WordUtils;
3636
import org.apache.jena.query.ARQ;
3737
import org.apache.jena.query.Query;
38-
import org.apache.jena.query.QueryExecution;
39-
import org.apache.jena.query.QueryExecutionFactory;
4038
import org.apache.jena.query.QueryFactory;
41-
import org.apache.jena.query.QuerySolution;
42-
import org.apache.jena.rdf.model.Literal;
4339
import org.apache.jena.rdf.model.Model;
4440
import org.apache.jena.rdf.model.ModelFactory;
4541
import org.apache.jena.rdf.model.Property;
4642
import org.apache.jena.rdf.model.RDFNode;
4743
import org.apache.jena.rdf.model.Resource;
4844
import org.apache.jena.rdf.model.ResourceFactory;
4945
import org.apache.jena.rdf.model.Statement;
50-
import org.apache.jena.sparql.function.FunctionRegistry;
5146
import org.apache.jena.vocabulary.RDF;
5247

5348
import com.google.common.collect.ImmutableList;
@@ -82,14 +77,14 @@ public String getArtifactFilename( final String aspectName, final Locale languag
8277

8378
static final String GET_ELEMENT_NAME_FUNC = "urn:bamm:io.openmanufacturing:function:2.0.0#getElementName";
8479

85-
private final GetElementNameFunctionFactory getElementNameFunctionFactory;
86-
8780
private final Query boxmodelToDotQuery;
8881
private final BoxModel boxModelNamespace;
8982

9083
protected final Model model;
9184
final KnownVersion bammVersion;
9285

86+
private final SparqlExecutor sparqlExecutor;
87+
9388
public AspectModelDiagramGenerator( final VersionedModel versionedModel ) {
9489
final ImmutableList<String> queryFilesForAllBammVersions = ImmutableList.of(
9590
"aspect",
@@ -146,7 +141,7 @@ ImmutableList.<String> builder().addAll( queryFilesForAllBammVersions )
146141
boxmodelToDotQuery = QueryFactory.create( getInputStreamAsString( "boxmodel2dot.sparql" ) );
147142
boxModelNamespace = new BoxModel( bammVersion );
148143

149-
getElementNameFunctionFactory = new GetElementNameFunctionFactory( model );
144+
sparqlExecutor = new SparqlExecutor().useCustomFunction( GET_ELEMENT_NAME_FUNC, new GetElementNameFunctionFactory( model ) );
150145
}
151146

152147
InputStream getInputStream( final String resource ) {
@@ -163,24 +158,6 @@ String getInputStreamAsString( final String resource ) {
163158
}
164159
}
165160

166-
/**
167-
* Executes a SPARQL query that is assumed to generate a flat list of literals
168-
*
169-
* @param model The model to query against
170-
* @param query The query
171-
* @return The string resulting by concatenating the result list in to a multi line string
172-
*/
173-
@SuppressWarnings( "squid:S1905" )
174-
String executeQuery( final Model model, final Query query ) {
175-
try ( final QueryExecution qexec = QueryExecutionFactory.create( query, model ) ) {
176-
FunctionRegistry.get( qexec.getContext() ).put( GET_ELEMENT_NAME_FUNC, getElementNameFunctionFactory );
177-
return StreamSupport.stream( ((Iterable<QuerySolution>) (qexec::execSelect)).spliterator(), false )
178-
.map( solution -> solution.getLiteral( "dotStatement" ) )
179-
.map( Literal::toString )
180-
.collect( Collectors.joining( "\n" ) );
181-
}
182-
}
183-
184161
@SuppressWarnings( "squid:S1166" )
185162
private void generatePng( final String dotInput, final OutputStream output ) throws IOException {
186163
// To make the font available during PNG generation, it needs to be registered
@@ -259,12 +236,7 @@ private String generateDot( final Locale language ) {
259236
.map( queryString -> queryString
260237
.replace( "\"en\"", "\"" + language.toLanguageTag() + "\"" ) )
261238
.map( QueryFactory::create )
262-
.forEach( query -> {
263-
try ( final QueryExecution qexec = QueryExecutionFactory.create( query, model ) ) {
264-
FunctionRegistry.get( qexec.getContext() ).put( GET_ELEMENT_NAME_FUNC, getElementNameFunctionFactory );
265-
qexec.execConstruct( targetModel );
266-
}
267-
} );
239+
.forEach( query -> sparqlExecutor.executeConstruct( model, query, targetModel ) );
268240

269241
breakLongLinesAndEscapeTexts( targetModel );
270242

@@ -274,7 +246,7 @@ private String generateDot( final Locale language ) {
274246
.map( TurtleLoader::loadTurtle )
275247
.ifPresent( tryModel -> tryModel.forEach( targetModel::add ) );
276248

277-
final String queryResult = executeQuery( targetModel, boxmodelToDotQuery );
249+
final String queryResult = sparqlExecutor.executeQuery( targetModel, boxmodelToDotQuery, "dotStatement" );
278250
final String template = getInputStreamAsString( "aspect2dot.mustache" );
279251
return template.replace( "{{&statements}}", queryResult )
280252
.replace( "{{&fontname}}", FONT_NAME )
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright (c) 2023 Robert Bosch Manufacturing Solutions GmbH
3+
*
4+
* See the AUTHORS file(s) distributed with this work for additional
5+
* information regarding authorship.
6+
*
7+
* This Source Code Form is subject to the terms of the Mozilla Public
8+
* License, v. 2.0. If a copy of the MPL was not distributed with this
9+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
10+
*
11+
* SPDX-License-Identifier: MPL-2.0
12+
*/
13+
14+
package io.openmanufacturing.sds.aspectmodel.generator.diagram;
15+
16+
import java.util.stream.Collectors;
17+
import java.util.stream.StreamSupport;
18+
19+
import org.apache.jena.query.Query;
20+
import org.apache.jena.query.QueryExecution;
21+
import org.apache.jena.query.QueryExecutionFactory;
22+
import org.apache.jena.query.QuerySolution;
23+
import org.apache.jena.rdf.model.Literal;
24+
import org.apache.jena.rdf.model.Model;
25+
import org.apache.jena.sparql.function.FunctionFactory;
26+
import org.apache.jena.sparql.function.FunctionRegistry;
27+
28+
/**
29+
* Utility class to help execute SPARQL queries, wrapping some tricky implementation details of the library used (Jena).
30+
*/
31+
public class SparqlExecutor {
32+
33+
private FunctionRegistry customFunctions;
34+
35+
public SparqlExecutor() {
36+
// by default, use the global function registry (the same for all contexts)
37+
customFunctions = FunctionRegistry.get();
38+
}
39+
40+
/**
41+
* Use a custom function when executing queries, the function will be unique to this executor instance.
42+
*
43+
* @param name Name of the function
44+
* @param functionFactory Function factory
45+
* @return this to allow fluent registration of more than one function
46+
*/
47+
public SparqlExecutor useCustomFunction( final String name, final FunctionFactory functionFactory ) {
48+
ensureContextSpecificFunctionRegistry();
49+
customFunctions.put( name, functionFactory );
50+
return this;
51+
}
52+
53+
// Workaround: current Jena implementation uses a single global function registry for all contexts;
54+
// so as we want to register a context-specific function, we need also a context-specific function registry
55+
private void ensureContextSpecificFunctionRegistry() {
56+
final FunctionRegistry global = FunctionRegistry.get();
57+
if ( customFunctions == global ) {
58+
customFunctions = new FunctionRegistry();
59+
global.keys().forEachRemaining( uri -> customFunctions.put( uri, global.get( uri ) ) );
60+
}
61+
}
62+
63+
/**
64+
* Executes a SPARQL query that is assumed to generate a flat list of literals
65+
*
66+
* @param model The model to query against
67+
* @param query The query
68+
* @return The string resulting by concatenating the result list in to a multi line string
69+
*/
70+
@SuppressWarnings( "squid:S1905" )
71+
public String executeQuery( final Model model, final Query query, final String literalName ) {
72+
try ( final QueryExecution qexec = QueryExecutionFactory.create( query, model ) ) {
73+
FunctionRegistry.set( qexec.getContext(), customFunctions );
74+
return StreamSupport.stream( ((Iterable<QuerySolution>) (qexec::execSelect)).spliterator(), false )
75+
.map( solution -> solution.getLiteral( literalName ) )
76+
.map( Literal::toString )
77+
.collect( Collectors.joining( "\n" ) );
78+
}
79+
}
80+
81+
/**
82+
* Executes a SPARQL construct query.
83+
*
84+
* @param model The model to query against
85+
* @param query The query
86+
* @param targetModel The result of the operation
87+
*/
88+
public void executeConstruct( final Model model, final Query query, final Model targetModel ) {
89+
try ( final QueryExecution qexec = QueryExecutionFactory.create( query, model ) ) {
90+
FunctionRegistry.set( qexec.getContext(), customFunctions );
91+
qexec.execConstruct( targetModel );
92+
}
93+
}
94+
}

core/sds-aspect-model-document-generators/src/test/java/io/openmanufacturing/sds/aspectmodel/generator/diagram/AspectModelDiagramGeneratorTest.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,12 @@
1414
package io.openmanufacturing.sds.aspectmodel.generator.diagram;
1515

1616
import static org.assertj.core.api.Assertions.assertThat;
17-
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
1817

1918
import java.io.ByteArrayOutputStream;
2019
import java.io.IOException;
2120
import java.nio.charset.StandardCharsets;
2221
import java.util.Locale;
2322

24-
import org.junit.jupiter.api.Test;
2523
import org.junit.jupiter.params.ParameterizedTest;
2624
import org.junit.jupiter.params.provider.MethodSource;
2725

@@ -38,7 +36,7 @@ public void testAspectWithRecursivePropertyWithOptional( final KnownVersion meta
3836
metaModelVersion );
3937
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
4038
context.service().generateDiagram( AspectModelDiagramGenerator.Format.DOT, Locale.ENGLISH, outStream );
41-
final String result = outStream.toString( StandardCharsets.UTF_8.name() );
39+
final String result = outStream.toString( StandardCharsets.UTF_8 );
4240

4341
// Aspect Node
4442
assertThat( result ).containsOnlyOnce( "testPropertyProperty [label=\"{ «Property»\\ntestProperty|}\"]" );
@@ -56,13 +54,4 @@ public void testAspectWithRecursivePropertyWithOptional( final KnownVersion meta
5654
assertThat( result ).containsOnlyOnce(
5755
"AspectWithRecursivePropertyWithOptionalAspect -> testPropertyProperty [label=\"property\"]" );
5856
}
59-
60-
@Test
61-
void testNoStaticFunctionRegistryIsUsed() {
62-
// the calculation of synthetic names (which involves function registry) is only done when blank nodes are present in the model
63-
final TestContext context = new TestContext( TestAspect.ASPECT_WITH_BLANK_NODE, KnownVersion.BAMM_2_0_0 );
64-
// if static (global) registry was used, this second context would overwrite it with a different model
65-
final TestContext context2 = new TestContext( TestAspect.ASPECT_WITH_BLANK_NODE, KnownVersion.BAMM_2_0_0 );
66-
assertDoesNotThrow( () -> context.service().generateDiagrams( AspectModelDiagramGenerator.Format.SVG, ( path ) -> new ByteArrayOutputStream() ) );
67-
}
6857
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright (c) 2023 Robert Bosch Manufacturing Solutions GmbH
3+
*
4+
* See the AUTHORS file(s) distributed with this work for additional
5+
* information regarding authorship.
6+
*
7+
* This Source Code Form is subject to the terms of the Mozilla Public
8+
* License, v. 2.0. If a copy of the MPL was not distributed with this
9+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
10+
*
11+
* SPDX-License-Identifier: MPL-2.0
12+
*/
13+
14+
package io.openmanufacturing.sds.aspectmodel.generator.diagram;
15+
16+
import static org.junit.jupiter.api.Assertions.assertTrue;
17+
18+
import java.io.ByteArrayInputStream;
19+
import java.nio.charset.StandardCharsets;
20+
21+
import org.apache.jena.graph.Node;
22+
import org.apache.jena.query.QueryFactory;
23+
import org.apache.jena.rdf.model.Model;
24+
import org.apache.jena.sparql.expr.NodeValue;
25+
import org.apache.jena.sparql.function.Function;
26+
import org.apache.jena.sparql.function.FunctionBase1;
27+
import org.apache.jena.sparql.function.FunctionFactory;
28+
import org.junit.jupiter.api.Test;
29+
30+
import io.openmanufacturing.sds.aspectmodel.resolver.services.TurtleLoader;
31+
32+
public class SparqlExecutorTest {
33+
34+
// function creating synthetic names starting with the given prefix for anonymous nodes
35+
record CustomFunctionFactory(String prefix) implements FunctionFactory {
36+
@Override
37+
public Function create( final String s ) {
38+
return new FunctionBase1() {
39+
@Override
40+
public NodeValue exec( final NodeValue v ) {
41+
final Node node = v.asNode();
42+
if ( node.isBlank() ) {
43+
return NodeValue.makeString( prefix + "_" + node );
44+
}
45+
return null;
46+
}
47+
};
48+
}
49+
}
50+
51+
@Test
52+
void testCustomFunctionIsUniqueForContext() {
53+
final String modelSource = """
54+
@prefix : <urn:bamm:dummy#>.
55+
[
56+
a :Object ;
57+
]
58+
""";
59+
60+
final String querySource = """
61+
prefix : <urn:bamm:dummy#>
62+
prefix func: <urn:bamm:function:2.0.0#>
63+
64+
select ?anonName
65+
where {
66+
{
67+
?anon a :Object .
68+
}
69+
bind( func:getElementName( ?anon ) as ?anonName )
70+
}
71+
""";
72+
73+
final Model model = TurtleLoader.loadTurtle( new ByteArrayInputStream( modelSource.getBytes( StandardCharsets.UTF_8 ) ) ).get();
74+
final SparqlExecutor executor1 = new SparqlExecutor().useCustomFunction( "urn:bamm:function:2.0.0#getElementName",
75+
new CustomFunctionFactory( "executor1" ) );
76+
final SparqlExecutor executor2 = new SparqlExecutor().useCustomFunction( "urn:bamm:function:2.0.0#getElementName",
77+
new CustomFunctionFactory( "executor2" ) );
78+
final String result1 = executor1.executeQuery( model, QueryFactory.create( querySource ), "anonName" );
79+
assertTrue( result1.startsWith( "executor1_" ) );
80+
final String result2 = executor2.executeQuery( model, QueryFactory.create( querySource ), "anonName" );
81+
assertTrue( result2.startsWith( "executor2_" ) );
82+
}
83+
}

core/sds-aspect-model-document-generators/src/test/java/io/openmanufacturing/sds/aspectmodel/generator/diagram/TestContext.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
import java.util.Map;
2121

2222
import org.apache.jena.query.Query;
23-
import org.apache.jena.query.QueryExecution;
24-
import org.apache.jena.query.QueryExecutionFactory;
2523
import org.apache.jena.query.QueryFactory;
2624
import org.apache.jena.rdf.model.Literal;
2725
import org.apache.jena.rdf.model.Model;
@@ -34,7 +32,6 @@
3432
import org.apache.jena.rdf.model.Selector;
3533
import org.apache.jena.rdf.model.SimpleSelector;
3634
import org.apache.jena.rdf.model.Statement;
37-
import org.apache.jena.sparql.function.FunctionRegistry;
3835
import org.apache.jena.vocabulary.RDF;
3936

4037
import io.openmanufacturing.sds.aspectmetamodel.KnownVersion;
@@ -46,15 +43,17 @@ public class TestContext {
4643
private final AspectModelDiagramGenerator service;
4744
private final KnownVersion version;
4845
private final BoxModel boxModel;
49-
private final GetElementNameFunctionFactory getElementNameFunctionFactory;
46+
private final SparqlExecutor sparqlExecutor;
5047

5148
public TestContext( final TestModel model, final KnownVersion version ) {
5249
this.version = version;
5350
final VersionedModel versionedModel = TestResources.getModel( model, version ).get();
5451
service = new AspectModelDiagramGenerator( versionedModel );
5552
boxModel = new BoxModel( version );
5653
service.model.setNsPrefix( "", boxModel.getNamespace() );
57-
getElementNameFunctionFactory = new GetElementNameFunctionFactory( versionedModel.getModel() );
54+
55+
sparqlExecutor = new SparqlExecutor().useCustomFunction( AspectModelDiagramGenerator.GET_ELEMENT_NAME_FUNC,
56+
new GetElementNameFunctionFactory( versionedModel.getModel() ) );
5857
}
5958

6059
/**
@@ -141,10 +140,7 @@ public void executeAttributeIsNotPresentTest( final String sparqlQueryFileName,
141140
public Model executeQuery( final String sparqlQueryFileName ) {
142141
final Query query = QueryFactory.create( getInputStreamAsString( sparqlQueryFileName ) );
143142
final Model queryResult = ModelFactory.createDefaultModel();
144-
try ( final QueryExecution qexec = QueryExecutionFactory.create( query, model() ) ) {
145-
FunctionRegistry.get( qexec.getContext() ).put( AspectModelDiagramGenerator.GET_ELEMENT_NAME_FUNC, getElementNameFunctionFactory );
146-
qexec.execConstruct( queryResult );
147-
}
143+
sparqlExecutor.executeConstruct( model(), query, queryResult );
148144
return queryResult;
149145
}
150146

@@ -184,7 +180,7 @@ public KnownVersion getVersion() {
184180
}
185181

186182
public String executeQuery( final Model model, final Query query ) {
187-
return service.executeQuery( model, query );
183+
return sparqlExecutor.executeQuery( model, query, "dotStatement" );
188184
}
189185

190186
public AspectModelDiagramGenerator service() {

0 commit comments

Comments
 (0)