Skip to content

Commit

Permalink
Feature/generate open api specification (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
michelu89 authored Nov 9, 2022
1 parent b94e1b9 commit bc982b9
Show file tree
Hide file tree
Showing 9 changed files with 531 additions and 81 deletions.
23 changes: 17 additions & 6 deletions .graalvm/reflect-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,22 @@
]
},
{
"name" : "java.lang.Class",
"methods" : [
{ "name" : "getSimpleName", "parameterTypes" : [] },
{ "name" : "getInterfaces", "parameterTypes" : [] },
{ "name" : "getInterfaces", "parameterTypes" : ["java.lang.boolean"] }
"name": "java.lang.Class",
"methods": [
{
"name": "getSimpleName",
"parameterTypes": []
},
{
"name": "getInterfaces",
"parameterTypes": []
},
{
"name": "getInterfaces",
"parameterTypes": [
"java.lang.boolean"
]
}
]
},
{
Expand Down Expand Up @@ -1575,4 +1586,4 @@
"allDeclaredClasses": true,
"allPublicClasses": true
}
]
]
18 changes: 18 additions & 0 deletions .graalvm/resource-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,24 @@
},
{
"pattern": "\\Qorg/apache/batik/transcoder/Transcoder.class\\E"
},
{
"pattern": "\\Qopenapi/OpenApiRootJson.json\\E"
},
{
"pattern": "\\Qopenapi/CursorBasedPaging.json\\E"
},
{
"pattern": "\\Qopenapi/Filter.json\\E"
},
{
"pattern": "\\Qopenapi/JsonRPC.json\\E"
},
{
"pattern": "\\Qopenapi/OffsetBasedPaging.json\\E"
},
{
"pattern": "\\Qopenapi/TimeBasedPaging.json\\E"
}
]
}
282 changes: 271 additions & 11 deletions postman/ame.postman_collection.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import io.openmanufacturing.ame.exceptions.model.ErrorResponse;
import io.openmanufacturing.sds.metamodel.loader.AspectLoadingException;

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

/**
* Method for handling exception of type {@link AspectLoadingException}
*
* @param request the Http request
* @param e the exception which occurred
* @return the custom {@link ErrorResponse} as {@link ResponseEntity} for the exception
*/
@ExceptionHandler( AspectLoadingException.class )
public ResponseEntity<Object> handleAspectLoadingException( final WebRequest request,
final AspectLoadingException e ) {
return error( HttpStatus.UNPROCESSABLE_ENTITY, request, e, e.getMessage() );
}

/**
* Method for handling exception of type {@link InvalidAspectModelException}
*
* @param request the Http request
* @param e the exception which occurred
* @param e the exception which occurred
* @return the custom {@link ErrorResponse} as {@link ResponseEntity} for the exception
*/
@ExceptionHandler(InvalidAspectModelException.class)
public ResponseEntity<Object> handleInvalidAspectModelException(final WebRequest request,
final InvalidAspectModelException e) {
return error(HttpStatus.BAD_REQUEST, request, e, e.getMessage());
@ExceptionHandler( InvalidAspectModelException.class )
public ResponseEntity<Object> handleInvalidAspectModelException( final WebRequest request,
final InvalidAspectModelException e ) {
return error( HttpStatus.BAD_REQUEST, request, e, e.getMessage() );
}

private ResponseEntity<Object> error( final HttpStatus responseCode, final WebRequest request,
final RuntimeException e, final String message ) {
logRequest(request, e, responseCode);
logRequest( request, e, responseCode );

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

private static String getLogRequestMessage( final WebRequest request, final Throwable ex, final HttpStatus httpStatus ) {
private static String getLogRequestMessage( final WebRequest request, final Throwable ex,
final HttpStatus httpStatus ) {
final HttpServletRequest servletRequest = ((ServletWebRequest) request).getRequest();
return servletRequest.getQueryString() == null ?
getLogRequestMessage( servletRequest.getRequestURI(), ex, httpStatus ) :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import io.openmanufacturing.sds.aspectmodel.generator.docu.AspectModelDocumentationGenerator;
import io.openmanufacturing.sds.aspectmodel.generator.json.AspectModelJsonPayloadGenerator;
import io.openmanufacturing.sds.aspectmodel.generator.jsonschema.AspectModelJsonSchemaGenerator;
import io.openmanufacturing.sds.aspectmodel.generator.openapi.AspectModelOpenApiGenerator;
import io.openmanufacturing.sds.aspectmodel.generator.openapi.PagingOption;
import io.openmanufacturing.sds.aspectmodel.resolver.services.DataType;
import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;
import io.openmanufacturing.sds.metamodel.Aspect;
Expand All @@ -41,15 +43,15 @@
@Service
public class GenerateService {
private static final Logger LOG = LoggerFactory.getLogger( GenerateService.class );

private static final String COULD_NOT_LOAD_ASPECT = "Could not load Aspect";
private static final String COULD_NOT_LOAD_ASPECT_MODEL = "Could not load Aspect model, please make sure the model is valid.";

public GenerateService() {
DataType.setupTypeMapping();
}

public byte[] generateHtmlDocument( final String aspectModel, final Optional<String> storagePath )
throws IOException {
public byte[] generateHtmlDocument( final String aspectModel ) throws IOException {
final Optional<String> storagePath = Optional.of( ApplicationSettings.getMetaModelStoragePath() );
final AspectModelDocumentationGenerator generator = new AspectModelDocumentationGenerator(
getVersionModel( aspectModel, storagePath ).get() );
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Expand All @@ -60,15 +62,15 @@ public byte[] generateHtmlDocument( final String aspectModel, final Optional<Str
return byteArrayOutputStream.toByteArray();
}

public String jsonSchema( final String aspectModel, final Optional<String> storagePath ) {
public String jsonSchema( final String aspectModel ) {
try {
final Aspect aspect = AspectModelLoader
.fromVersionedModel( getVersionModel( aspectModel, storagePath ).get() )
.getOrElseThrow( e -> {
LOG.error( COULD_NOT_LOAD_ASPECT_MODEL );
return new InvalidAspectModelException(
COULD_NOT_LOAD_ASPECT_MODEL, e );
} );
final Optional<String> storagePath = Optional.of( ApplicationSettings.getMetaModelStoragePath() );
final Aspect aspect = AspectModelLoader.fromVersionedModel( getVersionModel( aspectModel, storagePath ).get() )
.getOrElseThrow( e -> {
LOG.error( COULD_NOT_LOAD_ASPECT_MODEL );
return new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT_MODEL,
e );
} );

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

public String sampleJSONPayload( final String aspectModel, final Optional<String> storagePath ) {
public String sampleJSONPayload( final String aspectModel ) {
try {
final Optional<String> storagePath = Optional.of( ApplicationSettings.getMetaModelStoragePath() );
final AspectModelJsonPayloadGenerator generator = new AspectModelJsonPayloadGenerator(
getVersionModel( aspectModel, storagePath ).get() );

return generator.generateJson();
} catch ( final AspectLoadingException e ) {
LOG.error( COULD_NOT_LOAD_ASPECT_MODEL );
throw new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT_MODEL,
e );
throw new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT_MODEL, e );
} catch ( final IOException e ) {
LOG.error( "Aspect Model could not be loaded correctly." );
throw new InvalidAspectModelException( "Could not load Aspect", e );
throw new InvalidAspectModelException( COULD_NOT_LOAD_ASPECT, e );
}
}

private Try<VersionedModel> getVersionModel( final String aspectModel, final Optional<String> storagePath ) {
return ModelUtils.fetchVersionModel( aspectModel,
storagePath.orElse( ApplicationSettings.getMetaModelStoragePath() ) );
}

public String generateYamlOpenApiSpec( final String aspectModel, final String baseUrl, final boolean includeQueryApi,
final boolean useSemanticVersion, final Optional<PagingOption> pagingOption ) {
try {
final AspectModelOpenApiGenerator generator = new AspectModelOpenApiGenerator();

return generator.applyForYaml( ModelUtils.resolveAspectFromModel( aspectModel ), useSemanticVersion, baseUrl,
Optional.empty(), Optional.empty(), includeQueryApi, pagingOption );
} catch ( final IOException e ) {
LOG.error( "YAML OpenAPI specification could not be generated." );
throw new InvalidAspectModelException( "Error generating YAML OpenAPI specification", e );
}
}

public String generateJsonOpenApiSpec( final String aspectModel, final String baseUrl,
final boolean includeQueryApi, final boolean useSemanticVersion, final Optional<PagingOption> pagingOption ) {
try {
final AspectModelOpenApiGenerator generator = new AspectModelOpenApiGenerator();

final JsonNode json = generator.applyForJson( ModelUtils.resolveAspectFromModel( aspectModel ),
useSemanticVersion, baseUrl, Optional.empty(), Optional.empty(), includeQueryApi, pagingOption );

final ByteArrayOutputStream out = new ByteArrayOutputStream();
final ObjectMapper objectMapper = new ObjectMapper();

objectMapper.writerWithDefaultPrettyPrinter().writeValue( out, json );

return out.toString();
} catch ( final IOException e ) {
LOG.error( "JSON OpenAPI specification could not be generated." );
throw new InvalidAspectModelException( "Error generating JSON OpenAPI specification", e );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public Namespaces migrateWorkspace( final String storagePath ) {
}

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

private Namespace resolveNamespace( final List<Namespace> namespaces, final AspectModelUrn aspectModelUrn ) {
Expand Down
102 changes: 69 additions & 33 deletions src/main/java/io/openmanufacturing/ame/services/utils/ModelUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
import java.util.List;
import java.util.regex.Pattern;

import org.apache.jena.rdf.model.Model;
import org.apache.jena.riot.RiotException;

import io.openmanufacturing.ame.config.ApplicationSettings;
import io.openmanufacturing.ame.exceptions.InvalidAspectModelException;
import io.openmanufacturing.ame.exceptions.UrnNotFoundException;
import io.openmanufacturing.ame.resolver.inmemory.InMemoryStrategy;
Expand All @@ -40,19 +42,19 @@
import io.openmanufacturing.sds.aspectmodel.validation.report.ValidationReportBuilder;
import io.openmanufacturing.sds.aspectmodel.validation.services.AspectModelValidator;
import io.openmanufacturing.sds.aspectmodel.versionupdate.MigratorService;
import io.openmanufacturing.sds.metamodel.Aspect;
import io.openmanufacturing.sds.metamodel.loader.AspectModelLoader;
import io.vavr.control.Try;
import lombok.NoArgsConstructor;

@NoArgsConstructor
public class ModelUtils {

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

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

private ModelUtils() {
}
"^urn:[a-z0-9][a-z0-9-]{0,31}:([a-z0-9()+,\\-.:=@;$_!*#']|%[0-9a-f]{2})+$", Pattern.CASE_INSENSITIVE );

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

public static Try<VersionedModel> loadButNotResolveModel( final File inputFile ) {
try ( final InputStream inputStream = new FileInputStream( inputFile ) ) {
final SdsAspectMetaModelResourceResolver metaModelResourceResolver = new SdsAspectMetaModelResourceResolver();
return TurtleLoader.loadTurtle( inputStream ).flatMap( model ->
metaModelResourceResolver.getBammVersion( model ).flatMap( metaModelVersion ->
metaModelResourceResolver.mergeMetaModelIntoRawModel( model, metaModelVersion ) ) );
/**
* Migrates a model to its latest version.
*
* @param aspectModel as a string.
* @param storagePath stored path to the Aspect Models.
* @return migrated Aspect Model as a string.
*/
public static String migrateModel( final String aspectModel, final String storagePath ) {
final InMemoryStrategy inMemoryStrategy = ModelUtils.inMemoryStrategy( aspectModel, storagePath );

final Try<VersionedModel> migratedFile = LoadModelFromStoragePath( inMemoryStrategy ).flatMap(
new MigratorService()::updateMetaModelVersion );

final VersionedModel versionedModel = migratedFile.getOrElseThrow(
e -> new InvalidAspectModelException( "Aspect Model cannot be migrated.", e ) );

return getPrettyPrintedVersionedModel( versionedModel, inMemoryStrategy.getAspectModelUrn().getUrn() );
}

/**
* Creates an Aspect instance from an Aspect Model.
*
* @param aspectModel as a string.
* @return the Aspect as an object.
*/
public static Aspect resolveAspectFromModel( final String aspectModel ) {
final InMemoryStrategy inMemoryStrategy = ModelUtils.inMemoryStrategy( aspectModel,
ApplicationSettings.getMetaModelStoragePath() );

final VersionedModel versionedModel = ModelUtils.LoadModelFromStoragePath( inMemoryStrategy ).getOrElseThrow(
e -> new InvalidAspectModelException( "Cannot resolve Aspect Model.", e ) );

return AspectModelLoader.fromVersionedModelUnchecked( versionedModel );
}

/**
* Load Aspect Model from storage path.
*
* @param inMemoryStrategy for the given storage path.
* @return the resulting {@link VersionedModel} that corresponds to the input Aspect model.
*/
public static Try<VersionedModel> LoadModelFromStoragePath( final InMemoryStrategy inMemoryStrategy ) {
return resolveModel( inMemoryStrategy.model );
}

/**
* Loading the Aspect Model from input file.
*
* @param file Aspect Model as a file.
* @return the resulting {@link VersionedModel} that corresponds to the input Aspect model.
*/
public static Try<VersionedModel> loadModelFromFile( final File file ) {
try ( final InputStream inputStream = new FileInputStream( file ) ) {
return TurtleLoader.loadTurtle( inputStream ).flatMap( ModelUtils::resolveModel );
} catch ( final IOException exception ) {
return Try.failure( exception );
}
}

public static String migrateModel( final String aspectModel, final String storagePath ) {
final InMemoryStrategy inMemoryStrategy = inMemoryStrategy( aspectModel, storagePath );

private static Try<VersionedModel> resolveModel( final Model model ) {
final SdsAspectMetaModelResourceResolver metaModelResourceResolver = new SdsAspectMetaModelResourceResolver();

final Try<VersionedModel> migratedFile = metaModelResourceResolver.getBammVersion( inMemoryStrategy.model )
.flatMap( metaModelVersion ->
metaModelResourceResolver.mergeMetaModelIntoRawModel(
inMemoryStrategy.model,
metaModelVersion ) )
.flatMap(
new MigratorService()::updateMetaModelVersion );

final VersionedModel versionedModel = migratedFile.getOrElseThrow(
e -> new InvalidAspectModelException( "AspectModel cannot be migrated.", e ) );

return getPrettyPrintedVersionedModel( versionedModel, inMemoryStrategy.getAspectModelUrn().getUrn() );
return metaModelResourceResolver.getBammVersion( model ).flatMap(
metaModelVersion -> metaModelResourceResolver.mergeMetaModelIntoRawModel( model, metaModelVersion ) );
}

/**
Expand Down Expand Up @@ -163,16 +201,14 @@ public static ValidationReport validateModel( final String aspectModel, final St
}

private static ValidationReport buildValidationSyntacticReport( final RiotException riotException ) {
return new ValidationReportBuilder()
.withValidationErrors( List.of( new ValidationError.Syntactic( riotException ) ) )
.buildInvalidReport();
return new ValidationReportBuilder().withValidationErrors(
List.of( new ValidationError.Syntactic( riotException ) ) ).buildInvalidReport();
}

private static ValidationReport buildValidationSemanticReport( final String message, final String focusNode,
final String resultPath, final String Severity, final String value ) {
return new ValidationReportBuilder()
.withValidationErrors( List
.of( new ValidationError.Semantic( message, focusNode, resultPath, Severity, value ) ) )
.buildInvalidReport();
final ValidationError.Semantic semantic = new ValidationError.Semantic( message, focusNode, resultPath, Severity,
value );
return new ValidationReportBuilder().withValidationErrors( List.of( semantic ) ).buildInvalidReport();
}
}
Loading

0 comments on commit bc982b9

Please sign in to comment.