eclipse-esmf / esmf-sdk

Load Aspect Models and their artifacts as Java code; share components to realize SAMM as code
https://eclipse-esmf.github.io/esmf-developer-guide/index.html
Mozilla Public License 2.0
25 stars 12 forks source link

[BUG] Generated OpenAPI spec differs between esmf-sdk 2.1.3 and 2.2.3 #438

Closed shijinrajbosch closed 1 year ago

shijinrajbosch commented 1 year ago

Describe the bug Test case works with sds-aspect-model-starter 2.1.3 & esmf-aspect-model-starter 2.2.3 and failing with - esmf-aspect-model-starter 2.3.2

Test class : org.eclipse.tractusx.semantics.hub.ModelsApiTest Test method : testGenerateOpenApiEndpointSpecExpectSuccess()

Semantic-hub Github : https://github.com/bci-oss/sldt-semantic-hub.git Branch - SAMM_BAMM_Support-ESMF_SDK_2.3.2

Where Test class : org.eclipse.tractusx.semantics.hub.ModelsApiTest Test method : testGenerateOpenApiEndpointSpecExpectSuccess()

Testcase Assertion Expected: #/components/schemas/urn_samm_org.eclipse.esmf.samm_characteristic_2.0.0_Boolean got: #/components/schemas/urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_Boolean ; components.schemas Testcase Assertion Expected: urn_samm_org.eclipse.esmf.samm_characteristic_2.0.0_Boolean but none found

Additional context Steps to reproduce the issue :

String urnPrefix = "urn:samm:org.eclipse.tractusx.testopenapi:1.0.0#"; String baseUrl = "example.com";

Get a Model - find model from fuseki data base using the org.eclipse.esmf.aspectmodel.urn.AspectModelUrn . The model created in apache fuseki database is here

String modelDefinition = model.toString()


String urnPrefix = "urn:samm:org.eclipse.tractusx.testopenapi:1.0.0#";
String baseUrl = "example.com";

private static final String AUXILIARY_NAMESPACE = "urn:bamm:io.openmanufacturing:aspect-model:aux#";

private static org.apache.jena.query.ParameterizedSparqlString create( final String query ) {
      final org.apache.jena.query.ParameterizedSparqlString pss = new ParameterizedSparqlString();
      pss.setCommandText( query );
      pss.setNsPrefix( "aux", AUXILIARY_NAMESPACE );
      return pss;
   }

private static final String CONSTRUCT_BY_URN_QUERY =
         "CONSTRUCT {\n"
               + " ?s ?p ?o .\n"
               + "} WHERE {\n"
               + "    bind( ns: as ?aspect )\n"
               + "    ?aspect (<>|!<>)* ?s .\n"
               + "    ?otherAspect (<>|!<>)* ?s .\n"
               + "    ?s ?p ?o .\n"
               + "}";

public static org.apache.jena.query.Query buildFindByUrnConstructQuery( final AspectModelUrn urn ) {
      final org.apache.jena.query.ParameterizedSparqlString pss = create( CONSTRUCT_BY_URN_QUERY );
      pss.setNsPrefix( "ns", urn.getUrn().toString() );
      return pss.asQuery();
   }

private org.apache.jena.rdf.model.Model findJenaModelByUrn( final AspectModelUrn urn ) {
      final org.apache.jena.query.Query constructQuery = buildFindByUrnConstructQuery( urn );
      try ( final org.apache.jena.rdfconnection.RDFConnection rdfConnection = rdfConnectionRemoteBuilder.build() ) {
         return rdfConnection.queryConstruct( constructQuery );
      }
   }

public String getModelDefinition( final AspectModelUrn urn ) {
      Model jenaModelByUrn = findJenaModelByUrn( urn );
      if ( jenaModelByUrn == null ) {
         throw new AspectModelNotFoundException( urn );
      }
      StringWriter out = new StringWriter();
      jenaModelByUrn.write( out, "TURTLE" );
      return out.toString();
   }

Try<VersionedModel> loadSammModel( String ttl ) {
      InputStream targetStream = new ByteArrayInputStream( ttl.getBytes() );
      Try<Model> model = TurtleLoader.loadTurtle( targetStream );

      StaticResolutionStrategy resolutionStrategy = new StaticResolutionStrategy( model );
      org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver resolver = new AspectModelResolver();
      Try<VersionedModel> versionedModel = resolver.resolveAspectModel( resolutionStrategy, resolutionStrategy.getAspectModelUrn() );

      if ( resolutionStrategy.getResolvementCounter() > 1 ) {
         return Try.failure( new ResolutionException( "The definition must be self contained!" ) );
      }
      return versionedModel;
   }

---------------------------------------------
import java.util.List;
import java.util.Optional;

import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.StmtIterator;
import org.apache.jena.vocabulary.RDF;
import org.eclipse.esmf.aspectmodel.resolver.AbstractResolutionStrategy;
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;
import org.eclipse.esmf.aspectmodel.vocabulary.SAMM;
import org.eclipse.esmf.samm.KnownVersion;

import io.vavr.NotImplementedError;
import io.vavr.control.Try;

public class StaticResolutionStrategy extends AbstractResolutionStrategy {
   private int counter;
   private final Try<Model> model;

   public StaticResolutionStrategy( Try<Model> model ) {
      this.model = model;
   }

   @Override
   public Try<Model> apply( AspectModelUrn t ) {
      counter++;
      return this.model;
   }

   public int getResolvementCounter() {
      return counter;
   }

   public AspectModelUrn getAspectModelUrn() {
      final Optional<StmtIterator> stmtIterator = getStmtIterator();

      final String aspectModelUrn = stmtIterator.orElseThrow(
                  () -> new NotImplementedError( "AspectModelUrn cannot be found." ) )
            .next().getSubject().getURI();

      return AspectModelUrn.fromUrn( aspectModelUrn );
   }

   private Optional<StmtIterator> getStmtIterator() {
      for ( final KnownVersion version : KnownVersion.getVersions() ) {
         final SAMM samm = new SAMM( version );
         final List<Resource> resources = List.of( samm.Aspect(), samm.Property(), samm.Entity(), samm.Characteristic() );
         final Optional<StmtIterator> stmtIterator = resources.stream().filter(
                     resource -> model.get().listStatements( null, RDF.type, resource ).hasNext() ).findFirst()
               .map( resource -> model.get().listStatements( null, RDF.type, resource ) );
         if ( stmtIterator.isPresent() ) {
            return stmtIterator;
         }
      }
      return Optional.empty();
   }
}

--------------------------------------

String modelDefinition = getModelDefinition( org.eclipse.esmf.aspectmodel.urn.AspectModelUrn.fromUrn( urn ) );

Try<org.eclipse.esmf.aspectmodel.resolver.services.VersionedModel> versionedModel =
            loadSammModel( modelDefinition );

Try<List<org.eclipse.esmf.metamodel.Aspect>> aspect = org.eclipse.esmf.metamodel.loader.AspectModelLoader.getAspects( versionedModel.get() );   

org.eclipse.esmf.metamodel.Aspect aspect = aspect.get().get( 0 );

org.eclipse.esmf.aspectmodel.generator.openapi.AspectModelOpenApiGenerator openApiGenerator = new AspectModelOpenApiGenerator();

com.fasterxml.jackson.databind.JsonNode resultJson = openApiGenerator.applyForJson( aspect, true, baseUrl, Optional.empty(), Optional.empty(), false, Optional.empty() );

String output = resultJson.toString();  
shijinrajbosch commented 1 year ago

Please find the actual.json and expected.json enclosed.

actual.json expected.json

atextor commented 1 year ago

Hi @shijinrajbosch, the summary of the relevant parts of the issue is as follows:

An OpenAPI specification document generated for an Aspect Model with esmf-sdk 2.1.3 differs from the one generated for the same model with esmf-sdk 2.3.2. In particular, identifiers that refer to samm-c Characteristics make use of SAMM version 2.1.0 instead of 2.0.0. For example, samm-c:Boolean will lead to the schema identifier urn_samm_org.eclipse.esmf.samm_characteristic_2.1.0_Boolean instead of the expected urn_samm_org.eclipse.esmf.samm_characteristic_2.0.0_Boolean.

The APIs that generate documents and artifacts such as OpenAPI specifications documents always work with Aspect Models based on the latest meta model version that is supported by esmf-sdk. In case an Aspect Model is based on a previous meta model version, the model is migrated on-the-fly to the latest meta model version. Therefore artifacts such as OpenAPI specification documents can also be generated for Aspect Models based on older meta model versions. The result is expected and works as intended: The generated OpenAPI specification document is therefore based on the latest meta model version and is equally valid as the previously generated document. The only difference is that is now based on an Aspect Model that makes used of SAMM 2.1.0. However, as the schema identifiers are only use in the spec but do not appear in the API itself, there is no difference in the API that was generated from the Aspect Model.

I suggest you adjust the tests in your project to not assume textual equality between the documents, because this is not provided. If you programmatically need to access the meta model version that will be used in the artifact generation (for example to assert its presence in the generated document), you can use the org.eclipse.esmf.samm.KnownVersion#getLatest() method which is provided by the org.eclipse.esmf:esmf-semantic-aspect-meta-model Maven artifact.

I'm closing this issue since the OpenAPI generation works as intended.

shijinrajbosch commented 1 year ago

Many thanks @atextor for the clarifications, help and support.