Skip to content

Commit edae7c7

Browse files
Extract handling for properties to allow more control
Property handling is now done through an indirection which allows to decide per property or property type how it should be handled. For the first this is now used when writing submodels with data, where serialized LangStrings are given, which require a special handling, as the payload structure is different from the metamodel structure.
1 parent 3d774bf commit edae7c7

File tree

7 files changed

+318
-145
lines changed

7 files changed

+318
-145
lines changed

core/sds-aspect-model-aas-generator/src/main/java/io/openmanufacturing/sds/aspectmodel/aas/AspectModelAASGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public void generateAasXmlFile(
7777
}
7878

7979
protected ByteArrayOutputStream generateXmlOutput( final Map<Aspect, JsonNode> aspectsWithData ) throws IOException {
80-
final AspectModelAASVisitor visitor = new AspectModelAASVisitor();
80+
final AspectModelAASVisitor visitor = new AspectModelAASVisitor().withPropertyMapper( new LangStringPropertyMapper() );
8181

8282
final Map<Aspect, Environment> aspectEnvironments =
8383
aspectsWithData.entrySet().stream()

core/sds-aspect-model-aas-generator/src/main/java/io/openmanufacturing/sds/aspectmodel/aas/AspectModelAASVisitor.java

Lines changed: 68 additions & 143 deletions
Large diffs are not rendered by default.

core/sds-aspect-model-aas-generator/src/main/java/io/openmanufacturing/sds/aspectmodel/aas/Context.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ public String getPropertyShortId() {
163163
* @return the property value at the current property path
164164
*/
165165
public String getPropertyValue( final String defaultValue ) {
166-
return getRawPropertyValue().map( JsonNode::asText ).orElse( defaultValue );
166+
return getRawPropertyValue().map( valueNode -> valueNode.asText( defaultValue ) )
167+
.orElse( defaultValue );
167168
}
168169

169170
/**
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.openmanufacturing.sds.aspectmodel.aas;
2+
3+
import org.eclipse.digitaltwin.aas4j.v3.model.Property;
4+
import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty;
5+
6+
import io.openmanufacturing.sds.metamodel.Type;
7+
8+
/**
9+
* The default mapper used for all properties.
10+
*/
11+
public class DefaultPropertyMapper implements PropertyMapper<Property> {
12+
@Override
13+
public Property mapToAasProperty( Type type, io.openmanufacturing.sds.metamodel.Property property, Context context ) {
14+
return new DefaultProperty.Builder()
15+
.idShort( context.getPropertyShortId() )
16+
.kind( context.getModelingKind() )
17+
.valueType( mapAASXSDataType( mapType( type ) ) )
18+
.displayName( LANG_STRING_MAPPER.map( property.getPreferredNames() ) )
19+
.value( context.getPropertyValue( UNKNOWN_EXAMPLE ) )
20+
.description( LANG_STRING_MAPPER.map( property.getDescriptions() ) )
21+
.semanticId( buildReferenceToConceptDescription( property ) )
22+
.build();
23+
}
24+
25+
private String mapType( final Type type ) {
26+
return type.getUrn();
27+
}
28+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.openmanufacturing.sds.aspectmodel.aas;
2+
3+
import java.util.List;
4+
import java.util.Locale;
5+
import java.util.Set;
6+
import java.util.stream.Collectors;
7+
8+
import org.eclipse.digitaltwin.aas4j.v3.model.LangString;
9+
import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangString;
10+
11+
/**
12+
* Default implementation of multiple ways to map Aspect Model {@code LangString}s to AAS4J {@link LangString}s.
13+
*/
14+
public class LangStringMapper {
15+
public List<LangString> map( final Set<io.openmanufacturing.sds.metamodel.datatypes.LangString> localizedStrings ) {
16+
return localizedStrings.stream()
17+
.map( ( entry ) -> map( entry.getLanguageTag(), entry.getValue() ) )
18+
.collect( Collectors.toList() );
19+
}
20+
21+
public LangString map( final io.openmanufacturing.sds.metamodel.datatypes.LangString langString ) {
22+
return map( langString.getLanguageTag(), langString.getValue() );
23+
}
24+
25+
public LangString map( final Locale locale, final String value ) {
26+
return createLangString( value, locale.getLanguage() );
27+
}
28+
29+
public LangString createLangString( final String text, final String locale ) {
30+
return new DefaultLangString.Builder().language( locale ).text( text ).build();
31+
}
32+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.openmanufacturing.sds.aspectmodel.aas;
2+
3+
import java.util.HashMap;
4+
import java.util.List;
5+
import java.util.Map;
6+
7+
import org.apache.jena.vocabulary.RDF;
8+
import org.eclipse.digitaltwin.aas4j.v3.model.LangString;
9+
import org.eclipse.digitaltwin.aas4j.v3.model.MultiLanguageProperty;
10+
import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultMultiLanguageProperty;
11+
12+
import com.fasterxml.jackson.databind.JsonNode;
13+
14+
import io.openmanufacturing.sds.metamodel.Property;
15+
import io.openmanufacturing.sds.metamodel.Type;
16+
17+
/**
18+
* Special mapper to create {@link MultiLanguageProperty}s from Aspect Model properties that carry multiple localized strings.
19+
*/
20+
public class LangStringPropertyMapper implements PropertyMapper<MultiLanguageProperty> {
21+
22+
@Override
23+
public boolean canHandle( io.openmanufacturing.sds.metamodel.Property property ) {
24+
return property.getDataType()
25+
.map( Type::getUrn )
26+
.filter( RDF.langString.getURI()::equals )
27+
.isPresent();
28+
}
29+
30+
@Override
31+
public MultiLanguageProperty mapToAasProperty( Type type, io.openmanufacturing.sds.metamodel.Property property, Context context ) {
32+
return new DefaultMultiLanguageProperty.Builder().idShort( context.getPropertyShortId() )
33+
.kind( context.getModelingKind() )
34+
.description( LANG_STRING_MAPPER.map( property.getDescriptions() ) )
35+
.displayName( LANG_STRING_MAPPER.map( property.getPreferredNames() ) )
36+
.semanticId( buildReferenceToConceptDescription( property ) )
37+
.value( extractLangStrings( property, context ) )
38+
.build();
39+
}
40+
41+
private List<LangString> extractLangStrings( Property property, Context context ) {
42+
return context.getRawPropertyValue()
43+
.filter( JsonNode::isObject )
44+
.map( node -> {
45+
final Map<String, String> entries = new HashMap<>();
46+
node.fields().forEachRemaining( field -> entries.put( field.getKey(), field.getValue().asText() ) );
47+
return entries;
48+
} )
49+
.map( rawEntries -> rawEntries.entrySet()
50+
.stream()
51+
.map( entry -> LANG_STRING_MAPPER.createLangString( entry.getValue(), entry.getKey() ) )
52+
.toList() )
53+
.orElseGet( () -> List.of() );
54+
}
55+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package io.openmanufacturing.sds.aspectmodel.aas;
2+
3+
import java.util.Map;
4+
5+
import org.apache.jena.rdf.model.Resource;
6+
import org.apache.jena.rdf.model.ResourceFactory;
7+
import org.apache.jena.vocabulary.XSD;
8+
import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd;
9+
import org.eclipse.digitaltwin.aas4j.v3.model.Key;
10+
import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes;
11+
import org.eclipse.digitaltwin.aas4j.v3.model.Reference;
12+
import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement;
13+
import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultKey;
14+
import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReference;
15+
16+
import com.google.common.collect.ImmutableMap;
17+
18+
import io.openmanufacturing.sds.aspectmodel.urn.AspectModelUrn;
19+
import io.openmanufacturing.sds.metamodel.NamedElement;
20+
import io.openmanufacturing.sds.metamodel.Property;
21+
import io.openmanufacturing.sds.metamodel.Type;
22+
23+
/**
24+
* Base interface for any class that can map a property to a {@link SubmodelElement}.
25+
*
26+
* @param <T> the concrete type of {@link SubmodelElement} the implementing mapper produces
27+
*/
28+
public interface PropertyMapper<T extends SubmodelElement> {
29+
static final String UNKNOWN_TYPE = "Unknown";
30+
31+
static final String UNKNOWN_EXAMPLE = UNKNOWN_TYPE;
32+
33+
static final LangStringMapper LANG_STRING_MAPPER = new LangStringMapper();
34+
35+
/**
36+
* Maps Aspect types to DataTypeDefXsd Schema types, with no explicit mapping defaulting to
37+
* string
38+
*/
39+
static final Map<Resource, DataTypeDefXsd> AAS_XSD_TYPE_MAP =
40+
ImmutableMap.<Resource, DataTypeDefXsd> builder()
41+
.put( XSD.anyURI, DataTypeDefXsd.ANY_URI )
42+
.put( XSD.yearMonthDuration, DataTypeDefXsd.YEAR_MONTH_DURATION )
43+
.put( XSD.xboolean, DataTypeDefXsd.BOOLEAN )
44+
.put( XSD.xbyte, DataTypeDefXsd.BYTE )
45+
.put( XSD.date, DataTypeDefXsd.DATE )
46+
.put( XSD.dateTime, DataTypeDefXsd.DATE_TIME )
47+
.put( XSD.dateTimeStamp, DataTypeDefXsd.DATE_TIME_STAMP )
48+
.put( XSD.dayTimeDuration, DataTypeDefXsd.DAY_TIME_DURATION )
49+
.put( XSD.decimal, DataTypeDefXsd.DECIMAL )
50+
.put( XSD.xdouble, DataTypeDefXsd.DOUBLE )
51+
.put( XSD.duration, DataTypeDefXsd.DURATION )
52+
.put( XSD.xfloat, DataTypeDefXsd.FLOAT )
53+
.put( XSD.gMonth, DataTypeDefXsd.GMONTH )
54+
.put( XSD.gMonthDay, DataTypeDefXsd.GMONTH_DAY )
55+
.put( XSD.gYear, DataTypeDefXsd.GYEAR )
56+
.put( XSD.gYearMonth, DataTypeDefXsd.GYEAR_MONTH )
57+
.put( XSD.hexBinary, DataTypeDefXsd.HEX_BINARY )
58+
.put( XSD.xint, DataTypeDefXsd.INT )
59+
.put( XSD.integer, DataTypeDefXsd.INTEGER )
60+
.put( XSD.xlong, DataTypeDefXsd.LONG )
61+
.put( XSD.negativeInteger, DataTypeDefXsd.NEGATIVE_INTEGER )
62+
.put( XSD.nonNegativeInteger, DataTypeDefXsd.NON_NEGATIVE_INTEGER )
63+
.put( XSD.positiveInteger, DataTypeDefXsd.POSITIVE_INTEGER )
64+
.put( XSD.xshort, DataTypeDefXsd.SHORT )
65+
.put( XSD.normalizedString, DataTypeDefXsd.STRING )
66+
.put( XSD.time, DataTypeDefXsd.TIME )
67+
.put( XSD.unsignedByte, DataTypeDefXsd.UNSIGNED_BYTE )
68+
.put( XSD.unsignedInt, DataTypeDefXsd.UNSIGNED_INT )
69+
.put( XSD.unsignedLong, DataTypeDefXsd.UNSIGNED_LONG )
70+
.put( XSD.unsignedShort, DataTypeDefXsd.UNSIGNED_SHORT )
71+
.build();
72+
73+
/**
74+
* Performs the mapping of the given property to a AAS {@link SubmodelElement}.
75+
*
76+
* @param type the type of the given property
77+
* @param property the property to map
78+
* @param context the current visitor context
79+
* @return the newly created {@link SubmodelElement}
80+
*/
81+
T mapToAasProperty( Type type, Property property, Context context );
82+
83+
/**
84+
* Whether this {@code PropertyMapper} can handle the given property.
85+
*
86+
* Defaults to {@code true}, implementors should override.
87+
*
88+
* @param property the property to test
89+
* @return {@code true} if this property mapper can handle the given property, {@code false} else
90+
*/
91+
default boolean canHandle( Property property ) {
92+
return true;
93+
}
94+
95+
/**
96+
* Maps the given URN to a {@link DataTypeDefXsd} schema type.
97+
*
98+
* @param urn the URN to map
99+
* @return the {@code DataTypeDefXsd} for the given URN
100+
*/
101+
default DataTypeDefXsd mapAASXSDataType( String urn ) {
102+
final Resource resource = ResourceFactory.createResource( urn );
103+
return AAS_XSD_TYPE_MAP.getOrDefault( resource, DataTypeDefXsd.STRING );
104+
}
105+
106+
/**
107+
* Builds a concept description reference for the given property.
108+
*
109+
* @param property the property to build the reference for
110+
* @return the newly created reference
111+
*/
112+
default Reference buildReferenceToConceptDescription( Property property ) {
113+
final Key key =
114+
new DefaultKey.Builder()
115+
.type( KeyTypes.CONCEPT_DESCRIPTION )
116+
.value( determineIdentifierFor( property ) )
117+
.build();
118+
return new DefaultReference.Builder().keys( key ).build();
119+
}
120+
121+
/**
122+
* Determines the identifier for the given {@link NamedElement}.
123+
*
124+
* @param element the element to get the identifier for
125+
* @return the identifier
126+
*/
127+
default String determineIdentifierFor( NamedElement element ) {
128+
return element.getAspectModelUrn()
129+
.map( AspectModelUrn::toString )
130+
.orElseGet( element::getName );
131+
}
132+
}

0 commit comments

Comments
 (0)