All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.spdx.tools.schema.AbstractOwlRdfConverter Maven / Gradle / Ivy

There is a newer version: 2.0.0-Alpha
Show newest version
/**
 * Copyright (c) 2020 Source Auditor Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 * 
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package org.spdx.tools.schema;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import org.apache.jena.ontology.Individual;
import org.apache.jena.ontology.OntClass;
import org.apache.jena.ontology.OntModel;
import org.apache.jena.ontology.OntProperty;
import org.apache.jena.ontology.OntResource;
import org.apache.jena.ontology.Ontology;
import org.apache.jena.ontology.Restriction;
import org.apache.jena.ontology.UnionClass;
import org.apache.jena.rdf.model.NodeIterator;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.util.iterator.ExtendedIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spdx.library.SpdxConstants;
import org.spdx.library.model.enumerations.SpdxEnumFactory;

/**
 * Abstract class for implementing classes which convert from RDF/XML OWL format to some other format
 * 
 * @author Gary O'Neall
 *
 */
public class AbstractOwlRdfConverter {
	
	static final Logger logger = LoggerFactory.getLogger(AbstractOwlRdfConverter.class);
	static final Set SKIPPED_PROPERTIES;
	static {
		Set skipped = new HashSet<>();
		skipped.add("http://www.w3.org/2003/06/sw-vocab-status/ns#term_status");
		skipped.add("http://www.w3.org/2002/07/owl#qualifiedCardinality");
		skipped.add("http://www.w3.org/2002/07/owl#deprecatedProperty");
		skipped.add("http://www.w3.org/2002/07/owl#deprecatedClass");
		skipped.add(SpdxConstants.SPDX_NAMESPACE + "describesPackage");   // This is an old deprecated field from 1.0 which should be ignored - it was only used in RDF format
		SKIPPED_PROPERTIES = Collections.unmodifiableSet(skipped);
	}
	
	/**
	 * Map of the properties renamed due to spec inconsistencies between the RDF format and other formats
	 */
	public static final Map RENAMED_PROPERTY_TO_OWL_PROPERTY;
	public static final Map OWL_PROPERTY_TO_RENAMED_PROPERTY;
	static {
		Map renamedToOwl = new HashMap<>();
		Map owlToRenamed = new HashMap<>();
		renamedToOwl.put(SpdxConstants.PROP_SPDX_SPEC_VERSION, SpdxConstants.PROP_SPDX_VERSION);
		owlToRenamed.put(SpdxConstants.PROP_SPDX_VERSION, SpdxConstants.PROP_SPDX_SPEC_VERSION);
		RENAMED_PROPERTY_TO_OWL_PROPERTY = Collections.unmodifiableMap(renamedToOwl);
		OWL_PROPERTY_TO_RENAMED_PROPERTY = Collections.unmodifiableMap(owlToRenamed);
	}
	
	/**
	 * @author Gary O'Neall
	 * 
	 * Holds information on the property restrictions
	 *
	 */
	class PropertyRestrictions {

		private OntProperty property;
		private boolean listProperty = false;
		private boolean singleProperty = false;
		private String typeUri = null;
		private int absoluteCardinality = -1;
		private int minCardinality = -1;
		private int maxCardinality = -1;
		private boolean optional = true;
		private boolean enumProperty = false;
		Set enumValues = new HashSet<>();

		public PropertyRestrictions(OntClass ontClass, OntProperty property) {
			Objects.requireNonNull(ontClass, "Missing required ontology class");
			Objects.requireNonNull(property, "Missing required property");
			this.property = property;
			List restrictions = getRestrictionsFromSuperclasses(ontClass, property);
			interpretRestrictions(restrictions);
		}
		
		/**
		 * Convert a list of restrictions for this property into the field values
		 * @param restrictions
		 */
		private void interpretRestrictions(List restrictions) {
			for (Restriction r:restrictions) {
				RDFNode typePropertyValue = r.getPropertyValue(owlClassProperty);
				if (Objects.nonNull(typePropertyValue) && typePropertyValue.isURIResource()) {
					typeUri = typePropertyValue.asResource().getURI();
					// check to see if this type is an enumeration type
					OntClass typeClass = model.getOntClass(typeUri);
					if (Objects.nonNull(typeClass)) {
						ExtendedIterator individualIter = model.listIndividuals(typeClass);
						while (individualIter.hasNext()) {
							Individual individual = individualIter.next();
							if (individual.isURIResource()) {
								Enum e = SpdxEnumFactory.uriToEnum.get(individual.getURI());
								if (Objects.nonNull(e)) {
									this.enumValues.add(e.toString());
									this.enumProperty = true;
								} else {
									logger.warn("Missing enum value for " + individual.getLocalName());
								}
							}
						}
					}
				} else {
					// Check for enumeration types as a direct restriction
					NodeIterator hasValueIter = r.listPropertyValues(hasValueProperty);
					while (hasValueIter.hasNext()) {
						RDFNode hasValue = hasValueIter.next();
						if (hasValue.isURIResource()) {
							Enum e = SpdxEnumFactory.uriToEnum.get(hasValue.asResource().getURI());
							if (Objects.nonNull(e)) {
								this.enumValues.add(e.toString());
								this.enumProperty = true;
							}
						}
					}
				}
				// cardinality
				RDFNode qualCardPropValue = r.getPropertyValue(qualCardProperty);
				RDFNode cardPropValue = r.getPropertyValue(cardProperty);
				RDFNode maxQualCardPropValue = r.getPropertyValue(maxQualCardProperty);
				RDFNode maxCardPropValue = r.getPropertyValue(maxCardProperty);
				RDFNode minCardPropValue = r.getPropertyValue(minCardProperty);
				RDFNode minQualCardPropValue = r.getPropertyValue(minQualCardProperty);
				if (Objects.nonNull(qualCardPropValue) && qualCardPropValue.isLiteral()) {
					absoluteCardinality = qualCardPropValue.asLiteral().getInt();
					if (absoluteCardinality > 0) {
						optional = false;
					}
					if (absoluteCardinality > 1) {
						listProperty = true;
					} else {
						singleProperty = true;
					}
				} else if (Objects.nonNull(cardPropValue) && cardPropValue.isLiteral()) {
					absoluteCardinality = cardPropValue.asLiteral().getInt();
					if (absoluteCardinality > 0) {
						optional = false;
					}
					if (absoluteCardinality > 1) {
						listProperty = true;
					} else {
						singleProperty = true;
					}
				} else if (Objects.nonNull(maxQualCardPropValue) && maxQualCardPropValue.isLiteral()) {
					maxCardinality = maxQualCardPropValue.asLiteral().getInt();
					if (maxCardinality > 1) {
						listProperty = true;
					} else if (maxCardinality == 1) {
						singleProperty = true;
					}
				} else if (Objects.nonNull(maxCardPropValue) && maxCardPropValue.isLiteral()) {
					maxCardinality = maxCardPropValue.asLiteral().getInt();
					if (maxCardinality > 1) {
						listProperty = true;
					} else if (maxCardinality == 1) {
						singleProperty = true;
					}
				} else if (Objects.nonNull(minCardPropValue) && minCardPropValue.isLiteral()) {
					minCardinality = minCardPropValue.asLiteral().getInt();
					if (minCardinality > 0) {
						optional = false;
					}
					listProperty = true;
				} else if (Objects.nonNull(minQualCardPropValue) && minQualCardPropValue.isLiteral()) {
					minCardinality = minQualCardPropValue.asLiteral().getInt();
					if (minCardinality > 0) {
						optional = false;
					}
					listProperty = true;
				}
			}
			if (Objects.isNull(typeUri)) {
				// get the type from the range of the property
				ExtendedIterator rangeIter = property.listRange();
				while (rangeIter.hasNext()) {
					OntResource range = rangeIter.next();
					if (range.isURIResource()) {
						if (Objects.isNull(typeUri) || typeUri.equals("http://www.w3.org/2000/01/rdf-schema#Literal")) {
							typeUri = range.asResource().getURI();
						}
					}
				}
			}
			if (Objects.isNull(typeUri) && ("comment".equals(property.getLocalName()) || "seeAlso".equals(property.getLocalName()))) {
				// A bit of a hack, the schema file can not store the type of rdfs:comment, so we must override it to xsd:string
				typeUri = SpdxConstants.XML_SCHEMA_NAMESPACE + "string";
			}
		}

		private List getRestrictionsFromSuperclasses(OntClass ontClass, OntProperty property) {
			List retval = new ArrayList<>();
			ExtendedIterator superClasses = ontClass.listSuperClasses();
			while (superClasses.hasNext()) {
				OntClass superClass = superClasses.next();
				if (superClass.isUnionClass()) {
		            UnionClass uClass = superClass.asUnionClass();
		            ExtendedIterator unionClassiter = uClass.listOperands();
		            while (unionClassiter.hasNext()) {
		                OntClass operand = unionClassiter.next();
		                if (operand.isRestriction() && property.equals(operand.asRestriction().getOnProperty())) {
		                    retval.add(operand.asRestriction());
		                }
		            }
		        } else if (superClass.isRestriction()) {
					if (property.equals(superClass.asRestriction().getOnProperty())) {
						retval.add(superClass.asRestriction());
					}
				} else {
					retval.addAll(getRestrictionsFromSuperclasses(superClass, property));
				}
			}
			return retval;
		}

		public PropertyRestrictions(OntProperty property) {
			Objects.requireNonNull(property, "Missing required property");
			this.property = property;
			List propertyRestrictions = new ArrayList<>();
			ExtendedIterator restrictionIter = property.listReferringRestrictions();
			while (restrictionIter.hasNext()) {
				propertyRestrictions.add(restrictionIter.next());
			}
			interpretRestrictions(propertyRestrictions);
		}
		/**
		 * @return the property
		 */
		public OntProperty getProperty() {
			return property;
		}

		/**
		 * @return the isListProperty
		 */
		public boolean isListProperty() {
			return listProperty;
		}

		/**
		 * @return the typeUri
		 */
		public String getTypeUri() {
			return typeUri;
		}

		/**
		 * @return the absoluteCardinality
		 */
		public int getAbsoluteCardinality() {
			return absoluteCardinality;
		}

		/**
		 * @return the minCardinality
		 */
		public int getMinCardinality() {
			return minCardinality;
		}

		/**
		 * @return the maxCardinality
		 */
		public int getMaxCardinality() {
			return maxCardinality;
		}

		/**
		 * @return the optional
		 */
		public boolean isOptional() {
			return optional;
		}

		/**
		 * @return the enumProperty
		 */
		public boolean isEnumProperty() {
			return enumProperty;
		}

		/**
		 * @return the enumValues
		 */
		public Set getEnumValues() {
			return enumValues;
		}

		/**
		 * @return the singleProperty
		 */
		public boolean isSingleProperty() {
			return singleProperty;
		}
	}

	protected OntModel model;
	Property qualCardProperty;
	Property maxQualCardProperty;
	Property maxCardProperty;
	Property cardProperty;
	Property minCardProperty;
	Property minQualCardProperty;
	Property owlClassProperty;
	Property hasValueProperty;
	Property commentProperty;
	Property unionOfProperty;
	Property firstResource;
	Property restResource;
	Property intersectionOfProperty;
	
	Ontology ontology;

	public AbstractOwlRdfConverter(OntModel model) {
		Objects.requireNonNull(model, "Model must not be null");
		this.model = model;
		qualCardProperty = model.createProperty("http://www.w3.org/2002/07/owl#qualifiedCardinality");
		maxQualCardProperty = model.createProperty("http://www.w3.org/2002/07/owl#maxQualifiedCardinality");
		maxCardProperty = model.createProperty("http://www.w3.org/2002/07/owl#maxCardinality");
		cardProperty = model.createProperty("http://www.w3.org/2002/07/owl#cardinality");
		minCardProperty = model.createProperty("http://www.w3.org/2002/07/owl#minCardinality");
		minQualCardProperty = model.createProperty("http://www.w3.org/2002/07/owl#minQualifiedCardinality");
		owlClassProperty = model.createProperty("http://www.w3.org/2002/07/owl#onClass");
		hasValueProperty = model.createProperty("http://www.w3.org/2002/07/owl#hasValue");
		commentProperty = model.createProperty("http://www.w3.org/2000/01/rdf-schema#comment");
		unionOfProperty = model.createProperty("http://www.w3.org/2002/07/owl#unionOf");
		intersectionOfProperty = model.createProperty("http://www.w3.org/2002/07/owl#intersectionOf");
		firstResource = model.createProperty("http://www.w3.org/1999/02/22-rdf-syntax-ns#first");
		restResource = model.createProperty("http://www.w3.org/1999/02/22-rdf-syntax-ns#rest");
		ontology = null;
		ExtendedIterator ontIter = model.listOntologies();
		if (!ontIter.hasNext()) {
			throw new RuntimeException("No ontologies defined in RDF OWL");
		}
		ontology = ontIter.next();
		if (ontIter.hasNext()) {
			throw new RuntimeException("No ontologies defined in RDF OWL");
		}
	}
	
	public PropertyRestrictions getPropertyRestrictions(OntClass ontClass, OntProperty property) {
		return new PropertyRestrictions(ontClass, property);
	}

	/**
	 * Checks to see if an OWL property name has been renamed for the JSON/YAML formats
	 * @param owlPropertyName
	 * @return renamed property if it was renamed, owlPropertyName if not renamed
	 */
	public static String checkConvertRenamedPropertyName(String owlPropertyName) {
		if (OWL_PROPERTY_TO_RENAMED_PROPERTY.containsKey(owlPropertyName)) {
			return OWL_PROPERTY_TO_RENAMED_PROPERTY.get(owlPropertyName);
		} else {
			return owlPropertyName;
		}
	}
	
	/**
	 * @param oClass
	 * @param excludeSuperClassProperties if true, only return properties for the class but not any superclasses
	 * @return collection of all properties which have a restriction on the class or superclasses if not direct
	 */
	protected Collection propertiesFromClassRestrictions(OntClass oClass, boolean excludeSuperClassProperties) {
		Collection properties = new HashSet<>();
		Collection reviewedClasses = new HashSet<>();
		collectPropertiesFromRestrictions(oClass, properties, reviewedClasses, excludeSuperClassProperties);
		removeSuperProperties(properties);
		ArrayList sorted = new ArrayList<>(properties);
		Collections.sort(sorted, new Comparator() {

			@Override
			public int compare(OntProperty arg0, OntProperty arg1) {
				return arg0.getLocalName().compareToIgnoreCase(arg1.getLocalName());
			}
		    
		});
		return sorted;
	}
	
	/**
	 * Removes and properties which have a sub-property present in the properties list
     * @param properties
     */
    private void removeSuperProperties(Collection properties) {
        List superProperties = new ArrayList<>();
        for (OntProperty property:properties) {
            if (property.isProperty()) {
                OntProperty op = property.asProperty();
                ExtendedIterator superIter = op.listSuperProperties();
                while (superIter.hasNext()) {
                    superProperties.add(superIter.next());
                }
            }
        }
        for (OntProperty superProp:superProperties) {
            if (properties.contains(superProp)) {
                properties.remove(superProp);
            }
        }
    }

    /**
	 * @param oClass
	 * @return collection of all properties which have a restriction on the class or superclasses
	 */
	protected Collection propertiesFromClassRestrictions(OntClass oClass) {
		return propertiesFromClassRestrictions(oClass, false);
	}
	
	/**
	 * @param property
	 * @return The class or type for the property
	 * @throws SchemaException
	 */
	protected Optional getPropertyType(OntProperty property) throws SchemaException {
		ExtendedIterator rangeIter = property.listRange();
		while (rangeIter.hasNext()) {
			OntResource range = rangeIter.next();
			if (range.isURIResource()) {
				return Optional.of(range);
			} else if (range.hasProperty(unionOfProperty)) {
				// Search for a type
				Resource unionOf = range.getPropertyResourceValue(unionOfProperty);
				Optional retval = findTypeInCollection(unionOf.getPropertyResourceValue(firstResource),
						unionOf.getPropertyResourceValue(restResource));
				if (retval.isPresent()) {
					return retval;
				}
			} else if (range.hasProperty(intersectionOfProperty)) {
				// Search for a type
				Resource intersectionOf = range.getPropertyResourceValue(intersectionOfProperty);
				Optional retval = findTypeInCollection(intersectionOf.getPropertyResourceValue(firstResource),
						intersectionOf.getPropertyResourceValue(restResource));
				if (retval.isPresent()) {
					return retval;
				}
			}
		}
		return Optional.empty();
	}

	/**
	 * Search a collection for a URI value assumed to be the type
	 * @param first first element in the collection
	 * @param rest rest of the collection
	 * @return
	 */
	private Optional findTypeInCollection(Resource first, Resource rest) {
		if (Objects.nonNull(first) && first.isURIResource()) {
			return Optional.of(first);
		} else if (Objects.isNull(rest) || "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil".equals(rest.getURI())) {
			return Optional.empty();
		} else {
			return findTypeInCollection(rest.getPropertyResourceValue(firstResource),
					rest.getPropertyResourceValue(restResource));
		}
	}

	/**
	 * Collects any properties used in restrictions and adds them to the properties collection - includes all subclasses
	 * @param oClass Class to collect properties from
	 * @param properties collection of any properties found
	 * @param reviewedClasses collection of classes already reviewed - used to prevent infinite recursion
	 * @param excludeSuperClassProperties if true, only collect properties for the class and not the superclasses
	 */
	private void collectPropertiesFromRestrictions(OntClass oClass, 
			Collection properties, Collection reviewedClasses, boolean excludeSuperClassProperties) {
		if (reviewedClasses.contains(oClass)) {
			return;
		}
		reviewedClasses.add(oClass);
		if (oClass.isUnionClass()) {
		    UnionClass uClass = oClass.asUnionClass();
		    ExtendedIterator unionClassiter = uClass.listOperands();
		    while (unionClassiter.hasNext()) {
		        collectPropertiesFromRestrictions(unionClassiter.next(), properties, 
		                reviewedClasses, excludeSuperClassProperties);
		    }
		} else if (oClass.isRestriction()) {
			Restriction r = oClass.asRestriction();
			OntProperty property = r.getOnProperty();
			if (Objects.nonNull(property)) {
				properties.add(property);
			}
		} else {
			ExtendedIterator subClassIter = oClass.listSuperClasses(excludeSuperClassProperties);
			if (excludeSuperClassProperties) {
				subClassIter = subClassIter.filterDrop(sc -> {
					return sc.isURIResource() && 
							!"http://www.w3.org/2000/01/rdf-schema#Container".equals(sc.getURI());
				});
			}			
			while (subClassIter.hasNext()) {
				collectPropertiesFromRestrictions(subClassIter.next(), properties, reviewedClasses, excludeSuperClassProperties);
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy