diff --git a/java/cli/src/main/java/org/brapi/schematools/cli/BrAPICommandException.java b/java/cli/src/main/java/org/brapi/schematools/cli/BrAPICommandException.java index 6e7e6dc..cd83bc5 100644 --- a/java/cli/src/main/java/org/brapi/schematools/cli/BrAPICommandException.java +++ b/java/cli/src/main/java/org/brapi/schematools/cli/BrAPICommandException.java @@ -16,6 +16,12 @@ public BrAPICommandException(String message) { this.allErrors = new ArrayList<>() ; } + public BrAPICommandException(String message, Throwable cause) { + super(message, cause) ; + + this.allErrors = new ArrayList<>() ; + } + public BrAPICommandException(String message, Collection allErrors) { super(message) ; diff --git a/java/cli/src/main/java/org/brapi/schematools/cli/GenerateSubCommand.java b/java/cli/src/main/java/org/brapi/schematools/cli/GenerateSubCommand.java index 61b840f..3a1ec23 100644 --- a/java/cli/src/main/java/org/brapi/schematools/cli/GenerateSubCommand.java +++ b/java/cli/src/main/java/org/brapi/schematools/cli/GenerateSubCommand.java @@ -66,6 +66,9 @@ public class GenerateSubCommand implements Runnable { @CommandLine.Option(names = {"-x", "--throwExceptionOnFail"}, description = "Throw an exception on failure. False by default, if set to True if an exception is thrown when validation or generation fails.") private boolean throwExceptionOnFail = false; + @CommandLine.Option(names = {"-s", "--stackTrace"}, description = "If an error is recorded output the stack trace.") + private boolean stackTrace = false; + @Override public void run() { try { @@ -100,8 +103,13 @@ public void run() { } } catch (Exception exception) { err.println(exception.getMessage()); + if (stackTrace) { + exception.printStackTrace(err); + } + if (throwExceptionOnFail) { - throw new BrAPICommandException(exception.getMessage()) ; + + throw new BrAPICommandException(exception.getMessage(), exception) ; } } finally { if (out != null) { diff --git a/java/core/src/main/java/org/brapi/schematools/core/markdown/MarkdownGenerator.java b/java/core/src/main/java/org/brapi/schematools/core/markdown/MarkdownGenerator.java index caf157f..9c898ca 100644 --- a/java/core/src/main/java/org/brapi/schematools/core/markdown/MarkdownGenerator.java +++ b/java/core/src/main/java/org/brapi/schematools/core/markdown/MarkdownGenerator.java @@ -60,7 +60,11 @@ public Response> generate() { try { Files.createDirectories(descriptionsPath) ; Files.createDirectories(fieldsPath) ; - return brAPISchemas.stream().filter(this::isGenerating).map(this::generateMarkdown).collect(Response.mergeLists()) ; + return brAPISchemas + .stream() + .filter(this::isGenerating) + .map(this::generateMarkdown) + .collect(Response.mergeLists()) ; } catch (IOException e) { return fail(Response.ErrorType.VALIDATION, e.getMessage()) ; } diff --git a/java/core/src/main/java/org/brapi/schematools/core/ontmodel/OntModelGenerator.java b/java/core/src/main/java/org/brapi/schematools/core/ontmodel/OntModelGenerator.java index df97793..02c2978 100644 --- a/java/core/src/main/java/org/brapi/schematools/core/ontmodel/OntModelGenerator.java +++ b/java/core/src/main/java/org/brapi/schematools/core/ontmodel/OntModelGenerator.java @@ -4,10 +4,7 @@ import lombok.Getter; import org.apache.jena.datatypes.xsd.XSDDatatype; import org.apache.jena.ontapi.OntModelFactory; -import org.apache.jena.ontapi.model.OntClass; -import org.apache.jena.ontapi.model.OntIndividual; -import org.apache.jena.ontapi.model.OntModel; -import org.apache.jena.rdf.model.Property; +import org.apache.jena.ontapi.model.*; import org.brapi.schematools.core.brapischema.BrAPISchemaReader; import org.brapi.schematools.core.model.*; import org.brapi.schematools.core.ontmodel.metadata.OntModelGeneratorMetadata; @@ -15,9 +12,11 @@ import org.brapi.schematools.core.response.Response; import java.nio.file.Path; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import static org.brapi.schematools.core.response.Response.success; @@ -81,13 +80,19 @@ private static class Generator { private final OntModel model ; private final String namespace ; + private final String language ; + private final HashMap types; + private final HashMap ontClasses; public Generator(OntModelGeneratorOptions options, OntModelGeneratorMetadata metadata, List brAPISchemas) { this.options = options; this.metadata = metadata; namespace = metadata.getNamespace() ; + language = metadata.getLanguage() ; model = OntModelFactory.createModel(); + model.setID(namespace) ; + Map map = new HashMap<>(); for (BrAPIClass brAPISchema : brAPISchemas) { if (map.put(brAPISchema.getName(), brAPISchema) != null) { @@ -95,96 +100,252 @@ public Generator(OntModelGeneratorOptions options, OntModelGeneratorMetadata met } } this.brAPISchemas = map; + this.types = new HashMap<>() ; + this.ontClasses = new HashMap<>() ; } public Response generate() { - return brAPISchemas.values().stream().map(this::createClass).collect(Response.toList()).map(() -> success(model)); + return brAPISchemas.values() + .stream() + .filter(this::isGenerating) + .map(this::createClass) + .collect(Response.toList()) + .map(this::updateClasses) + .map(() -> success(model)); + } + + private boolean isGenerating(BrAPIClass brAPIClass) { + return brAPIClass.getMetadata() == null || + !(brAPIClass.getMetadata().isRequest() || brAPIClass.getMetadata().isParameters()); } - private Response createClass(BrAPIClass schema) { - if (schema instanceof BrAPIObjectType brAPIObjectType) { + private Response createClass(BrAPIType brAPIType) { + if (brAPIType instanceof BrAPIObjectType brAPIObjectType) { return createObjectType(brAPIObjectType) ; - } else if (schema instanceof BrAPIOneOfType brAPIOneOfType) { + } else if (brAPIType instanceof BrAPIOneOfType brAPIOneOfType) { return createOneOfType(brAPIOneOfType) ; - } else if (schema instanceof BrAPIEnumType brAPIEnumType) { + } else if (brAPIType instanceof BrAPIEnumType brAPIEnumType) { return createEnumType(brAPIEnumType) ; } - return Response.fail(Response.ErrorType.VALIDATION, String.format("Unknown output type '%s'", schema.getName())); + return Response.fail(Response.ErrorType.VALIDATION, String.format("Unknown supported BrAPI Type '%s'", brAPIType.getName())); + } + + private Response createObjectType(BrAPIObjectType brAPIObjectType) { + OntClass ontClass = createOntClass(brAPIObjectType) ; + + return brAPIObjectType.getProperties() + .stream() + .map(property -> createClassForType(property.getType())) + .collect(Response.toList()) + .map (() -> success(true)) ; } - private Response createObjectType(BrAPIObjectType brAPIObjectType) { - OntClass.Named ontClass = model.createOntClass(createURIForBrAPIClass(brAPIObjectType)); + private Response createOneOfType(BrAPIOneOfType brAPIOneOfType) { + types.put(brAPIOneOfType.getName(), brAPIOneOfType) ; - return brAPIObjectType.getProperties().stream() - .map(property -> createProperty(ontClass, property)) + return brAPIOneOfType.getPossibleTypes() + .stream() + .map(this::createClass) .collect(Response.toList()) - .map(() -> success(ontClass)); + .map(() -> success(false)) ; } - private Response createProperty(OntClass.Named ontClass, BrAPIObjectProperty property) { + private Response createEnumType(BrAPIEnumType brAPIEnumType) { + OntClass ontClass = createOntClass(brAPIEnumType) ; - Property ontProperty = null ; + return brAPIEnumType.getValues() + .stream() + .map(enumValue -> createOntIndividual(ontClass, enumValue)) + .collect(Response.toList()) + .mapResult(model::createObjectOneOf) + .map(() -> success(true)) ; + } - BrAPIType type = property.getType(); + private OntClass createOntClass(BrAPIClass brAPIClass) { + types.put(brAPIClass.getName(), brAPIClass) ; - if (type instanceof BrAPIObjectType brAPIObjectType) { - ontProperty = model.createObjectProperty(createURIForBrAPIObjectProperty(ontClass, property)); - ontClass.addProperty(ontProperty, createURIForBrAPIClass(brAPIObjectType)) ; + OntClass ontClass = model.createOntClass(createURIForBrAPIType(brAPIClass)); + ontClass.addLabel(brAPIClass.getName(), language) ; + + if (brAPIClass.getDescription() != null) { + ontClass.addComment(brAPIClass.getDescription(), language); + } + + ontClasses.put(brAPIClass.getName(), ontClass) ; + return ontClass ; + } + + private Response createClassForType(BrAPIType type) { + if (type instanceof BrAPIObjectType brAPIObjectType) { + return createObjectType(brAPIObjectType) ; } else if (type instanceof BrAPIArrayType brAPIArrayType) { - ontProperty = model.createObjectProperty(String.format("%s/%s", ontClass.getNameSpace(), property.getName())); - //ontClass.addProperty(ontProperty, brAPIArrayType.getItems()) ; + BrAPIType itemsType = brAPIArrayType.getItems(); + while (itemsType instanceof BrAPIArrayType brAPIArrayItemsType) { + itemsType = brAPIArrayItemsType.getItems() ; + } + + return createClassForType(itemsType) ; + } else if (type instanceof BrAPIEnumType brAPIEnumType) { + return createEnumType(brAPIEnumType) ; + } - } else if (type instanceof BrAPIReferenceType) { + return Response.success(false) ; + } + private Response> updateClasses() { + return this.ontClasses.values().stream() + .map(this::updateClass) + .collect(Response.toList()) ; + } - } else if (type instanceof BrAPIEnumType) { + private Response updateClass(OntClass ontClass) { + BrAPIClass brAPIClass = types.get(ontClass.getLabel()); + + if (brAPIClass instanceof BrAPIObjectType brAPIObjectType) { + return brAPIObjectType.getProperties() + .stream() + .map(property -> createProperty(ontClass, property)) + .collect(Response.toList()) + .map (() -> success(ontClass)) ; + } else if (brAPIClass instanceof BrAPIOneOfType brAPIOneOfType) { + return success(ontClass) ; + } else if (brAPIClass instanceof BrAPIEnumType brAPIEnumType) { + return success(ontClass) ; + } - } else if (type instanceof BrAPIPrimitiveType brAPIPrimitiveType) { - ontProperty = model.createObjectProperty(createURIForBrAPIObjectProperty(ontClass, property)); + return Response.fail(Response.ErrorType.VALIDATION, String.format("Unknown BrAPI Class '%s'", brAPIClass.getName())); + } - String typeURI = switch (brAPIPrimitiveType.getName()) { - case "string" -> XSDDatatype.XSDstring.getURI(); - case "integer" -> XSDDatatype.XSDinteger.getURI(); - case "number" -> XSDDatatype.XSDdouble.getURI(); - case "boolean" -> XSDDatatype.XSDboolean.getURI(); - default -> null; - }; + private Response createProperty(OntClass ontClass, BrAPIObjectProperty property) { + return createProperty(ontClass, property, property.getType()) ; + } - if (typeURI == null) { - return Response.fail(Response.ErrorType.VALIDATION, String.format("Can not find XSDDatatype for '%s' in property '%s'", brAPIPrimitiveType.getName(), property.getName())); + private Response createProperty(OntClass ontClass, BrAPIObjectProperty property, BrAPIType brAPIType) { + if (brAPIType instanceof BrAPIObjectType brAPIObjectType) { + return findReferencedClass(brAPIObjectType.getName()) + .mapResultToResponse(childClass -> createObjectProperty(ontClass, childClass, property)) ; + } else if (brAPIType instanceof BrAPIArrayType brAPIArrayType) { + BrAPIType itemsType = brAPIArrayType.getItems(); + AtomicInteger dimension = new AtomicInteger(1) ; + + while (itemsType instanceof BrAPIArrayType brAPIArrayItemsType) { + itemsType = brAPIArrayItemsType.getItems() ; + dimension.incrementAndGet() ; } - ontClass.addProperty(ontProperty, typeURI) ; + return createProperty(ontClass, property, itemsType) + .mapResultToResponse(ontRelationalProperty -> updateProperty(ontRelationalProperty, dimension.get())) ; + } else if (brAPIType instanceof BrAPIReferenceType brAPIReferenceType) { + return findReferencedBrAPIType(brAPIReferenceType.getName()) + .mapResultToResponse(referencedBrAPIType -> createProperty(ontClass, property, referencedBrAPIType)) ; + } else if (brAPIType instanceof BrAPIOneOfType brAPIOneOfType) { + return brAPIOneOfType.getPossibleTypes() + .stream() + .map(type -> findReferencedClass(type.getName())) + .collect(Response.toList()) + .mapResultToResponse(childClasses -> createObjectProperty(ontClass, childClasses, property)) ; + } else if (brAPIType instanceof BrAPIEnumType brAPIEnumType) { + return findReferencedClass(brAPIEnumType.getName()) + .mapResultToResponse(childClass -> createObjectProperty(ontClass, childClass, property)) ; + } else if (brAPIType instanceof BrAPIPrimitiveType) { + return createDataProperty(ontClass, property, brAPIType) ; } - return success(ontProperty) ; + return Response.fail(Response.ErrorType.VALIDATION, String.format("Unknown BrAPI Type '%s'", brAPIType.getName())); } - private Response createOneOfType(BrAPIOneOfType brAPIOneOfType) { - return success(model.createOntClass(namespace + brAPIOneOfType.getName())); + private Response updateProperty(OntRelationalProperty ontRelationalProperty, int dimension) { + //TODO dimension + return success(ontRelationalProperty) ; } - private Response createEnumType(BrAPIEnumType brAPIEnumType) { - OntClass.Named ontClass = model.createOntClass(namespace + brAPIEnumType.getName()); + private Response createObjectProperty(OntClass domain, OntClass range, BrAPIObjectProperty property) { + final OntObjectProperty objectProperty = model.createObjectProperty(createURIForBrAPIObjectProperty(domain, property)); + objectProperty.addDomain(domain); + objectProperty.addRange(range); + objectProperty.addLabel(property.getName(), language); + + if (property.getDescription() != null) { + objectProperty.addComment(property.getDescription(), language); + } - //OntClass.OneOf oneOf = ontClass.as(OntClass.OneOf.class); + return success(objectProperty) ; + } - // oneOf.setComponents(brAPIEnumType.getValues().stream().map(this::createOntIndividual).toList()) ; + private Response createObjectProperty(OntClass domain, List ranges, BrAPIObjectProperty property) { + final OntObjectProperty objectProperty = model.createObjectProperty(createURIForBrAPIObjectProperty(domain, property)); + objectProperty.addDomain(domain); + ranges.forEach(objectProperty::addRange); + objectProperty.addLabel(property.getName(), language); - return success(ontClass); + if (property.getDescription() != null) { + objectProperty.addComment(property.getDescription(), language); + } + + return success(objectProperty) ; } - private OntIndividual createOntIndividual(BrAPIEnumValue enumValue) { - return model.createIndividual(namespace + enumValue.getName()) ; + private Response createDataProperty(OntClass domain, BrAPIObjectProperty property, BrAPIType type) { + final OntDataProperty dataProperty = model.createDataProperty(createURIForBrAPIObjectProperty(domain, property)); + dataProperty.addDomain(domain); + dataProperty.addLabel(property.getName(), language); + + if (property.getDescription() != null) { + dataProperty.addComment(property.getDescription(), language); + } + + OntDataRange.Named datatype = switch (type.getName()) { + case "string" -> model.getDatatype(XSDDatatype.XSDstring.getURI()) ; + case "integer" -> model.getDatatype(XSDDatatype.XSDinteger.getURI()) ; + case "number" -> model.getDatatype(XSDDatatype.XSDdouble.getURI()) ; + case "boolean" -> model.getDatatype(XSDDatatype.XSDboolean.getURI()) ; + default -> null; + }; + + if (datatype == null) { + return Response.fail(Response.ErrorType.VALIDATION, String.format("Can not find XSDDatatype for '%s' in property '%s'", property.getType().getName(), property.getName())); + } + + dataProperty.addRange(datatype) ; + + return success(dataProperty) ; } - private String createURIForBrAPIObjectProperty(OntClass.Named ontClass, BrAPIObjectProperty property) { + private Response findReferencedBrAPIType(String name) { + BrAPIType referencedBrAPIType = types.get(name); + + if (referencedBrAPIType != null) { + return success(referencedBrAPIType) ; + } else { + return Response.fail(Response.ErrorType.VALIDATION, String.format("Can not find referenced BrAPI type '%s'", name)); + } + } + + private Response findReferencedClass(String name) { + OntClass referencedClass = ontClasses.get(name); + + if (referencedClass != null) { + return success(referencedClass) ; + } else { + return Response.fail(Response.ErrorType.VALIDATION, String.format("Can not find referenced class '%s'", name)); + } + } + + private Response createOntIndividual(OntClass ontClass, BrAPIEnumValue enumValue) { + return success(ontClass.createIndividual(createURIForBrAPIEnumValue(enumValue))) ; + } + + private String createURIForBrAPIObjectProperty(OntClass ontClass, BrAPIObjectProperty property) { return String.format("%s/%s", ontClass.getURI(), property.getName()) ; } - private String createURIForBrAPIClass(BrAPIClass brAPIClass) { - return String.format("%s/%s", namespace, brAPIClass.getName()) ; + private String createURIForBrAPIType(BrAPIType brAPIType) { + return String.format("%s/%s", namespace, brAPIType.getName()) ; + } + + private String createURIForBrAPIEnumValue(BrAPIEnumValue enumValue) { + return String.format("%s/%s", namespace, enumValue.getName()) ; } } } diff --git a/java/core/src/main/java/org/brapi/schematools/core/ontmodel/metadata/OntModelGeneratorMetadata.java b/java/core/src/main/java/org/brapi/schematools/core/ontmodel/metadata/OntModelGeneratorMetadata.java index 18bdaec..a698d79 100644 --- a/java/core/src/main/java/org/brapi/schematools/core/ontmodel/metadata/OntModelGeneratorMetadata.java +++ b/java/core/src/main/java/org/brapi/schematools/core/ontmodel/metadata/OntModelGeneratorMetadata.java @@ -16,6 +16,7 @@ @Setter public class OntModelGeneratorMetadata implements Metadata { private String namespace ; + private String language ; /** * Load the metadata from a metadata file in YAML or Json. The metadata file may have missing diff --git a/java/core/src/main/resources/ont-model-metadata.yaml b/java/core/src/main/resources/ont-model-metadata.yaml index 88e7038..cf69f87 100644 --- a/java/core/src/main/resources/ont-model-metadata.yaml +++ b/java/core/src/main/resources/ont-model-metadata.yaml @@ -1 +1,2 @@ -namespace: http://brapi.org \ No newline at end of file +namespace: http://brapi.org +language: en \ No newline at end of file