Skip to content

Commit b86c02c

Browse files
authored
Feature/add generation of async api (#71)
* Add generate async api functionality * Add e2e test collection * Update sdk version to latest one * Fix unit test for jsonschema * Fix graal native image reflection und resource
1 parent 52c6527 commit b86c02c

File tree

9 files changed

+586
-54
lines changed

9 files changed

+586
-54
lines changed

aspect-model-editor-runtime/.graalvm/reflect-config.json

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5634,6 +5634,18 @@
56345634
"boolean",
56355635
"java.util.Optional"
56365636
]
5637+
},
5638+
{
5639+
"name": "asyncApiSpec",
5640+
"parameterTypes": [
5641+
"java.lang.String",
5642+
"java.lang.String",
5643+
"java.lang.String",
5644+
"java.lang.String",
5645+
"java.lang.String",
5646+
"boolean",
5647+
"boolean"
5648+
]
56375649
}
56385650
]
56395651
},
@@ -13411,21 +13423,21 @@
1341113423
},
1341213424
{
1341313425
"name": "sun.awt.X11FontManager",
13414-
"allDeclaredConstructors" : true,
13415-
"allPublicConstructors" : true,
13416-
"allDeclaredMethods" : true,
13417-
"allPublicMethods" : true,
13418-
"allDeclaredFields" : true,
13419-
"allPublicFields" : true
13426+
"allDeclaredConstructors": true,
13427+
"allPublicConstructors": true,
13428+
"allDeclaredMethods": true,
13429+
"allPublicMethods": true,
13430+
"allDeclaredFields": true,
13431+
"allPublicFields": true
1342013432
},
1342113433
{
1342213434
"name": "sun.font.FontConfigManager",
13423-
"allDeclaredConstructors" : true,
13424-
"allPublicConstructors" : true,
13425-
"allDeclaredMethods" : true,
13426-
"allPublicMethods" : true,
13427-
"allDeclaredFields" : true,
13428-
"allPublicFields" : true
13435+
"allDeclaredConstructors": true,
13436+
"allPublicConstructors": true,
13437+
"allDeclaredMethods": true,
13438+
"allPublicMethods": true,
13439+
"allDeclaredFields": true,
13440+
"allPublicFields": true
1342913441
},
1343013442
{
1343113443
"name": "sun.misc.Unsafe",

aspect-model-editor-runtime/.graalvm/resource-config.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@
7373
{
7474
"pattern": "\\Qopenapi/OffsetBasedPaging.json\\E"
7575
},
76+
{
77+
"pattern": "\\Qasyncapi/AsyncApiRootJson.json\\E"
78+
},
7679
{
7780
"pattern": "\\Qcom/eclipsesource/v8/V8.class\\E"
7881
},

aspect-model-editor-runtime/postman/ame.postman_collection.json

Lines changed: 305 additions & 10 deletions
Large diffs are not rendered by default.

aspect-model-editor-service/pom.xml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
~ Copyright (c) 2023 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
44
-->
55

6-
<project xmlns="http://maven.apache.org/POM/4.0.0"
7-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
87
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
98
<modelVersion>4.0.0</modelVersion>
109

@@ -71,6 +70,10 @@
7170
<groupId>com.fasterxml.jackson.dataformat</groupId>
7271
<artifactId>jackson-dataformat-xml</artifactId>
7372
</dependency>
73+
<dependency>
74+
<groupId>com.fasterxml.jackson.core</groupId>
75+
<artifactId>jackson-core</artifactId>
76+
</dependency>
7477

7578
<!-- Third party testing dependencies -->
7679
<dependency>

aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/GenerateService.java

Lines changed: 103 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,52 @@
1616
import java.io.ByteArrayOutputStream;
1717
import java.io.IOException;
1818
import java.nio.charset.StandardCharsets;
19+
import java.nio.file.Path;
20+
import java.util.HashMap;
1921
import java.util.Locale;
22+
import java.util.Map;
2023
import java.util.Optional;
2124

2225
import org.apache.commons.lang3.LocaleUtils;
26+
import org.eclipse.esmf.ame.exceptions.FileHandlingException;
2327
import org.eclipse.esmf.ame.exceptions.GenerationException;
2428
import org.eclipse.esmf.ame.exceptions.InvalidAspectModelException;
2529
import org.eclipse.esmf.ame.resolver.strategy.FileSystemStrategy;
2630
import org.eclipse.esmf.ame.resolver.strategy.utils.ResolverUtils;
31+
import org.eclipse.esmf.ame.services.utils.ZipUtils;
2732
import org.eclipse.esmf.ame.utils.ModelUtils;
2833
import org.eclipse.esmf.aspectmodel.aas.AasFileFormat;
2934
import org.eclipse.esmf.aspectmodel.aas.AspectModelAasGenerator;
35+
import org.eclipse.esmf.aspectmodel.generator.asyncapi.AspectModelAsyncApiGenerator;
36+
import org.eclipse.esmf.aspectmodel.generator.asyncapi.AsyncApiSchemaArtifact;
37+
import org.eclipse.esmf.aspectmodel.generator.asyncapi.AsyncApiSchemaGenerationConfig;
38+
import org.eclipse.esmf.aspectmodel.generator.asyncapi.AsyncApiSchemaGenerationConfigBuilder;
3039
import org.eclipse.esmf.aspectmodel.generator.docu.AspectModelDocumentationGenerator;
3140
import org.eclipse.esmf.aspectmodel.generator.json.AspectModelJsonPayloadGenerator;
3241
import org.eclipse.esmf.aspectmodel.generator.jsonschema.AspectModelJsonSchemaGenerator;
3342
import org.eclipse.esmf.aspectmodel.generator.openapi.AspectModelOpenApiGenerator;
3443
import org.eclipse.esmf.aspectmodel.generator.openapi.PagingOption;
3544
import org.eclipse.esmf.aspectmodel.resolver.services.DataType;
3645
import org.eclipse.esmf.aspectmodel.resolver.services.VersionedModel;
46+
import org.eclipse.esmf.metamodel.Aspect;
3747
import org.eclipse.esmf.metamodel.AspectContext;
3848
import org.slf4j.Logger;
3949
import org.slf4j.LoggerFactory;
4050
import org.springframework.stereotype.Service;
4151

52+
import com.fasterxml.jackson.core.JsonProcessingException;
4253
import com.fasterxml.jackson.databind.JsonNode;
4354
import com.fasterxml.jackson.databind.ObjectMapper;
55+
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
56+
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
4457
import com.google.common.collect.ImmutableMap;
4558

4659
import io.vavr.control.Try;
4760

4861
@Service
4962
public class GenerateService {
5063
private static final Logger LOG = LoggerFactory.getLogger( GenerateService.class );
51-
64+
private static final ObjectMapper YAML_MAPPER = new YAMLMapper().enable( YAMLGenerator.Feature.MINIMIZE_QUOTES );
5265
private static final String COULD_NOT_LOAD_ASPECT = "Could not load Aspect";
5366
private static final String COULD_NOT_LOAD_ASPECT_MODEL = "Could not load Aspect model, please make sure the model is valid.";
5467
public static final String WRONG_RESOURCE_PATH_ID = "The resource path ID and properties ID do not match. Please verify and correct them.";
@@ -90,8 +103,7 @@ public String jsonSchema( final String aspectModel, final String language ) {
90103

91104
public String sampleJSONPayload( final String aspectModel ) {
92105
try {
93-
return new AspectModelJsonPayloadGenerator(
94-
generateAspectContext( aspectModel ) ).generateJson();
106+
return new AspectModelJsonPayloadGenerator( generateAspectContext( aspectModel ) ).generateJson();
95107
} catch ( final IOException e ) {
96108
LOG.error( COULD_NOT_LOAD_ASPECT_MODEL );
97109
throw new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT, e );
@@ -144,30 +156,24 @@ private AspectContext generateAspectContext( final String aspectModel ) {
144156
public String generateYamlOpenApiSpec( final String language, final String aspectModel, final String baseUrl,
145157
final boolean includeQueryApi, final boolean useSemanticVersion, final Optional<PagingOption> pagingOption,
146158
final Optional<String> resourcePath, final Optional<String> yamlProperties ) {
147-
try {
148-
final String ymlOutput = new AspectModelOpenApiGenerator().applyForYaml(
149-
ResolverUtils.resolveAspectFromModel( aspectModel ),
150-
useSemanticVersion, baseUrl, resourcePath, yamlProperties, includeQueryApi, pagingOption,
151-
Locale.forLanguageTag( language ) );
152-
153-
if ( ymlOutput.equals( "--- {}\n" ) ) {
154-
throw new GenerationException( WRONG_RESOURCE_PATH_ID );
155-
}
159+
final String ymlOutput = new AspectModelOpenApiGenerator().applyForYaml(
160+
ResolverUtils.resolveAspectFromModel( aspectModel ), useSemanticVersion, baseUrl, resourcePath,
161+
yamlProperties, includeQueryApi, pagingOption, Locale.forLanguageTag( language ) );
156162

157-
return ymlOutput;
158-
} catch ( final IOException e ) {
159-
LOG.error( "YAML OpenAPI specification could not be generated." );
160-
throw new InvalidAspectModelException( "Error generating YAML OpenAPI specification", e );
163+
if ( ymlOutput.equals( "--- {}\n" ) ) {
164+
throw new GenerationException( WRONG_RESOURCE_PATH_ID );
161165
}
166+
167+
return ymlOutput;
162168
}
163169

164170
public String generateJsonOpenApiSpec( final String language, final String aspectModel, final String baseUrl,
165171
final boolean includeQueryApi, final boolean useSemanticVersion, final Optional<PagingOption> pagingOption,
166172
final Optional<String> resourcePath, final Optional<JsonNode> jsonProperties ) {
167173
try {
168174
final JsonNode json = new AspectModelOpenApiGenerator().applyForJson(
169-
ResolverUtils.resolveAspectFromModel( aspectModel ), useSemanticVersion, baseUrl,
170-
resourcePath, jsonProperties, includeQueryApi, pagingOption, LocaleUtils.toLocale( language ) );
175+
ResolverUtils.resolveAspectFromModel( aspectModel ), useSemanticVersion, baseUrl, resourcePath,
176+
jsonProperties, includeQueryApi, pagingOption, LocaleUtils.toLocale( language ) );
171177

172178
final ByteArrayOutputStream out = new ByteArrayOutputStream();
173179
final ObjectMapper objectMapper = new ObjectMapper();
@@ -186,4 +192,83 @@ public String generateJsonOpenApiSpec( final String language, final String aspec
186192
throw new InvalidAspectModelException( "Error generating JSON OpenAPI specification", e );
187193
}
188194
}
195+
196+
public byte[] generateAsyncApiSpec( final String aspectModel, final String language, final String output,
197+
final String applicationId, final String channelAddress, final boolean useSemanticVersion,
198+
final boolean writeSeparateFiles ) {
199+
final AspectModelAsyncApiGenerator generator = new AspectModelAsyncApiGenerator();
200+
final AsyncApiSchemaGenerationConfig config = buildAsyncApiSchemaGenerationConfig( applicationId, channelAddress,
201+
useSemanticVersion, language );
202+
203+
final Aspect aspect = ResolverUtils.resolveAspectFromModel( aspectModel );
204+
final AsyncApiSchemaArtifact asyncApiSpec = generator.apply( aspect, config );
205+
206+
if ( writeSeparateFiles ) {
207+
return generateZipFile( asyncApiSpec, output );
208+
}
209+
210+
return generateSingleFile( asyncApiSpec, output );
211+
}
212+
213+
private AsyncApiSchemaGenerationConfig buildAsyncApiSchemaGenerationConfig( final String applicationId,
214+
final String channelAddress, final boolean useSemanticVersion, final String language ) {
215+
return AsyncApiSchemaGenerationConfigBuilder.builder().useSemanticVersion( useSemanticVersion )
216+
.applicationId( applicationId ).channelAddress( channelAddress )
217+
.locale( LocaleUtils.toLocale( language ) ).build();
218+
}
219+
220+
private byte[] generateZipFile( final AsyncApiSchemaArtifact asyncApiSpec, final String output ) {
221+
if ( output.equals( "json" ) ) {
222+
return jsonZip( asyncApiSpec.getContentWithSeparateSchemasAsJson() );
223+
}
224+
225+
return yamlZip( asyncApiSpec.getContentWithSeparateSchemasAsYaml() );
226+
}
227+
228+
private byte[] jsonZip( final Map<Path, JsonNode> separateFilesContent ) {
229+
final ObjectMapper objectMapper = new ObjectMapper();
230+
final Map<Path, byte[]> content = new HashMap<>();
231+
232+
for ( final Map.Entry<Path, JsonNode> entry : separateFilesContent.entrySet() ) {
233+
try {
234+
final byte[] bytes = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsBytes( entry.getValue() );
235+
content.put( entry.getKey(), bytes );
236+
} catch ( final JsonProcessingException e ) {
237+
LOG.error( "Failed to convert JSON to bytes.", e );
238+
throw new FileHandlingException( "Failed to get JSON async api.", e );
239+
}
240+
}
241+
242+
return ZipUtils.createAsyncApiPackage( content );
243+
}
244+
245+
private byte[] yamlZip( final Map<Path, String> separateFilesContent ) {
246+
final Map<Path, byte[]> content = new HashMap<>();
247+
248+
for ( final Map.Entry<Path, String> entry : separateFilesContent.entrySet() ) {
249+
final byte[] bytes = entry.getValue().getBytes( StandardCharsets.UTF_8 );
250+
content.put( entry.getKey(), bytes );
251+
}
252+
253+
return ZipUtils.createAsyncApiPackage( content );
254+
}
255+
256+
private byte[] generateSingleFile( final AsyncApiSchemaArtifact asyncApiSpec, final String output ) {
257+
final JsonNode json = asyncApiSpec.getContent();
258+
259+
if ( output.equals( "yaml" ) ) {
260+
return jsonToYaml( json ).getBytes( StandardCharsets.UTF_8 );
261+
}
262+
263+
return json.toString().getBytes( StandardCharsets.UTF_8 );
264+
}
265+
266+
private String jsonToYaml( final JsonNode json ) {
267+
try {
268+
return YAML_MAPPER.writeValueAsString( json );
269+
} catch ( final JsonProcessingException e ) {
270+
LOG.error( "JSON could not be converted to YAML", e );
271+
throw new FileHandlingException( "Failed to get YAML async api.", e );
272+
}
273+
}
189274
}

aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/utils/ZipUtils.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.FileOutputStream;
2121
import java.io.IOException;
2222
import java.nio.file.Files;
23+
import java.nio.file.Path;
2324
import java.nio.file.attribute.BasicFileAttributes;
2425
import java.util.ArrayList;
2526
import java.util.HashSet;
@@ -43,14 +44,35 @@ private ZipUtils() {
4344

4445
static final int BUFFER = 1024;
4546

47+
public static byte[] createAsyncApiPackage( final Map<Path, byte[]> content ) {
48+
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
49+
50+
try ( final ZipOutputStream zos = new ZipOutputStream( outputStream ) ) {
51+
for ( final Map.Entry<Path, byte[]> entry : content.entrySet() ) {
52+
final ZipEntry zipEntry = new ZipEntry( entry.getKey().toString() );
53+
zos.putNextEntry( zipEntry );
54+
55+
final byte[] bytes = entry.getValue();
56+
zos.write( bytes, 0, bytes.length );
57+
58+
zos.closeEntry();
59+
}
60+
} catch ( final IOException e ) {
61+
LOG.error( "Failed to create the asynchronous API ZIP file." );
62+
throw new CreateFileException( "An error occurred while creating the ZIP file for the async API.", e );
63+
}
64+
65+
return outputStream.toByteArray();
66+
}
67+
4668
public static byte[] createPackageFromCache( final Map<String, String> exportCache ) throws IOException {
47-
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
69+
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
4870

49-
try ( ZipOutputStream zos = new ZipOutputStream( outputStream ) ) {
50-
Set<String> zipFolderSet = new HashSet<>();
51-
Set<String> zipVersionedNamespaceSet = new HashSet<>();
71+
try ( final ZipOutputStream zos = new ZipOutputStream( outputStream ) ) {
72+
final Set<String> zipFolderSet = new HashSet<>();
73+
final Set<String> zipVersionedNamespaceSet = new HashSet<>();
5274

53-
for ( Map.Entry<String, String> entry : exportCache.entrySet() ) {
75+
for ( final Map.Entry<String, String> entry : exportCache.entrySet() ) {
5476
final String[] fileStructure = entry.getKey().split( ":" );
5577
final String aspectModel = entry.getValue();
5678

@@ -85,7 +107,8 @@ public static void createPackageFromWorkspace( final String zipFileName, final S
85107
final String storagePath ) throws IOException {
86108
final String zipFile = aspectModelPath + File.separator + zipFileName;
87109

88-
try ( FileOutputStream fos = new FileOutputStream( zipFile ); ZipOutputStream zos = new ZipOutputStream( fos ) ) {
110+
try ( final FileOutputStream fos = new FileOutputStream( zipFile );
111+
final ZipOutputStream zos = new ZipOutputStream( fos ) ) {
89112

90113
final List<File> fileList = getFileList( new File( storagePath ), new ArrayList<>(), storagePath );
91114

0 commit comments

Comments
 (0)