Skip to content

Commit 49bf538

Browse files
authored
Feature/add aasx generation (#51)
* Add AASX and AAS-XML generation to backend * Fix native-image generation with aasx file generation
1 parent 512cdde commit 49bf538

File tree

12 files changed

+2773
-103
lines changed

12 files changed

+2773
-103
lines changed

.graalvm/reflect-config.json

Lines changed: 1876 additions & 92 deletions
Large diffs are not rendered by default.

pom.xml

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
<lombok-version>1.18.24</lombok-version>
4646
<commons-io-version>2.13.0</commons-io-version>
4747
<commons-validator-version>1.7</commons-validator-version>
48+
<commons-code-version>1.15</commons-code-version>
4849
<port-allocator-version>1.2</port-allocator-version>
4950
<process-exec-version>0.9</process-exec-version>
5051
<mockito-inline-version>5.1.1</mockito-inline-version>
@@ -72,7 +73,7 @@
7273
<maven-scm-plugin-version>1.12.2</maven-scm-plugin-version>
7374
<jacoco-maven-plugin-version>0.8.7</jacoco-maven-plugin-version>
7475
<maven-surefire-plugin-version>2.22.2</maven-surefire-plugin-version>
75-
<cyclonedx-maven-plugin-version>1.4.1</cyclonedx-maven-plugin-version>
76+
<cyclonedx-maven-plugin-version>2.7.10</cyclonedx-maven-plugin-version>
7677
<frontend-maven-plugin-version>1.12.1</frontend-maven-plugin-version>
7778

7879
<!-- General settings -->
@@ -138,6 +139,11 @@
138139
<artifactId>esmf-aspect-meta-model-java</artifactId>
139140
<version>${esmf-sdk-version}</version>
140141
</dependency>
142+
<dependency>
143+
<groupId>org.eclipse.esmf</groupId>
144+
<artifactId>esmf-aspect-model-aas-generator</artifactId>
145+
<version>${esmf-sdk-version}</version>
146+
</dependency>
141147
<dependency>
142148
<groupId>org.eclipse.esmf</groupId>
143149
<artifactId>esmf-aspect-model-document-generators</artifactId>
@@ -249,6 +255,11 @@
249255
<artifactId>memoryfilesystem</artifactId>
250256
<version>${memoryfilesystem-version}</version>
251257
</dependency>
258+
<dependency>
259+
<groupId>commons-codec</groupId>
260+
<artifactId>commons-codec</artifactId>
261+
<version>${commons-code-version}</version>
262+
</dependency>
252263

253264
<!-- Third party dependencies for testing -->
254265
<dependency>
@@ -475,6 +486,28 @@
475486
<id>native-image</id>
476487
<build>
477488
<plugins>
489+
<plugin>
490+
<groupId>org.codehaus.mojo</groupId>
491+
<artifactId>exec-maven-plugin</artifactId>
492+
<executions>
493+
<!-- Generate admin-shell reflection config -->
494+
<execution>
495+
<id>generate-admin-shell-reflection-config</id>
496+
<phase>process-classes</phase>
497+
<goals>
498+
<goal>java</goal>
499+
</goals>
500+
<configuration>
501+
<!-- The main class of your build-time scanning code -->
502+
<mainClass>org.eclipse.esmf.ame.buildtime.Aas4jClassSetup</mainClass>
503+
<!-- Pass the properties file as a commandline param -->
504+
<commandlineArgs>${project.build.outputDirectory}/adminshell.properties
505+
</commandlineArgs>
506+
<cleanupDaemonThreads>false</cleanupDaemonThreads>
507+
</configuration>
508+
</execution>
509+
</executions>
510+
</plugin>
478511
<plugin>
479512
<groupId>org.graalvm.buildtools</groupId>
480513
<artifactId>native-maven-plugin</artifactId>
@@ -505,7 +538,7 @@
505538
<buildArg>--no-fallback</buildArg>
506539
<buildArg>--report-unsupported-elements-at-runtime</buildArg>
507540
<buildArg>
508-
--initialize-at-build-time=org.slf4j.LoggerFactory,ch.qos.logback,guru.nidi.graphviz.attribute.,org.apache.velocity.
541+
--initialize-at-build-time=org.slf4j.LoggerFactory,ch.qos.logback,guru.nidi.graphviz.attribute.,org.apache.velocity.,org.apache.poi.util.LocaleUtil
509542
</buildArg>
510543
<buildArg>-J-Xmx8G</buildArg>
511544
<buildArg>-H:+ReportExceptionStackTraces</buildArg>

postman/ame.postman_collection.json

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{
22
"info": {
3-
"_postman_id": "60a84c95-6f04-4bea-86ee-17e70d30b603",
3+
"_postman_id": "556ca409-a79a-4d08-a85e-eb1482a8a672",
44
"name": "AME.POSTMAN.RESOURCES",
5-
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
5+
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
6+
"_exporter_id": "30151852"
67
},
78
"item": [
89
{
@@ -902,6 +903,94 @@
902903
}
903904
},
904905
"response": []
906+
},
907+
{
908+
"name": "GenerateAASX",
909+
"event": [
910+
{
911+
"listen": "test",
912+
"script": {
913+
"exec": [
914+
"pm.test(\"Status code is 200\", function () {\r",
915+
" pm.response.to.have.status(200);\r",
916+
"});\r",
917+
"pm.test(\"Response body is valid\", function () {\r",
918+
" const jsonData = pm.response.text();\r",
919+
" pm.expect(jsonData).to.include(\"aasx\");\r",
920+
"});"
921+
],
922+
"type": "text/javascript"
923+
}
924+
}
925+
],
926+
"request": {
927+
"method": "POST",
928+
"header": [],
929+
"body": {
930+
"mode": "raw",
931+
"raw": "@prefix samm: <urn:samm:org.eclipse.esmf.samm:meta-model:2.1.0#> .\r\n@prefix samm-c: <urn:samm:org.eclipse.esmf.samm:characteristic:2.1.0#> .\r\n@prefix samm-e: <urn:samm:org.eclipse.esmf.samm:entity:2.1.0#> .\r\n@prefix unit: <urn:samm:org.eclipse.esmf.samm:unit:2.1.0#> .\r\n@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\r\n@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\r\n@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\r\n@prefix : <urn:samm:io.aspectmodel:1.0.0#> .\r\n\r\n:AspectDefault a samm:Aspect ;\r\n samm:properties (:property1) ;\r\n samm:operations () .\r\n\r\n:property1 a samm:Property;\r\n samm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a samm:Characteristic ;\r\n samm:dataType xsd:string .\r\n"
932+
},
933+
"url": {
934+
"raw": "http://localhost:{{port}}/ame/api/generate/aasx",
935+
"protocol": "http",
936+
"host": [
937+
"localhost"
938+
],
939+
"port": "{{port}}",
940+
"path": [
941+
"ame",
942+
"api",
943+
"generate",
944+
"aasx"
945+
]
946+
}
947+
},
948+
"response": []
949+
},
950+
{
951+
"name": "GenerateAASXML",
952+
"event": [
953+
{
954+
"listen": "test",
955+
"script": {
956+
"exec": [
957+
"pm.test(\"Status code is 200\", function () {\r",
958+
" pm.response.to.have.status(200);\r",
959+
"});\r",
960+
"pm.test(\"Response body is valid\", function () {\r",
961+
" const jsonData = pm.response.text();\r",
962+
" pm.expect(jsonData).to.include(\"<?xml version='1.0' encoding='UTF-8'?>\");\r",
963+
" pm.expect(jsonData).to.include(\"https://admin-shell.io/aas/3/0\");\r",
964+
"\r",
965+
"});"
966+
],
967+
"type": "text/javascript"
968+
}
969+
}
970+
],
971+
"request": {
972+
"method": "POST",
973+
"header": [],
974+
"body": {
975+
"mode": "raw",
976+
"raw": "@prefix samm: <urn:samm:org.eclipse.esmf.samm:meta-model:2.1.0#> .\r\n@prefix samm-c: <urn:samm:org.eclipse.esmf.samm:characteristic:2.1.0#> .\r\n@prefix samm-e: <urn:samm:org.eclipse.esmf.samm:entity:2.1.0#> .\r\n@prefix unit: <urn:samm:org.eclipse.esmf.samm:unit:2.1.0#> .\r\n@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\r\n@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\r\n@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\r\n@prefix : <urn:samm:io.aspectmodel:1.0.0#> .\r\n\r\n:AspectDefault a samm:Aspect ;\r\n samm:properties (:property1) ;\r\n samm:operations () .\r\n\r\n:property1 a samm:Property;\r\n samm:characteristic :Characteristic1 .\r\n\r\n:Characteristic1 a samm:Characteristic ;\r\n samm:dataType xsd:string .\r\n"
977+
},
978+
"url": {
979+
"raw": "http://localhost:{{port}}/ame/api/generate/aas-xml",
980+
"protocol": "http",
981+
"host": [
982+
"localhost"
983+
],
984+
"port": "{{port}}",
985+
"path": [
986+
"ame",
987+
"api",
988+
"generate",
989+
"aas-xml"
990+
]
991+
}
992+
},
993+
"response": []
905994
}
906995
],
907996
"event": [
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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 org.eclipse.esmf.ame.buildtime;
15+
16+
import static org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.ReflectionHelper.*;
17+
18+
import java.io.File;
19+
import java.io.FileOutputStream;
20+
import java.io.IOException;
21+
import java.util.ArrayList;
22+
import java.util.HashMap;
23+
import java.util.HashSet;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Objects;
27+
import java.util.Properties;
28+
import java.util.Set;
29+
import java.util.stream.Collectors;
30+
31+
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util.ReflectionHelper;
32+
import org.eclipse.esmf.ame.substitution.AdminShellConfig;
33+
import org.eclipse.esmf.ame.substitution.ImplementationInfo;
34+
import org.eclipse.esmf.ame.substitution.Target_org_eclipse_digitaltwin_aas4j_v3_dataformat_core_util_ReflectionHelper;
35+
36+
import io.github.classgraph.ClassGraph;
37+
import io.github.classgraph.ClassInfo;
38+
import io.github.classgraph.ClassInfoList;
39+
import io.github.classgraph.ScanResult;
40+
41+
/**
42+
* This class generates the reflection information normally stored by {@link ReflectionHelper} and serializes it into a
43+
* .properties file.
44+
* It is part of the substitution logic for this class, see
45+
* {@link Target_org_eclipse_digitaltwin_aas4j_v3_dataformat_core_util_ReflectionHelper} for more information.
46+
* Note that this class is <i>only</i> supposed to run at build time (via execution from the Maven build) and is not
47+
* part of the
48+
* resulting CLI codebase. Running this class is configured in the pom.xml of the CLI Maven module (via
49+
* exec-maven-plugin).
50+
*/
51+
public class Aas4jClassSetup {
52+
private final AdminShellConfig config;
53+
54+
public Aas4jClassSetup() {
55+
// The following replicates the logic from ReflectionHelper's static constructor, but instead stores its result
56+
// in the AdminShellConfig object that can then be written to a .properties file
57+
final ScanResult modelScan = new ClassGraph()
58+
.enableClassInfo()
59+
.acceptPackagesNonRecursive( MODEL_PACKAGE_NAME )
60+
.scan();
61+
config = new AdminShellConfig();
62+
config.typesWithModelType = scanModelTypes( modelScan );
63+
config.subtypes = scanSubtypes( modelScan );
64+
config.jsonMixins = scanMixins( modelScan, JSON_MIXINS_PACKAGE_NAME );
65+
config.xmlMixins = scanMixins( modelScan, XML_MIXINS_PACKAGE_NAME );
66+
config.defaultImplementations = scanDefaultImplementations( modelScan );
67+
config.interfaces = scanAasInterfaces();
68+
config.enums = modelScan.getAllEnums().loadClasses( Enum.class );
69+
config.interfacesWithoutDefaultImplementation = getInterfacesWithoutDefaultImplementation( modelScan );
70+
}
71+
72+
public static void main( final String[] args ) throws IOException {
73+
final AdminShellConfig config = new Aas4jClassSetup().config;
74+
final Properties p = config.toProperties();
75+
final File out = new File( args[0] );
76+
final FileOutputStream outputStream = new FileOutputStream( out );
77+
p.store( outputStream, null );
78+
}
79+
80+
/**
81+
* Logic duplicated from {@link ReflectionHelper#hasSubclass(ClassInfo)}
82+
*/
83+
private boolean hasSubclass( final ClassInfo clazzInfo ) {
84+
return !getSubclasses( clazzInfo ).isEmpty();
85+
}
86+
87+
/**
88+
* Logic duplicated from {@link ReflectionHelper#scanModelTypes(ScanResult)}
89+
*/
90+
private Set<Class<?>> scanModelTypes( final ScanResult modelScan ) {
91+
final Set<Class<?>> typesWithModelTypes;
92+
typesWithModelTypes = MODEL_TYPE_SUPERCLASSES.stream()
93+
.flatMap( x -> modelScan.getClassesImplementing( x.getName() )
94+
.loadClasses().stream() )
95+
.collect( Collectors.toSet() );
96+
typesWithModelTypes.addAll( MODEL_TYPE_SUPERCLASSES );
97+
return typesWithModelTypes;
98+
}
99+
100+
/**
101+
* Logic duplicated from {@link ReflectionHelper#scanSubtypes(ScanResult)}
102+
*/
103+
private Map<Class<?>, Set<Class<?>>> scanSubtypes( final ScanResult modelScan ) {
104+
return modelScan.getAllInterfaces().stream()
105+
.filter( this::hasSubclass )
106+
.collect( Collectors.toMap( ClassInfo::loadClass, this::getSubclasses ) );
107+
}
108+
109+
/**
110+
* Logic duplicated from {@link ReflectionHelper#getSubclasses(ClassInfo)}
111+
*/
112+
private Set<Class<?>> getSubclasses( final ClassInfo clazzInfo ) {
113+
return new HashSet<>( clazzInfo.getClassesImplementing()
114+
.directOnly()
115+
.filter( ClassInfo::isInterface )
116+
.loadClasses() );
117+
}
118+
119+
/**
120+
* Logic duplicated from {@link ReflectionHelper#scanMixins(ScanResult, String)}
121+
*/
122+
private Map<Class<?>, Class<?>> scanMixins( final ScanResult modelScan, final String packageName ) {
123+
final ScanResult mixinScan = new ClassGraph()
124+
.enableClassInfo()
125+
.acceptPackagesNonRecursive( packageName )
126+
.scan();
127+
final Map<Class<?>, Class<?>> mixins = new HashMap<>();
128+
mixinScan.getAllClasses()
129+
.filter( x -> x.getSimpleName().endsWith( MIXIN_SUFFIX ) )
130+
.loadClasses()
131+
.forEach( x -> {
132+
final String modelClassName = x.getSimpleName()
133+
.substring( 0, x.getSimpleName().length() - MIXIN_SUFFIX.length() );
134+
final ClassInfoList modelClassInfos = modelScan.getAllClasses().filter(
135+
y -> Objects.equals( y.getSimpleName(), modelClassName ) );
136+
if ( !modelClassInfos.isEmpty() ) {
137+
mixins.put( modelClassInfos.get( 0 ).loadClass(), x );
138+
}
139+
} );
140+
return mixins;
141+
}
142+
143+
/**
144+
* Logic duplicated from {@link ReflectionHelper#scanDefaultImplementations(ScanResult)}
145+
*/
146+
private List<ReflectionHelper.ImplementationInfo> scanDefaultImplementations( final ScanResult modelScan ) {
147+
final ScanResult defaulImplementationScan = new ClassGraph()
148+
.enableClassInfo()
149+
.acceptPackagesNonRecursive( DEFAULT_IMPLEMENTATION_PACKAGE_NAME )
150+
.scan();
151+
final List<ReflectionHelper.ImplementationInfo> defaultImplementations = new ArrayList<>();
152+
defaulImplementationScan.getAllClasses()
153+
.filter( x -> x.getSimpleName().startsWith( DEFAULT_IMPLEMENTATION_PREFIX ) )
154+
.loadClasses()
155+
.forEach( x -> {
156+
final String interfaceName = x.getSimpleName().substring(
157+
DEFAULT_IMPLEMENTATION_PREFIX.length() );// using conventions
158+
final ClassInfoList interfaceClassInfos = modelScan.getAllClasses()
159+
.filter( y -> y.isInterface()
160+
&& Objects.equals(
161+
y.getSimpleName(),
162+
interfaceName ) );
163+
if ( !interfaceClassInfos.isEmpty() ) {
164+
final Class<?> implementedClass = interfaceClassInfos.get( 0 ).loadClass();
165+
defaultImplementations.add( new ImplementationInfo( implementedClass, x ) );
166+
}
167+
} );
168+
return defaultImplementations;
169+
}
170+
171+
/**
172+
* Logic duplicated from {@link ReflectionHelper#scanAasInterfaces()}
173+
*/
174+
private Set<Class> scanAasInterfaces() {
175+
return config.defaultImplementations.stream().map( ReflectionHelper.ImplementationInfo::getInterfaceType )
176+
.collect( Collectors.toSet() );
177+
}
178+
179+
/**
180+
* Logic duplicated from {@link ReflectionHelper#getInterfacesWithoutDefaultImplementation(ScanResult)}
181+
*/
182+
private Set<Class<?>> getInterfacesWithoutDefaultImplementation( final ScanResult modelScan ) {
183+
return modelScan.getAllInterfaces().loadClasses().stream()
184+
.filter( x -> !hasDefaultImplementation( x ) )
185+
.collect( Collectors.toSet() );
186+
}
187+
188+
/**
189+
* Logic duplicated from {@link ReflectionHelper#hasDefaultImplementation(Class)}
190+
*/
191+
public boolean hasDefaultImplementation( final Class<?> interfaceType ) {
192+
return config.defaultImplementations.stream().anyMatch( x -> x.getInterfaceType().equals( interfaceType ) );
193+
}
194+
}

0 commit comments

Comments
 (0)