Skip to content

Commit bc982b9

Browse files
authored
Feature/generate open api specification (#8)
1 parent b94e1b9 commit bc982b9

File tree

9 files changed

+531
-81
lines changed

9 files changed

+531
-81
lines changed

.graalvm/reflect-config.json

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -334,11 +334,22 @@
334334
]
335335
},
336336
{
337-
"name" : "java.lang.Class",
338-
"methods" : [
339-
{ "name" : "getSimpleName", "parameterTypes" : [] },
340-
{ "name" : "getInterfaces", "parameterTypes" : [] },
341-
{ "name" : "getInterfaces", "parameterTypes" : ["java.lang.boolean"] }
337+
"name": "java.lang.Class",
338+
"methods": [
339+
{
340+
"name": "getSimpleName",
341+
"parameterTypes": []
342+
},
343+
{
344+
"name": "getInterfaces",
345+
"parameterTypes": []
346+
},
347+
{
348+
"name": "getInterfaces",
349+
"parameterTypes": [
350+
"java.lang.boolean"
351+
]
352+
}
342353
]
343354
},
344355
{
@@ -1575,4 +1586,4 @@
15751586
"allDeclaredClasses": true,
15761587
"allPublicClasses": true
15771588
}
1578-
]
1589+
]

.graalvm/resource-config.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,24 @@
7171
},
7272
{
7373
"pattern": "\\Qorg/apache/batik/transcoder/Transcoder.class\\E"
74+
},
75+
{
76+
"pattern": "\\Qopenapi/OpenApiRootJson.json\\E"
77+
},
78+
{
79+
"pattern": "\\Qopenapi/CursorBasedPaging.json\\E"
80+
},
81+
{
82+
"pattern": "\\Qopenapi/Filter.json\\E"
83+
},
84+
{
85+
"pattern": "\\Qopenapi/JsonRPC.json\\E"
86+
},
87+
{
88+
"pattern": "\\Qopenapi/OffsetBasedPaging.json\\E"
89+
},
90+
{
91+
"pattern": "\\Qopenapi/TimeBasedPaging.json\\E"
7492
}
7593
]
7694
}

postman/ame.postman_collection.json

Lines changed: 271 additions & 11 deletions
Large diffs are not rendered by default.

src/main/java/io/openmanufacturing/ame/exceptions/ResponseExceptionHandler.java

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
3131

3232
import io.openmanufacturing.ame.exceptions.model.ErrorResponse;
33+
import io.openmanufacturing.sds.metamodel.loader.AspectLoadingException;
3334

3435
/**
3536
* Provides custom exception handling for the REST API.
@@ -103,22 +104,35 @@ public ResponseEntity<Object> handleInvalidStateTransitionException( final WebRe
103104
return error( HttpStatus.BAD_REQUEST, request, e, e.getMessage() );
104105
}
105106

107+
/**
108+
* Method for handling exception of type {@link AspectLoadingException}
109+
*
110+
* @param request the Http request
111+
* @param e the exception which occurred
112+
* @return the custom {@link ErrorResponse} as {@link ResponseEntity} for the exception
113+
*/
114+
@ExceptionHandler( AspectLoadingException.class )
115+
public ResponseEntity<Object> handleAspectLoadingException( final WebRequest request,
116+
final AspectLoadingException e ) {
117+
return error( HttpStatus.UNPROCESSABLE_ENTITY, request, e, e.getMessage() );
118+
}
119+
106120
/**
107121
* Method for handling exception of type {@link InvalidAspectModelException}
108122
*
109123
* @param request the Http request
110-
* @param e the exception which occurred
124+
* @param e the exception which occurred
111125
* @return the custom {@link ErrorResponse} as {@link ResponseEntity} for the exception
112126
*/
113-
@ExceptionHandler(InvalidAspectModelException.class)
114-
public ResponseEntity<Object> handleInvalidAspectModelException(final WebRequest request,
115-
final InvalidAspectModelException e) {
116-
return error(HttpStatus.BAD_REQUEST, request, e, e.getMessage());
127+
@ExceptionHandler( InvalidAspectModelException.class )
128+
public ResponseEntity<Object> handleInvalidAspectModelException( final WebRequest request,
129+
final InvalidAspectModelException e ) {
130+
return error( HttpStatus.BAD_REQUEST, request, e, e.getMessage() );
117131
}
118132

119133
private ResponseEntity<Object> error( final HttpStatus responseCode, final WebRequest request,
120134
final RuntimeException e, final String message ) {
121-
logRequest(request, e, responseCode);
135+
logRequest( request, e, responseCode );
122136

123137
final ErrorResponse errorResponse = new ErrorResponse( message,
124138
((ServletWebRequest) request).getRequest().getRequestURI(), responseCode.value() );
@@ -138,7 +152,8 @@ protected static void logRequest( final WebRequest request, final Throwable ex,
138152
}
139153
}
140154

141-
private static String getLogRequestMessage( final WebRequest request, final Throwable ex, final HttpStatus httpStatus ) {
155+
private static String getLogRequestMessage( final WebRequest request, final Throwable ex,
156+
final HttpStatus httpStatus ) {
142157
final HttpServletRequest servletRequest = ((ServletWebRequest) request).getRequest();
143158
return servletRequest.getQueryString() == null ?
144159
getLogRequestMessage( servletRequest.getRequestURI(), ex, httpStatus ) :

src/main/java/io/openmanufacturing/ame/services/GenerateService.java

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import io.openmanufacturing.sds.aspectmodel.generator.docu.AspectModelDocumentationGenerator;
3232
import io.openmanufacturing.sds.aspectmodel.generator.json.AspectModelJsonPayloadGenerator;
3333
import io.openmanufacturing.sds.aspectmodel.generator.jsonschema.AspectModelJsonSchemaGenerator;
34+
import io.openmanufacturing.sds.aspectmodel.generator.openapi.AspectModelOpenApiGenerator;
35+
import io.openmanufacturing.sds.aspectmodel.generator.openapi.PagingOption;
3436
import io.openmanufacturing.sds.aspectmodel.resolver.services.DataType;
3537
import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;
3638
import io.openmanufacturing.sds.metamodel.Aspect;
@@ -41,15 +43,15 @@
4143
@Service
4244
public class GenerateService {
4345
private static final Logger LOG = LoggerFactory.getLogger( GenerateService.class );
44-
46+
private static final String COULD_NOT_LOAD_ASPECT = "Could not load Aspect";
4547
private static final String COULD_NOT_LOAD_ASPECT_MODEL = "Could not load Aspect model, please make sure the model is valid.";
4648

4749
public GenerateService() {
4850
DataType.setupTypeMapping();
4951
}
5052

51-
public byte[] generateHtmlDocument( final String aspectModel, final Optional<String> storagePath )
52-
throws IOException {
53+
public byte[] generateHtmlDocument( final String aspectModel ) throws IOException {
54+
final Optional<String> storagePath = Optional.of( ApplicationSettings.getMetaModelStoragePath() );
5355
final AspectModelDocumentationGenerator generator = new AspectModelDocumentationGenerator(
5456
getVersionModel( aspectModel, storagePath ).get() );
5557
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
@@ -60,15 +62,15 @@ public byte[] generateHtmlDocument( final String aspectModel, final Optional<Str
6062
return byteArrayOutputStream.toByteArray();
6163
}
6264

63-
public String jsonSchema( final String aspectModel, final Optional<String> storagePath ) {
65+
public String jsonSchema( final String aspectModel ) {
6466
try {
65-
final Aspect aspect = AspectModelLoader
66-
.fromVersionedModel( getVersionModel( aspectModel, storagePath ).get() )
67-
.getOrElseThrow( e -> {
68-
LOG.error( COULD_NOT_LOAD_ASPECT_MODEL );
69-
return new InvalidAspectModelException(
70-
COULD_NOT_LOAD_ASPECT_MODEL, e );
71-
} );
67+
final Optional<String> storagePath = Optional.of( ApplicationSettings.getMetaModelStoragePath() );
68+
final Aspect aspect = AspectModelLoader.fromVersionedModel( getVersionModel( aspectModel, storagePath ).get() )
69+
.getOrElseThrow( e -> {
70+
LOG.error( COULD_NOT_LOAD_ASPECT_MODEL );
71+
return new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT_MODEL,
72+
e );
73+
} );
7274

7375
final AspectModelJsonSchemaGenerator generator = new AspectModelJsonSchemaGenerator();
7476
final JsonNode jsonSchema = generator.apply( aspect );
@@ -81,28 +83,61 @@ public String jsonSchema( final String aspectModel, final Optional<String> stora
8183
return out.toString();
8284
} catch ( final IOException e ) {
8385
LOG.error( "Aspect Model could not be loaded correctly." );
84-
throw new InvalidAspectModelException( "Could not load Aspect", e );
86+
throw new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT, e );
8587
}
8688
}
8789

88-
public String sampleJSONPayload( final String aspectModel, final Optional<String> storagePath ) {
90+
public String sampleJSONPayload( final String aspectModel ) {
8991
try {
92+
final Optional<String> storagePath = Optional.of( ApplicationSettings.getMetaModelStoragePath() );
9093
final AspectModelJsonPayloadGenerator generator = new AspectModelJsonPayloadGenerator(
9194
getVersionModel( aspectModel, storagePath ).get() );
9295

9396
return generator.generateJson();
9497
} catch ( final AspectLoadingException e ) {
9598
LOG.error( COULD_NOT_LOAD_ASPECT_MODEL );
96-
throw new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT_MODEL,
97-
e );
99+
throw new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT_MODEL, e );
98100
} catch ( final IOException e ) {
99101
LOG.error( "Aspect Model could not be loaded correctly." );
100-
throw new InvalidAspectModelException( "Could not load Aspect", e );
102+
throw new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT, e );
101103
}
102104
}
103105

104106
private Try<VersionedModel> getVersionModel( final String aspectModel, final Optional<String> storagePath ) {
105107
return ModelUtils.fetchVersionModel( aspectModel,
106108
storagePath.orElse( ApplicationSettings.getMetaModelStoragePath() ) );
107109
}
110+
111+
public String generateYamlOpenApiSpec( final String aspectModel, final String baseUrl, final boolean includeQueryApi,
112+
final boolean useSemanticVersion, final Optional<PagingOption> pagingOption ) {
113+
try {
114+
final AspectModelOpenApiGenerator generator = new AspectModelOpenApiGenerator();
115+
116+
return generator.applyForYaml( ModelUtils.resolveAspectFromModel( aspectModel ), useSemanticVersion, baseUrl,
117+
Optional.empty(), Optional.empty(), includeQueryApi, pagingOption );
118+
} catch ( final IOException e ) {
119+
LOG.error( "YAML OpenAPI specification could not be generated." );
120+
throw new InvalidAspectModelException( "Error generating YAML OpenAPI specification", e );
121+
}
122+
}
123+
124+
public String generateJsonOpenApiSpec( final String aspectModel, final String baseUrl,
125+
final boolean includeQueryApi, final boolean useSemanticVersion, final Optional<PagingOption> pagingOption ) {
126+
try {
127+
final AspectModelOpenApiGenerator generator = new AspectModelOpenApiGenerator();
128+
129+
final JsonNode json = generator.applyForJson( ModelUtils.resolveAspectFromModel( aspectModel ),
130+
useSemanticVersion, baseUrl, Optional.empty(), Optional.empty(), includeQueryApi, pagingOption );
131+
132+
final ByteArrayOutputStream out = new ByteArrayOutputStream();
133+
final ObjectMapper objectMapper = new ObjectMapper();
134+
135+
objectMapper.writerWithDefaultPrettyPrinter().writeValue( out, json );
136+
137+
return out.toString();
138+
} catch ( final IOException e ) {
139+
LOG.error( "JSON OpenAPI specification could not be generated." );
140+
throw new InvalidAspectModelException( "Error generating JSON OpenAPI specification", e );
141+
}
142+
}
108143
}

src/main/java/io/openmanufacturing/ame/services/ModelService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public Namespaces migrateWorkspace( final String storagePath ) {
114114
}
115115

116116
private Try<VersionedModel> updateModelVersion( final File inputFile ) {
117-
return ModelUtils.loadButNotResolveModel( inputFile ).flatMap( new MigratorService()::updateMetaModelVersion );
117+
return ModelUtils.loadModelFromFile( inputFile ).flatMap( new MigratorService()::updateMetaModelVersion );
118118
}
119119

120120
private Namespace resolveNamespace( final List<Namespace> namespaces, final AspectModelUrn aspectModelUrn ) {

src/main/java/io/openmanufacturing/ame/services/utils/ModelUtils.java

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
import java.util.List;
2525
import java.util.regex.Pattern;
2626

27+
import org.apache.jena.rdf.model.Model;
2728
import org.apache.jena.riot.RiotException;
2829

30+
import io.openmanufacturing.ame.config.ApplicationSettings;
2931
import io.openmanufacturing.ame.exceptions.InvalidAspectModelException;
3032
import io.openmanufacturing.ame.exceptions.UrnNotFoundException;
3133
import io.openmanufacturing.ame.resolver.inmemory.InMemoryStrategy;
@@ -40,19 +42,19 @@
4042
import io.openmanufacturing.sds.aspectmodel.validation.report.ValidationReportBuilder;
4143
import io.openmanufacturing.sds.aspectmodel.validation.services.AspectModelValidator;
4244
import io.openmanufacturing.sds.aspectmodel.versionupdate.MigratorService;
45+
import io.openmanufacturing.sds.metamodel.Aspect;
46+
import io.openmanufacturing.sds.metamodel.loader.AspectModelLoader;
4347
import io.vavr.control.Try;
48+
import lombok.NoArgsConstructor;
4449

50+
@NoArgsConstructor
4551
public class ModelUtils {
4652

4753
public static final String TTL = "ttl";
4854
public static final String TTL_EXTENSION = "." + TTL;
4955

5056
public final static Pattern URN_PATTERN = Pattern.compile(
51-
"^urn:[a-z0-9][a-z0-9-]{0,31}:([a-z0-9()+,\\-.:=@;$_!*#']|%[0-9a-f]{2})+$",
52-
Pattern.CASE_INSENSITIVE );
53-
54-
private ModelUtils() {
55-
}
57+
"^urn:[a-z0-9][a-z0-9-]{0,31}:([a-z0-9()+,\\-.:=@;$_!*#']|%[0-9a-f]{2})+$", Pattern.CASE_INSENSITIVE );
5658

5759
/**
5860
* This Method is used to create an in memory strategy for the given Aspect Model.
@@ -92,34 +94,70 @@ public static Try<VersionedModel> fetchVersionModel( final String aspectModel, f
9294
return new AspectModelResolver().resolveAspectModel( inMemoryStrategy, inMemoryStrategy.getAspectModelUrn() );
9395
}
9496

95-
public static Try<VersionedModel> loadButNotResolveModel( final File inputFile ) {
96-
try ( final InputStream inputStream = new FileInputStream( inputFile ) ) {
97-
final SdsAspectMetaModelResourceResolver metaModelResourceResolver = new SdsAspectMetaModelResourceResolver();
98-
return TurtleLoader.loadTurtle( inputStream ).flatMap( model ->
99-
metaModelResourceResolver.getBammVersion( model ).flatMap( metaModelVersion ->
100-
metaModelResourceResolver.mergeMetaModelIntoRawModel( model, metaModelVersion ) ) );
97+
/**
98+
* Migrates a model to its latest version.
99+
*
100+
* @param aspectModel as a string.
101+
* @param storagePath stored path to the Aspect Models.
102+
* @return migrated Aspect Model as a string.
103+
*/
104+
public static String migrateModel( final String aspectModel, final String storagePath ) {
105+
final InMemoryStrategy inMemoryStrategy = ModelUtils.inMemoryStrategy( aspectModel, storagePath );
106+
107+
final Try<VersionedModel> migratedFile = LoadModelFromStoragePath( inMemoryStrategy ).flatMap(
108+
new MigratorService()::updateMetaModelVersion );
109+
110+
final VersionedModel versionedModel = migratedFile.getOrElseThrow(
111+
e -> new InvalidAspectModelException( "Aspect Model cannot be migrated.", e ) );
112+
113+
return getPrettyPrintedVersionedModel( versionedModel, inMemoryStrategy.getAspectModelUrn().getUrn() );
114+
}
115+
116+
/**
117+
* Creates an Aspect instance from an Aspect Model.
118+
*
119+
* @param aspectModel as a string.
120+
* @return the Aspect as an object.
121+
*/
122+
public static Aspect resolveAspectFromModel( final String aspectModel ) {
123+
final InMemoryStrategy inMemoryStrategy = ModelUtils.inMemoryStrategy( aspectModel,
124+
ApplicationSettings.getMetaModelStoragePath() );
125+
126+
final VersionedModel versionedModel = ModelUtils.LoadModelFromStoragePath( inMemoryStrategy ).getOrElseThrow(
127+
e -> new InvalidAspectModelException( "Cannot resolve Aspect Model.", e ) );
128+
129+
return AspectModelLoader.fromVersionedModelUnchecked( versionedModel );
130+
}
131+
132+
/**
133+
* Load Aspect Model from storage path.
134+
*
135+
* @param inMemoryStrategy for the given storage path.
136+
* @return the resulting {@link VersionedModel} that corresponds to the input Aspect model.
137+
*/
138+
public static Try<VersionedModel> LoadModelFromStoragePath( final InMemoryStrategy inMemoryStrategy ) {
139+
return resolveModel( inMemoryStrategy.model );
140+
}
141+
142+
/**
143+
* Loading the Aspect Model from input file.
144+
*
145+
* @param file Aspect Model as a file.
146+
* @return the resulting {@link VersionedModel} that corresponds to the input Aspect model.
147+
*/
148+
public static Try<VersionedModel> loadModelFromFile( final File file ) {
149+
try ( final InputStream inputStream = new FileInputStream( file ) ) {
150+
return TurtleLoader.loadTurtle( inputStream ).flatMap( ModelUtils::resolveModel );
101151
} catch ( final IOException exception ) {
102152
return Try.failure( exception );
103153
}
104154
}
105155

106-
public static String migrateModel( final String aspectModel, final String storagePath ) {
107-
final InMemoryStrategy inMemoryStrategy = inMemoryStrategy( aspectModel, storagePath );
108-
156+
private static Try<VersionedModel> resolveModel( final Model model ) {
109157
final SdsAspectMetaModelResourceResolver metaModelResourceResolver = new SdsAspectMetaModelResourceResolver();
110158

111-
final Try<VersionedModel> migratedFile = metaModelResourceResolver.getBammVersion( inMemoryStrategy.model )
112-
.flatMap( metaModelVersion ->
113-
metaModelResourceResolver.mergeMetaModelIntoRawModel(
114-
inMemoryStrategy.model,
115-
metaModelVersion ) )
116-
.flatMap(
117-
new MigratorService()::updateMetaModelVersion );
118-
119-
final VersionedModel versionedModel = migratedFile.getOrElseThrow(
120-
e -> new InvalidAspectModelException( "AspectModel cannot be migrated.", e ) );
121-
122-
return getPrettyPrintedVersionedModel( versionedModel, inMemoryStrategy.getAspectModelUrn().getUrn() );
159+
return metaModelResourceResolver.getBammVersion( model ).flatMap(
160+
metaModelVersion -> metaModelResourceResolver.mergeMetaModelIntoRawModel( model, metaModelVersion ) );
123161
}
124162

125163
/**
@@ -163,16 +201,14 @@ public static ValidationReport validateModel( final String aspectModel, final St
163201
}
164202

165203
private static ValidationReport buildValidationSyntacticReport( final RiotException riotException ) {
166-
return new ValidationReportBuilder()
167-
.withValidationErrors( List.of( new ValidationError.Syntactic( riotException ) ) )
168-
.buildInvalidReport();
204+
return new ValidationReportBuilder().withValidationErrors(
205+
List.of( new ValidationError.Syntactic( riotException ) ) ).buildInvalidReport();
169206
}
170207

171208
private static ValidationReport buildValidationSemanticReport( final String message, final String focusNode,
172209
final String resultPath, final String Severity, final String value ) {
173-
return new ValidationReportBuilder()
174-
.withValidationErrors( List
175-
.of( new ValidationError.Semantic( message, focusNode, resultPath, Severity, value ) ) )
176-
.buildInvalidReport();
210+
final ValidationError.Semantic semantic = new ValidationError.Semantic( message, focusNode, resultPath, Severity,
211+
value );
212+
return new ValidationReportBuilder().withValidationErrors( List.of( semantic ) ).buildInvalidReport();
177213
}
178214
}

0 commit comments

Comments
 (0)