Introduction
Semantic annotations were created as a means of standardisation to provide interoperability between systems.
Different systems can then name the same kinds of data differently, but providing the same semantic annotations systems could theoretically interoperate.
One of the biggest hurdles to overcome when trying to understand semantic annotations is the nomenclature. There is a plethora of information often using different terms.
We will now introduce the main three nomenclatures to help the reader to familiarise with them.
Classic Nomenclature
This is typically found in literature.
For reference: http://www.linkeddatatools.com/semantic-web-basics
Modern Nomenclature
This is typically found in standards.
For reference: https://www.biomedit.ch/rdf/sphn-ontology/sphn/2022/2
openBIS Nomenclature
This is typically found in systems or programming languages.
openBIS Implementation : Java Examples
Use Case 1 : Annotating a Semantic Class corresponds to Annotating an openBIS Type
openBIS allows you to annotate Types using semantic annotation URIs through the use of its API using SemanticAnnotationCreation.
In the Example below:
The openBIS Sample Type ADMINISTRATIVE_GENDER is annotated with the semantic class AdministrativeGender.
package ch.ethz.sis.pat; import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.EntityKind; import ch.ethz.sis.openbis.generic.asapi.v3.dto.semanticannotation.create.SemanticAnnotationCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.semanticannotation.id.SemanticAnnotationPermId; import ch.systemsx.cisd.common.spring.HttpInvokerUtils; import java.util.List; import java.util.Map; public class MainSemantic { private static final String URL = "https://openbis-sis-ci-sprint.ethz.ch/openbis/openbis" + IApplicationServerApi.SERVICE_URL; private static final int TIMEOUT = 10000; private static final String USER = "admin"; private static final String PASSWORD = "changeit"; public static void main(String[] args) { IApplicationServerApi v3 = HttpInvokerUtils.createServiceStub(IApplicationServerApi.class, URL, TIMEOUT); String sessionToken = v3.login(USER, PASSWORD); System.out.println("sessionToken: " + sessionToken); // Creating semantic annotations using helper methods SemanticAnnotationCreation administrative_gender = ApplicationServerSemanticAPIExtensions.getSemanticSubjectCreation( EntityKind.SAMPLE, "ADMINISTRATIVE_GENDER", "https://biomedit.ch/rdf/sphn-ontology/sphn", "https://biomedit.ch/rdf/sphn-ontology/sphn/2022/2", "https://biomedit.ch/rdf/sphn-ontology/sphn#AdministrativeGender"); List<SemanticAnnotationPermId> annotations = v3.createSemanticAnnotations(sessionToken, List.of(administrative_gender)); System.out.println("created annotations: " + annotations); v3.logout(sessionToken); } }
Use Case 2 : Annotating a Semantic Class Property corresponds to Annotating an openBIS Property Assignment
openBIS allows you to annotate Property Assignments using semantic annotation URIs through the use of its API using SemanticAnnotationCreation.
In the Example below:
The openBIS Property Type IDENTIFIER of the Type ADMINISTRATIVE_GENDER is annotated with the semantic property hasIdentifier.
package ch.ethz.sis.pat; import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.EntityKind; import ch.ethz.sis.openbis.generic.asapi.v3.dto.semanticannotation.create.SemanticAnnotationCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.semanticannotation.id.SemanticAnnotationPermId; import ch.systemsx.cisd.common.spring.HttpInvokerUtils; import java.util.List; import java.util.Map; public class MainSemantic { private static final String URL = "https://openbis-sis-ci-sprint.ethz.ch/openbis/openbis" + IApplicationServerApi.SERVICE_URL; private static final int TIMEOUT = 10000; private static final String USER = "admin"; private static final String PASSWORD = "changeit"; public static void main(String[] args) { IApplicationServerApi v3 = HttpInvokerUtils.createServiceStub(IApplicationServerApi.class, URL, TIMEOUT); String sessionToken = v3.login(USER, PASSWORD); System.out.println("sessionToken: " + sessionToken); SemanticAnnotationCreation identifier = ApplicationServerSemanticAPIExtensions.getSemanticPredicateWithSubjectCreation( EntityKind.SAMPLE, "ADMINISTRATIVE_GENDER", "identifier", "https://biomedit.ch/rdf/sphn-ontology/sphn", "https://biomedit.ch/rdf/sphn-ontology/sphn/2022/2", "https://biomedit.ch/rdf/sphn-ontology/sphn#hasIdentifier"); List<SemanticAnnotationPermId> annotations = v3.createSemanticAnnotations(sessionToken, List.of(identifier)); System.out.println("created annotations: " + annotations); v3.logout(sessionToken); } }
Use Case 3 : Annotating a Semantic Property corresponds to Annotating an openBIS Property
Even if less common, sometimes we can be under the need of annotating a predicate without a subject.
openBIS allows you to annotate Property Types without Types using semantic annotation URIs through the use of its API using SemanticAnnotationCreation.
In the Example below:
The openBIS Property Type IDENTIFIER is annotated with the semantic property hasIdentifier.
package ch.ethz.sis.pat; import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.EntityKind; import ch.ethz.sis.openbis.generic.asapi.v3.dto.semanticannotation.create.SemanticAnnotationCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.semanticannotation.id.SemanticAnnotationPermId; import ch.systemsx.cisd.common.spring.HttpInvokerUtils; import java.util.List; import java.util.Map; public class MainSemantic { private static final String URL = "https://openbis-sis-ci-sprint.ethz.ch/openbis/openbis" + IApplicationServerApi.SERVICE_URL; private static final int TIMEOUT = 10000; private static final String USER = "admin"; private static final String PASSWORD = "changeit"; public static void main(String[] args) { IApplicationServerApi v3 = HttpInvokerUtils.createServiceStub(IApplicationServerApi.class, URL, TIMEOUT); String sessionToken = v3.login(USER, PASSWORD); System.out.println("sessionToken: " + sessionToken); SemanticAnnotationCreation identifierOnly = ApplicationServerSemanticAPIExtensions.getSemanticPredicateCreation( "identifier", "https://biomedit.ch/rdf/sphn-ontology/sphn", "https://biomedit.ch/rdf/sphn-ontology/sphn/2022/2", "https://biomedit.ch/rdf/sphn-ontology/sphn#hasIdentifier"); List<SemanticAnnotationPermId> annotations = v3.createSemanticAnnotations(sessionToken, List.of(identifierOnly)); System.out.println("created annotations: " + annotations); v3.logout(sessionToken); } }
Search Based on Semantic Annotations
openBIS search doesn't directly allow a search based on semantic annotations. This is currently a two steps process.
On the next example we reduce this two step process to a single call for what we believe is the most common use case: Search for subject and predicate of a well known ontology.
We build this example on top of the previous example. We would like now to make a search based on the previously created subject (AdministrativeGender) and predicate (hasIdentifier) to finally obtain Samples having the identifier "12345678".
The reader will appreciate that this search can be done without having previous knowledge of how this semantic annotations map to openBIS types and property types. See example below.
package ch.ethz.sis.pat; import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.EntityKind; import ch.ethz.sis.openbis.generic.asapi.v3.dto.semanticannotation.create.SemanticAnnotationCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.semanticannotation.id.SemanticAnnotationPermId; import ch.systemsx.cisd.common.spring.HttpInvokerUtils; import java.util.List; import java.util.Map; public class MainSemantic { private static final String URL = "https://openbis-sis-ci-sprint.ethz.ch/openbis/openbis" + IApplicationServerApi.SERVICE_URL; private static final int TIMEOUT = 10000; private static final String USER = "admin"; private static final String PASSWORD = "changeit"; public static void main(String[] args) { IApplicationServerApi v3 = HttpInvokerUtils.createServiceStub(IApplicationServerApi.class, URL, TIMEOUT); String sessionToken = v3.login(USER, PASSWORD); System.out.println("sessionToken: " + sessionToken); // Searching semantic annotations using helper methods SearchResult searchResult = ApplicationServerSemanticAPIExtensions.searchEntityWithSemanticAnnotations(v3, sessionToken, EntityKind.SAMPLE, "https://biomedit.ch/rdf/sphn-ontology/sphn#AdministrativeGender", Map.of("https://biomedit.ch/rdf/sphn-ontology/sphn#hasIdentifier", "12345678"), 0, Integer.MAX_VALUE ); System.out.println("Found Entities: " + searchResult.getTotalCount()); v3.logout(sessionToken); } }
Helper Class - Semantic API Extensions
To facilitate previous straightforward examples we have created the next utility class:
package ch.ethz.sis.pat; import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.fetchoptions.FetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.AbstractEntitySearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions.DataSetFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.search.DataSetSearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.EntityKind; import ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id.EntityTypePermId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.fetchoptions.ExperimentFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.search.ExperimentSearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.fetchoptions.PropertyAssignmentFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.id.PropertyAssignmentPermId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.property.id.PropertyTypePermId; import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.fetchoptions.SampleFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.search.SampleSearchCriteria; import ch.ethz.sis.openbis.generic.asapi.v3.dto.semanticannotation.SemanticAnnotation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.semanticannotation.create.SemanticAnnotationCreation; import ch.ethz.sis.openbis.generic.asapi.v3.dto.semanticannotation.fetchoptions.SemanticAnnotationFetchOptions; import ch.ethz.sis.openbis.generic.asapi.v3.dto.semanticannotation.search.SemanticAnnotationSearchCriteria; import ch.systemsx.cisd.common.exceptions.UserFailureException; import java.util.Map; public abstract class ApplicationServerSemanticAPIExtensions { /** * This utility method provides a simplified API to create subject semantic annotations * */ public static SemanticAnnotationCreation getSemanticSubjectCreation( EntityKind subjectEntityKind, String subjectClass, String subjectClassOntologyId, String subjectClassOntologyVersion, String subjectClassId) { SemanticAnnotationCreation semanticAnnotationCreation = new SemanticAnnotationCreation(); // Subject: Type matching an ontology class semanticAnnotationCreation.setEntityTypeId(new EntityTypePermId(subjectClass, subjectEntityKind)); // Ontology URL semanticAnnotationCreation.setPredicateOntologyId(subjectClassOntologyId); semanticAnnotationCreation.setDescriptorOntologyId(subjectClassOntologyId); // Ontology Version URL semanticAnnotationCreation.setPredicateOntologyVersion(subjectClassOntologyVersion); semanticAnnotationCreation.setDescriptorOntologyVersion(subjectClassOntologyVersion); // Ontology Class URL semanticAnnotationCreation.setPredicateAccessionId(subjectClassId); semanticAnnotationCreation.setDescriptorAccessionId(subjectClassId); return semanticAnnotationCreation; } /** * This utility method provides a simplified API to create predicate semantic annotations * */ public static SemanticAnnotationCreation getSemanticPredicateWithSubjectCreation( EntityKind subjectEntityKind, String subjectClass, String predicateProperty, String predicatePropertyOntologyId, String predicatePropertyOntologyVersion, String predicatePropertyId) { SemanticAnnotationCreation semanticAnnotationCreation = new SemanticAnnotationCreation(); // Subject: Type matching an ontology class // Predicate: Property matching an ontology class property semanticAnnotationCreation.setPropertyAssignmentId(new PropertyAssignmentPermId( new EntityTypePermId(subjectClass, subjectEntityKind), new PropertyTypePermId(predicateProperty))); // Ontology URL semanticAnnotationCreation.setPredicateOntologyId(predicatePropertyOntologyId); // Ontology Version URL semanticAnnotationCreation.setPredicateOntologyVersion(predicatePropertyOntologyVersion); // Ontology Property URL semanticAnnotationCreation.setPredicateAccessionId(predicatePropertyId); return semanticAnnotationCreation; } /** * This utility method provides a simplified API to create predicate semantic annotations * */ public static SemanticAnnotationCreation getSemanticPredicateCreation( String predicateProperty, String predicatePropertyOntologyId, String predicatePropertyOntologyVersion, String predicatePropertyId) { SemanticAnnotationCreation semanticAnnotationCreation = new SemanticAnnotationCreation(); // Predicate: Property matching an ontology class property semanticAnnotationCreation.setPropertyTypeId(new PropertyTypePermId(predicateProperty)); // Ontology URL semanticAnnotationCreation.setPredicateOntologyId(predicatePropertyOntologyId); // Ontology Version URL semanticAnnotationCreation.setPredicateOntologyVersion(predicatePropertyOntologyVersion); // Ontology Property URL semanticAnnotationCreation.setPredicateAccessionId(predicatePropertyId); return semanticAnnotationCreation; } /** * This utility method provides a simplified API to search based on semantic subjects and predicates * * @throws UserFailureException in case of any problems */ public static SearchResult searchEntityWithSemanticAnnotations(IApplicationServerApi v3, String sessionToken, EntityKind entityKind, String subjectClassIDOrNull, Map<String, String> predicatePropertyIDsOrNull, Integer fromOrNull, Integer countOrNull) { if (entityKind == null) { throw new UserFailureException("entityKind cannot be null"); } if (entityKind == EntityKind.DATA_SET) { throw new UserFailureException("EntityKind.DATA_SET is not supported"); } // // Part 1 : Translate semantic classes and properties into openBIS types and property types // SemanticAnnotationSearchCriteria semanticCriteria = new SemanticAnnotationSearchCriteria(); semanticCriteria.withOrOperator(); SemanticAnnotationFetchOptions semanticFetchOptions = new SemanticAnnotationFetchOptions(); // Request and collect subjects if (subjectClassIDOrNull != null) { semanticCriteria.withPredicateAccessionId().thatEquals(subjectClassIDOrNull); } semanticFetchOptions.withEntityType(); // Request and collect predicates if (predicatePropertyIDsOrNull != null) { for (String predicate : predicatePropertyIDsOrNull.keySet()) { semanticCriteria.withPredicateAccessionId().thatEquals(predicate); } } PropertyAssignmentFetchOptions propertyAssignmentFetchOptions = semanticFetchOptions.withPropertyAssignment(); propertyAssignmentFetchOptions.withPropertyType(); propertyAssignmentFetchOptions.withEntityType(); SearchResult<SemanticAnnotation> semanticAnnotationSearchResult = v3.searchSemanticAnnotations(sessionToken, new SemanticAnnotationSearchCriteria(), semanticFetchOptions); // // Part 2 : Create openBIS search matching semantic results // AbstractEntitySearchCriteria criteria = getEntitySearchCriteria(entityKind); criteria.withAndOperator(); // Set Subject String entityTypeCode = null; for (SemanticAnnotation semanticAnnotation:semanticAnnotationSearchResult.getObjects()) { if (semanticAnnotation.getEntityType() != null) { EntityTypePermId permId = (EntityTypePermId) semanticAnnotation.getEntityType().getPermId(); if (permId.getEntityKind() == entityKind) { entityTypeCode = semanticAnnotation.getEntityType().getCode(); setWithTypeThatEquals(entityKind, criteria, entityTypeCode); } } } if (entityTypeCode == null) { throw new UserFailureException("Entity Type matching Subject not found."); } // Set Predicates matching the Subject if (predicatePropertyIDsOrNull != null) { int predicatesFound = 0; for (SemanticAnnotation semanticAnnotation : semanticAnnotationSearchResult.getObjects()) { if (semanticAnnotation.getPropertyAssignment() != null && semanticAnnotation.getPropertyAssignment().getEntityType().getCode().equals(entityTypeCode)) { EntityTypePermId permId = (EntityTypePermId) semanticAnnotation.getPropertyAssignment().getEntityType().getPermId(); if (permId.getEntityKind() == entityKind) { String value = predicatePropertyIDsOrNull.get(semanticAnnotation.getPredicateAccessionId()); criteria.withProperty(semanticAnnotation.getPropertyAssignment().getPropertyType().getCode()).thatEquals(value); predicatesFound++; } } } if (predicatesFound != predicatePropertyIDsOrNull.size()) { throw new UserFailureException("Property Types matching Predicates not found."); } } FetchOptions fetchOptions = getEntityFetchOptions(entityKind); if (fromOrNull != null) { fetchOptions.from(fromOrNull); } if (countOrNull != null) { fetchOptions.count(countOrNull); } SearchResult searchResult = getSearchResult(v3, sessionToken, entityKind, criteria, fetchOptions); return searchResult; } private static void setWithTypeThatEquals(EntityKind entityKind, AbstractEntitySearchCriteria criteria, String entityTypeCode) { switch (entityKind) { case EXPERIMENT: ((ExperimentSearchCriteria) criteria).withType().withCode().thatEquals(entityTypeCode); break; case SAMPLE: ((SampleSearchCriteria) criteria).withType().withCode().thatEquals(entityTypeCode); break; case DATA_SET: ((DataSetSearchCriteria) criteria).withType().withCode().thatEquals(entityTypeCode); break; } } private static AbstractEntitySearchCriteria getEntitySearchCriteria(EntityKind entityKind) { AbstractEntitySearchCriteria criteria = null; switch (entityKind) { case EXPERIMENT: criteria = new ExperimentSearchCriteria(); break; case SAMPLE: criteria = new SampleSearchCriteria(); break; case DATA_SET: criteria = new DataSetSearchCriteria(); break; } return criteria; } private static FetchOptions getEntityFetchOptions(EntityKind entityKind) { FetchOptions fetchOptions = null; switch (entityKind) { case EXPERIMENT: fetchOptions = new ExperimentFetchOptions(); break; case SAMPLE: fetchOptions = new SampleFetchOptions(); break; case DATA_SET: fetchOptions = new DataSetFetchOptions(); break; } return fetchOptions; } private static SearchResult getSearchResult(IApplicationServerApi v3, String sessionToken, EntityKind entityKind, AbstractEntitySearchCriteria criteria, FetchOptions fetchOptions) { SearchResult searchResult = null; switch (entityKind) { case EXPERIMENT: searchResult = v3.searchExperiments(sessionToken, (ExperimentSearchCriteria) criteria, (ExperimentFetchOptions) fetchOptions); break; case SAMPLE: searchResult = v3.searchSamples(sessionToken, (SampleSearchCriteria) criteria, (SampleFetchOptions) fetchOptions); break; case DATA_SET: searchResult = v3.searchDataSets(sessionToken, (DataSetSearchCriteria) criteria, (DataSetFetchOptions) fetchOptions); break; } return searchResult; } }