Skip to content

Commit

Permalink
Merge pull request #177 from cweedall/defect/exactly-one-item-dont-ma…
Browse files Browse the repository at this point in the history
…ke-property-item-array

Complex restrictions; enum change; cardinality + required improvements
  • Loading branch information
dgarijo authored May 9, 2024
2 parents a69d201 + f4a4309 commit f9d7654
Show file tree
Hide file tree
Showing 13 changed files with 938 additions and 531 deletions.
38 changes: 38 additions & 0 deletions docs/configuration_file.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ default_descriptions: true

## Enable/disable generation of default properties (description, id, label, and type) for each schema
default_properties: true

## Enable/disable generation of arrays for property type(s) all the time, regardless of cardinality / restrictions
always_generate_arrays: true

## Enable/disable generation of list of required properties for a schema, if the the cardinality indicates it is required (e.g. exactly 1)
required_properties_from_cardinality: false
```
## Supported settings
Expand Down Expand Up @@ -303,6 +309,38 @@ For more information, go to [filtering classes](filtering.md#default_properties)
default_properties: false
```

### always_generate_arrays

Enable/disable generation of arrays for property type(s) all the time, regardless of cardinality / restrictions.

| Field | Value |
| ------------- | --------- |
| **Required:** | `false` |
| **Type:** | `Boolean` |
| **Default:** | `True` |

For more information, go to [filtering classes](filtering.md#always_generate_arrays)

```yaml
always_generate_arrays: true
```

### required_properties_from_cardinality

Enable/disable generation of list of required properties for a schema, if the the cardinality indicates it is required (e.g. exactly 1).

| Field | Value |
| ------------- | --------- |
| **Required:** | `false` |
| **Type:** | `Boolean` |
| **Default:** | `False` |

For more information, go to [filtering classes](filtering.md#required_properties_from_cardinality)

```yaml
required_properties_from_cardinality: true
```

## auth

Add login to the API and add security to the following methods: `POST`, `PUT` and `DELETE`
Expand Down
94 changes: 94 additions & 0 deletions docs/filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ default_descriptions: true

## Enable/disable generation of default properties (description, id, label, and type) for each schema
default_properties: true

## Enable/disable generation of arrays for property type(s) all the time, regardless of cardinality / restrictions
always_generate_arrays: true

## Enable/disable generation of list of required properties for a schema, if the the cardinality indicates it is required (e.g. exactly 1)
required_properties_from_cardinality: false
```
The result is available at: [DBPedia Music](https://app.swaggerhub.com/apis/mosoriob/dbpedia-music/v1.3.0)
Expand Down Expand Up @@ -231,3 +237,91 @@ components:
type: integer
type: object
```

### Generating property types as array of items uniformly vs. non-array when cardinality/restrictions warrant it

When a property can have multiple items (e.g. a list of names), the property schema will always generate `type: array` with an `items:` key and sub-item types (e.g. `type: string`). However, in cases where the property is restricted by cardinality, the API spec can be generated so that the property is not an array but a single type (e.g. `type: string`). These cases include cardinality where the property is exactly 1 or is a maximum of 1.

The default is to treat everything as an array, such as:

```yaml
components:
schemas:
YourClass:
properties:
propertyA:
items:
maxItems: 1
minItems: 1
type: string
type: array
propertyB:
items:
maxItems: 3
type: string
type: array
type: object
```

The option `always_generate_arrays` allows you to disable the generating all properties with an array of items, if the class restrictions warrant it (e.g. there is exactly one property value allowed). By setting the `always_generate_arrays` value to `false`, the above example becomes:

```yaml
components:
schemas:
YourClass:
properties:
propertyA:
type: array
propertyB:
items:
maxItems: 3
type: string
type: array
type: object
```

### Generating list of required property/properties for a class, when cardinality/restrictions warrant it

When a property is known to be required based on class restrictions (e.g. exactly 1 or a minimum >= 1), OpenAPI specification supports a `required` key directly under the property. By default, OBA does not generate this required list of properties (for each class), for example:

```yaml
components:
schemas:
YourClass:
properties:
propertyA:
items:
maxItems: 1
minItems: 1
type: string
type: array
propertyB:
items:
maxItems: 3
type: string
type: array
type: object
```

The option `required_properties_from_cardinality` allows you to generate this list based on class restrictions (e.g. there is exactly one property value or there is a minimum of 1 or more of the property value). By setting the `required_properties_from_cardinality` value to `true`, the above example becomes:

```yaml
components:
schemas:
YourClass:
properties:
propertyA:
items:
maxItems: 1
minItems: 1
type: string
type: array
propertyB:
items:
maxItems: 3
type: string
type: array
type: object
required:
- propertyA
```
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<packaging>jar</packaging>
<groupId>edu.isi.oba</groupId>
<artifactId>oba</artifactId>
<version>3.8.1</version>
<version>3.9.0</version>
<name>core</name>
<url>https://github.com/KnowledgeCaptureAndDiscovery/OBA</url>

Expand Down
36 changes: 20 additions & 16 deletions src/main/java/edu/isi/oba/Mapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,20 @@ public Mapper(YamlConfig config_data) throws OWLOntologyCreationException, IOExc
//Load the ontology into the manager
int i = 0;
List<String> ontologyPaths = new ArrayList<>();
download_ontologies(config_ontologies, destination_dir, i, ontologyPaths);
this.download_ontologies(config_ontologies, destination_dir, i, ontologyPaths);
//set ontology paths in YAML to the ones we have downloaded (for later reference by owl2jsonld)
this.config_data.setOntologies(ontologyPaths);
ontologies = this.manager.ontologies().collect(Collectors.toList());

//Create a temporal Map<IRI, String> schemaNames with the classes
for (OWLOntology ontology : ontologies) {
Set<OWLClass> classes = ontology.getClassesInSignature();
setSchemaNames(classes);
setSchemaDrescriptions(classes,ontology);
this.setSchemaNames(classes);
this.setSchemaDrescriptions(classes,ontology);
}

if (config_data.getClasses() != null) {
this.selected_classes = filter_classes();
this.selected_classes = this.filter_classes();
}
}

Expand Down Expand Up @@ -127,15 +127,15 @@ public void createSchemas(String destination_dir) {
for (OWLClass cls : classes) {
//filter if the class prefix does not have the default ontology prefix
if (cls.getIRI() != null) {
if (selected_classes == null || selected_classes.contains(cls)) {
add_owlclass_to_openapi(query, pathGenerator, ontology, defaultOntologyPrefixIRI, cls, true);
if (this.selected_classes == null || this.selected_classes.contains(cls)) {
this.add_owlclass_to_openapi(query, pathGenerator, ontology, defaultOntologyPrefixIRI, cls, true);
}
}
}
}

if (this.config_data.getAuth().getEnable()) {
add_user_path(pathGenerator);
this.add_user_path(pathGenerator);
}
}

Expand Down Expand Up @@ -163,10 +163,11 @@ private List<OWLClass> add_owlclass_to_openapi(Query query, PathGenerator pathGe
if (defaultOntologyPrefixIRI.equals(classPrefixIRI)) {
try{
MapperSchema mapperSchema = getMapperSchema(query, ontology, cls, this.schemaDescriptions.get(cls.getIRI()));

// add references to schemas in class restrictions (check selected classes to avoid conflicts)
for (String classToCheck : mapperSchema.getPropertiesFromObjectRestrictions_ranges()) {
OWLClass clsToCheck = manager.getOWLDataFactory().getOWLClass(IRI.create(classPrefixIRI + classToCheck));
if (this.mappedClasses.contains(clsToCheck) || this.selected_classes.contains(clsToCheck)){
if (this.mappedClasses.contains(clsToCheck) || (this.selected_classes != null && this.selected_classes.contains(clsToCheck))){
logger.info("The class " + clsToCheck + " exists ");
} else {
//rare cases have instances, so we filter them out and recheck that the target is a class.
Expand All @@ -175,13 +176,14 @@ private List<OWLClass> add_owlclass_to_openapi(Query query, PathGenerator pathGe
for (OWLOntology temp_ontology : this.ontologies) {
if (this.config_data.getConfigFlagValue(CONFIG_FLAG.FOLLOW_REFERENCES)) {
this.mappedClasses.add(clsToCheck);
getMapperSchema(query, temp_ontology, clsToCheck, this.schemaDescriptions.get(clsToCheck.getIRI()));
add_owlclass_to_openapi(query, pathGenerator, temp_ontology, classPrefixIRI, clsToCheck, false);
this.getMapperSchema(query, temp_ontology, clsToCheck, this.schemaDescriptions.get(clsToCheck.getIRI()));
this.add_owlclass_to_openapi(query, pathGenerator, temp_ontology, classPrefixIRI, clsToCheck, false);
}
}
}
}
}

// add references to schemas in property ranges
for (OWLClass ref_class : mapperSchema.getProperties_range()) {
if (this.mappedClasses.contains(ref_class)){
Expand All @@ -190,15 +192,17 @@ private List<OWLClass> add_owlclass_to_openapi(Query query, PathGenerator pathGe
for (OWLOntology temp_ontology : this.ontologies) {
if (this.config_data.getConfigFlagValue(CONFIG_FLAG.FOLLOW_REFERENCES)) {
this.mappedClasses.add(ref_class);
getMapperSchema(query, temp_ontology, ref_class,this.schemaDescriptions.get(ref_class.getIRI()));
add_owlclass_to_openapi(query, pathGenerator, temp_ontology, classPrefixIRI, ref_class, false);
this.getMapperSchema(query, temp_ontology, ref_class,this.schemaDescriptions.get(ref_class.getIRI()));
this.add_owlclass_to_openapi(query, pathGenerator, temp_ontology, classPrefixIRI, ref_class, false);
}
}
}
}

//Add the OpenAPI paths
if (topLevel)
if (topLevel) {
addOpenAPIPaths(pathGenerator, mapperSchema, cls);
}
}catch(Exception e){
logger.log(Level.SEVERE,"Could not parse class "+cls.getIRI().toString());
}
Expand All @@ -218,16 +222,16 @@ private MapperSchema getMapperSchema(Query query, OWLOntology ontology, OWLClass
}

private void addOpenAPIPaths(PathGenerator pathGenerator, MapperSchema mapperSchema, OWLClass cls) {
if (selected_classes != null && !selected_classes.contains(cls)) {
if (this.selected_classes != null && !this.selected_classes.contains(cls)) {
logger.info("Ignoring class " + cls.toString());
} else {
add_path(pathGenerator, mapperSchema);
this.add_path(pathGenerator, mapperSchema);
}
}

private void setSchemaNames(Set<OWLClass> classes) {
for (OWLClass cls : classes) {
schemaNames.put(cls.getIRI(), cls.getIRI().getShortForm());
this.schemaNames.put(cls.getIRI(), cls.getIRI().getShortForm());
}
}

Expand Down
Loading

0 comments on commit f9d7654

Please sign in to comment.