Skip to content

Commit

Permalink
feat(Order): Creation of an automatic default order for properties fr…
Browse files Browse the repository at this point in the history
…om ontologies, to be used when no order defined in Vocabulary

OpenSILEX/opensilex-dev!1186
  • Loading branch information
HART Maximilian committed Mar 14, 2024
1 parent 98df2ab commit 3bbf00e
Show file tree
Hide file tree
Showing 12 changed files with 432 additions and 110 deletions.
2 changes: 0 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<!-- TOC -->
* [Changelog](#changelog)
* [[Next]]
* prévenir les utilisateurs que les services deprecated seront supprimés dans la version suivante
* [[1.2.1]](#121)
* [Fixed](#fixed)
* [[1.2.0] - Caramelized Crystal](#120---caramelized-crystal)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,73 @@ public Response getLinkableProperties(
return new ResourceTreeResponse(properties).getResponse();
}

@GET
@Path("/domain_hierarchy_restrictions")
@ApiOperation("Get restrictions from some super-class domain to one lower down in the hierarchy, ordered by what domain they first appear in.")
@ApiProtected
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Return list of objects containing domain source and list of property trees", response = PropertiesByDomainDTO.class, responseContainer = "List")
})
public Response getPropertiesByDomainHierarchyUsingRestrictions(
@ApiParam(value = "Domain ancestor URI") @QueryParam("ancestor") @NotNull @ValidURI URI ancestorURI,
@ApiParam(value = "Domain uris from types that have ancestor as an ancestor") @NotEmpty @NotNull @ValidURI @QueryParam("children") List<URI> childrenDomains
) throws Exception {

OntologyStore ontologyStore = SPARQLModule.getOntologyStoreInstance();
List<PropertiesByDomainDTO> propertiesByDomainDTOList = new ArrayList<>();
List<URI> encounteredRdfTypeUris = new ArrayList<>();

if(childrenDomains.size()==1 && SPARQLDeserializers.compareURIs(childrenDomains.get(0), ancestorURI)){
encounteredRdfTypeUris = Collections.singletonList(ancestorURI);
}else{
//Loop over children then fusion common rdf types after
for(URI currentChild : childrenDomains){
LinkedHashSet<String> encounteredRdfTypesFromChildAsStrings = ontologyStore.getAncestorHierarchy(currentChild, ancestorURI);
if(encounteredRdfTypesFromChildAsStrings.isEmpty()){
throw new BadRequestException("The ancestor uri was never encountered from one of the domainUris and up.");
}
List<URI> encounteredRdfTypesFromChild = new ArrayList<>();
for(String typeUriString : encounteredRdfTypesFromChildAsStrings){
encounteredRdfTypesFromChild.add(new URI(typeUriString));
}
encounteredRdfTypesFromChild.removeAll(encounteredRdfTypeUris);
encounteredRdfTypeUris.addAll(encounteredRdfTypesFromChild);
}
encounteredRdfTypeUris.addAll(childrenDomains);
}

//Part 2 : now that we have the hierarchy of classes, get the properties from the ancestor, and at each level until domainUri
//Get by restrictions ad return trees only that match the restrictions
Set<String> restrictionPropertiesFromSuperClassAndUnder = ontologyStore.getOwlRestrictionsUris(ancestorURI, true);

Set<URI> visitedProperties = new HashSet<>();
BiPredicate<DatatypePropertyModel, ClassModel> dataPropFilter = ((property, classModel) ->
property.getRangeURI() != null &&
restrictionPropertiesFromSuperClassAndUnder.contains(SPARQLDeserializers.getShortURI(property.getUri())));
BiPredicate<ObjectPropertyModel, ClassModel> objectPropFilter = ((property, classModel) ->
property.getRangeURI() != null &&
restrictionPropertiesFromSuperClassAndUnder.contains(SPARQLDeserializers.getShortURI(property.getUri())));
for(int i = 0 ; i<encounteredRdfTypeUris.size() ; i++){
URI currentRdfType = encounteredRdfTypeUris.get(i);
List<ResourceTreeDTO> propertiesForCurrentRdfType = ResourceTreeDTO.fromResourceTree(Arrays.asList(
ontologyStore.searchDataProperties(currentRdfType, null, currentUser.getLanguage(), false, dataPropFilter),
ontologyStore.searchObjectProperties(currentRdfType, null, currentUser.getLanguage(), false, objectPropFilter)));
Set<ResourceTreeDTO> nonVisitedPropertiesForCurrentRdfType = new HashSet<>();
for(ResourceTreeDTO nextResourceTreeDTO : propertiesForCurrentRdfType){
if(nextResourceTreeDTO.allMatch(tree -> visitedProperties.contains(tree.getUri()))){
continue;
}
nextResourceTreeDTO.visit((e -> visitedProperties.add(e.getUri())), true);
nonVisitedPropertiesForCurrentRdfType.add(nextResourceTreeDTO);
}
propertiesByDomainDTOList.add(new PropertiesByDomainDTO(currentRdfType, new ArrayList<>(nonVisitedPropertiesForCurrentRdfType)));
}

return new PaginatedListResponse<>(propertiesByDomainDTOList).getResponse();
}

@GET
@Path("/data_properties")
@ApiOperation("Search data properties tree")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.opensilex.core.ontology.api;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.opensilex.sparql.response.ResourceTreeDTO;

import java.net.URI;
import java.util.List;

public class PropertiesByDomainDTO {

PropertiesByDomainDTO(URI domain, List<ResourceTreeDTO> properties){
this.domain = domain;
this.properties = properties;
}

@JsonProperty("domain")
URI domain;

@JsonProperty("properties")
List<ResourceTreeDTO> properties;

public URI getDomain() {
return domain;
}

public void setDomain(URI domain) {
this.domain = domain;
}

public List<ResourceTreeDTO> getProperties() {
return properties;
}

public void setProperties(List<ResourceTreeDTO> properties) {
this.properties = properties;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ export default class OntologyObjectForm extends Vue {
baseType = null;
typeSwitch(type: string, initialLoad: boolean) {
async typeSwitch(type: string, initialLoad: boolean) {
if(this.ontologyRelationsForm){
this.ontologyRelationsForm.typeSwitch(type, initialLoad);
await this.ontologyRelationsForm.typeSwitch(type, initialLoad);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import Vue from "vue";
import {VueJsOntologyExtensionService, VueRDFTypeDTO, VueRDFTypePropertyDTO} from "../../lib";
import OpenSilexVuePlugin from "../../models/OpenSilexVuePlugin";
import {MultiValuedRDFObjectRelation} from "./models/MultiValuedRDFObjectRelation";
import { RDFObjectRelationDTO } from 'opensilex-core/index';
import {PropertiesByDomainDTO, RDFObjectRelationDTO } from 'opensilex-core/index';
import { createUriListFromGetPropertiesResult, sortProperties } from './OntologyTools';
import {OntologyService} from "opensilex-core/api/ontology.service";
@Component
/**
Expand All @@ -39,6 +41,7 @@ export default class OntologyRelationsForm extends Vue {
$opensilex: OpenSilexVuePlugin;
vueOntologyService: VueJsOntologyExtensionService;
ontologyService: OntologyService;
/**
* Initial relations, multi-valued are decomposed into multiple mono-valued relation.
Expand Down Expand Up @@ -83,6 +86,8 @@ export default class OntologyRelationsForm extends Vue {
*/
typeModel: VueRDFTypeDTO = null;
propertiesByDomainHierarchy: PropertiesByDomainDTO[] = [];
/**
* The model graph/context. Can be passed in some custom component has a "context" property
*/
Expand Down Expand Up @@ -115,6 +120,7 @@ export default class OntologyRelationsForm extends Vue {
created() {
this.vueOntologyService = this.$opensilex.getService("opensilex.VueJsOntologyExtensionService");
this.ontologyService = this.$opensilex.getService("opensilex.OntologyService");
this.internalRelations = [];
}
Expand All @@ -141,7 +147,24 @@ export default class OntologyRelationsForm extends Vue {
.concat(this.typeModel.object_properties)
.filter(property => ! shortExcludedProperties.has(this.$opensilex.getShortUri(property.uri)));
return this.sortProperties(properties);
//If no order has ever been set then use the default one (ordered by properties from basest type first)
if(! this.typeModel.properties_order || this.typeModel.properties_order.length === 0){
let newPropertyOrder: Array<VueRDFTypePropertyDTO> = [];
for(let propertiesByDomainDTO of this.propertiesByDomainHierarchy){
let propsOfDomainsUris: Array<string> = createUriListFromGetPropertiesResult(propertiesByDomainDTO.properties, this.$opensilex);
propsOfDomainsUris.forEach(propertyUri => {
let filteredByCurrentProperty = properties.filter(e => this.$opensilex.checkURIs(e.uri, propertyUri));
if(filteredByCurrentProperty.length === 1){
newPropertyOrder.push(filteredByCurrentProperty[0]);
}
});
}
//Add any properties that weren't returned by propsOfDomainsUris
let visitedPropsUris = newPropertyOrder.map(property => this.$opensilex.getShortUri(property.uri));
newPropertyOrder.push(...properties.filter(property => !visitedPropsUris.includes(this.$opensilex.getShortUri(property.uri))));
return newPropertyOrder;
}
return sortProperties(properties, this.typeModel, this.$opensilex);
}
/**
Expand Down Expand Up @@ -170,83 +193,40 @@ export default class OntologyRelationsForm extends Vue {
* @param initialLoad if true, then {@link internalRelations} are initialized according {@link relations}
*
*/
typeSwitch(type: string, initialLoad: boolean) {
async typeSwitch(type: string, initialLoad: boolean) {
// only in create mode, since in update mode, the type can't be changed
if (!type || type.length == 0) {
return;
}
return this.vueOntologyService
.getRDFTypeProperties(type, this.baseType)
.then(http => {
this.typeModel = http.response.result;
this.internalRelations.splice(0);
if (initialLoad) {
this.internalRelations.push(...this.toMultiValuedRelations(this.relations));
} else {
this.internalRelations.push(...this.getHandledProperties().map(property => {
return {
property: property,
value: property.is_list ? [] : undefined
}
}));
}
if (this.initHandler) {
this.internalRelations.forEach(relation => {
this.initHandler.apply(this, [relation]);
});
}
// #TODO sort properties according typeModel order
}).catch(this.$opensilex.errorHandler);
}
/**
* @param properties properties to sort according {@link typeModel} {@link VueRDFTypeDTO#properties_order}
* @return sorted properties if {@link typeModel} has a non null or empty {@link VueRDFTypeDTO#properties_order}, return properties else
*/
sortProperties(properties: Array<VueRDFTypePropertyDTO>): Array<VueRDFTypePropertyDTO>{
if(! this.typeModel.properties_order || this.typeModel.properties_order.length == 0){
return properties;
try{
//Call service to work out which properties come from where
let propertiesByDomainHttpResponse = await this.ontologyService.getPropertiesByDomainHierarchyUsingRestrictions(this.baseType, [type]);
this.propertiesByDomainHierarchy = propertiesByDomainHttpResponse.response.result;
let vueRdfTypeResponse = await this.vueOntologyService.getRDFTypeProperties(type, this.baseType);
this.typeModel = vueRdfTypeResponse.response.result;
this.internalRelations.splice(0);
if (initialLoad) {
this.internalRelations.push(...this.toMultiValuedRelations(this.relations));
} else {
this.internalRelations.push(...this.getHandledProperties().map(property => {
return {
property: property,
value: property.is_list ? [] : undefined
}
}));
}
if (this.initHandler) {
this.internalRelations.forEach(relation => {
this.initHandler.apply(this, [relation]);
});
}
}catch(e){
this.$opensilex.errorHandler(e);
}
return properties.sort((propModel1, propModel2) => {
let property1 = propModel1.uri;
let property2 = propModel2.uri;
if (property1 == property2) {
return 0;
}
// always put name (rdfs:label) in first
if (this.$opensilex.checkURIs(property1, this.$opensilex.Rdfs.LABEL)) {
return -1;
}
if (this.$opensilex.checkURIs(property2, this.$opensilex.Rdfs.LABEL)) {
return 1;
}
let aIndex = this.typeModel.properties_order.indexOf(property1);
let bIndex = this.typeModel.properties_order.indexOf(property2);
if (aIndex == -1) {
if (bIndex == -1) {
return property1.localeCompare(property2);
} else {
return -1;
}
} else {
if (bIndex == -1) {
return 1;
} else {
return aIndex - bIndex;
}
}
});
}
/**
Expand Down
90 changes: 90 additions & 0 deletions opensilex-front/front/src/components/ontology/OntologyTools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {VueRDFTypeDTO, VueRDFTypePropertyDTO} from "../../lib";
import OpenSilexVuePlugin from "../../models/OpenSilexVuePlugin";
import {ResourceTreeDTO} from "opensilex-core/model/resourceTreeDTO";

interface NameAndUri{
name: string,
uri: string
}

/**
* @param properties properties to sort according {@link typeModel} {@link VueRDFTypeDTO#properties_order}
* @param typeModel the type from which the properties originate from
* @param opensilex opensilexVuePlugin
* @return sorted properties if {@link typeModel} has a non null or empty {@link VueRDFTypeDTO#properties_order}, return properties else
* Precondition : typeModel has a properties order
*/
export function sortProperties(properties: Array<VueRDFTypePropertyDTO>, typeModel: VueRDFTypeDTO, opensilex: OpenSilexVuePlugin): Array<VueRDFTypePropertyDTO>{

//Create property-index map from the properties_order list to use during the sort
let propertyToOrderIndexMap : Map<string, number> = new Map<string, number>();

for(let orderIndex = 0; orderIndex < typeModel.properties_order.length; orderIndex++){
propertyToOrderIndexMap.set(typeModel.properties_order[orderIndex], orderIndex);
}
return properties.sort((propModel1, propModel2) => {
let property1 = propModel1.uri;
let property2 = propModel2.uri;
if (property1 === property2) {
return 0;
}

// always put name (rdfs:label) in first
if (opensilex.checkURIs(property1, opensilex.Rdfs.LABEL)) {
return -1;
}

if (opensilex.checkURIs(property2, opensilex.Rdfs.LABEL)) {
return 1;
}

let aIndex = propertyToOrderIndexMap.get(property1);
let bIndex = propertyToOrderIndexMap.get(property2);

if (aIndex === -1) {
if (bIndex === -1) {
return property1.localeCompare(property2);
} else {
return -1;
}
} else {
if (bIndex === -1) {
return 1;
} else {
return aIndex - bIndex;
}
}
});
}

/**
* Creates a list of every property uri that appears in a list of ResourceTreeDTOs, ordered by name
*/
export function createUriListFromGetPropertiesResult(propertyList : Array<ResourceTreeDTO>, opensilex: OpenSilexVuePlugin) : Array<string>{
let nameUriUnorderedList: Array<NameAndUri> = createNameUriListFromGetPropertiesResult(propertyList, opensilex);
let nameUriOrderedList: Array<NameAndUri> = nameUriUnorderedList.sort((a, b) => {
if(!a.name){
return 1;
}
if(!b.name){
return -1;
}
return a.name.localeCompare(b.name);
});
return nameUriOrderedList.map(e => e.uri);
}

/**
* Recursive function to look in children, children of children, etc...
* To create a list of every {uri, name} of every property that appears in an array of ResourceTreeDTOs
*/
function createNameUriListFromGetPropertiesResult(propertyList : Array<ResourceTreeDTO>, opensilex: OpenSilexVuePlugin) : Array<NameAndUri>{
let result = [];
propertyList.forEach(e => {
result.push({uri: opensilex.getShortUri(e.uri), name: e.name});
if(e.children && e.children.length > 0){
result.push(...createNameUriListFromGetPropertiesResult(e.children, opensilex));
}
});
return result;
}
Loading

0 comments on commit 3bbf00e

Please sign in to comment.