/******************************************************************************* * Copyright 2012 Pearson Education * * 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.semantictools.frame.api; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import org.semantictools.frame.api.impl.DatatypeReader; import org.semantictools.frame.model.BindVocabulary; import org.semantictools.frame.model.Datatype; import org.semantictools.frame.model.Enumeration; import org.semantictools.frame.model.Field; import org.semantictools.frame.model.Frame; import org.semantictools.frame.model.ListType; import org.semantictools.frame.model.NamedIndividual; import org.semantictools.frame.model.RdfType; import org.semantictools.frame.model.RestCategory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; import com.hp.hpl.jena.ontology.FunctionalProperty; import com.hp.hpl.jena.ontology.OntClass; import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.ontology.OntProperty; import com.hp.hpl.jena.ontology.OntResource; import com.hp.hpl.jena.rdf.model.ModelFactory; import com.hp.hpl.jena.rdf.model.RDFList; import com.hp.hpl.jena.rdf.model.RDFNode; import com.hp.hpl.jena.rdf.model.Resource; import com.hp.hpl.jena.rdf.model.Statement; import com.hp.hpl.jena.rdf.model.StmtIterator; import com.hp.hpl.jena.vocabulary.OWL; import com.hp.hpl.jena.vocabulary.OWL2; import com.hp.hpl.jena.vocabulary.RDF; import com.hp.hpl.jena.vocabulary.RDFS; public class FrameBuilder { private static Logger logger = LoggerFactory.getLogger(FrameBuilder.class); private static final OntModel owlModel = ModelFactory.createOntologyModel(); private static final OntClass THING = owlModel.createClass("http://www.w3.org/2002/07/owl#Thing"); private static final String[] STANDARD_URI = { "http://www.w3.org/2001/XMLSchema#", "http://www.w3.org/2002/07/owl#", "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "http://www.w3.org/2000/01/rdf-schema#", "http://purl.org/semantictools/v1/vocab/bind#" }; private TypeManager manager; private OntModel model; private DatatypeReader datatypeReader; public FrameBuilder(TypeManager manager) { this.manager = manager; datatypeReader = new DatatypeReader(manager); } public OntModel getOntModel() { return model; } public void setOntModel(OntModel model) { this.model = model; } private static boolean isStandard(String uri) { for (int i=0; i<STANDARD_URI.length; i++) { if (uri.startsWith(STANDARD_URI[i])) return true; } return false; } public void buildFrames(OntModel model) { this.model = model; applyInverseOfReasoning(); createFrames(); addSupertypesAndRestrictions(); addFields(); addFieldsFromRestrictions(); } /** * For all inverse relations, ensure that the relationship * is defined in both directions. */ private void applyInverseOfReasoning() { List<Statement> list = model.listStatements(null, OWL.inverseOf, (RDFNode) null).toList(); for (Statement s : list) { Resource subject = s.getSubject(); Resource object = s.getObject().asResource(); Statement newStatement = model.createStatement(object, OWL.inverseOf, subject); model.add(newStatement); } } private void addFieldsFromRestrictions() { for (Frame frame : manager.listFrames()) { addFieldsFromRestrictions(frame); } } private void addFieldsFromRestrictions(Frame frame) { Map<String, Field> fieldMap = createFieldMap(frame); for (OntClass restriction : frame.listRestrictions()) { addFieldFromRestriction(fieldMap, frame, restriction); } } private void addFieldFromRestriction(Map<String, Field> fieldMap, Frame frame, OntClass restriction) { int minCardinality = 0; int maxCardinality = -1; OntResource range = null; Resource resource = restriction.getPropertyResourceValue(OWL2.onProperty); String uri = resource.getURI(); Field priorField = fieldMap.get(uri); if (priorField != null) { maxCardinality = priorField.getMaxCardinality(); } OntProperty property = null; if (restriction.getPropertyResourceValue(OWL2.onProperty).canAs(OntProperty.class)) { property = resource.as(OntProperty.class); } else { property = model.createOntProperty(resource.getURI()); } if (property != null && property.canAs(FunctionalProperty.class)) { maxCardinality = 1; } if (restriction.hasProperty(OWL.minCardinality)) { minCardinality = restriction.getProperty(OWL.minCardinality).getInt(); } if (restriction.hasProperty(OWL.maxCardinality)) { maxCardinality = restriction.getProperty(OWL.maxCardinality).getInt(); } Resource valueType = restriction.getPropertyResourceValue(OWL.allValuesFrom); if (valueType != null) { range = valueType.as(OntResource.class); } Resource hasValue = restriction.getPropertyResourceValue(OWL.hasValue); NamedIndividual individualValue = null; if (hasValue != null && hasValue.getURI() != null) { String individualURI = hasValue.getURI(); individualValue = new NamedIndividual(hasValue.as(OntResource.class)); } // Resource onClass = restriction.getPropertyResourceValue(OWL2.onClass); // if (onClass != null) { // range = onClass.as(OntResource.class); // if (restriction.hasProperty(OWL2.minQualifiedCardinality)) { // minCardinality = restriction.getProperty(OWL2.minQualifiedCardinality).getInt(); // } // if (restriction.hasProperty(OWL2.maxQualifiedCardinality)) { // maxCardinality = restriction.getProperty(OWL2.maxQualifiedCardinality).getInt(); // } // // } else { // if (restriction.hasProperty(OWL.minCardinality)) { // minCardinality = restriction.getProperty(OWL.minCardinality).getInt(); // } // if (restriction.hasProperty(OWL.maxCardinality)) { // maxCardinality = restriction.getProperty(OWL.maxCardinality).getInt(); // } // } if (range == null) { Resource value = property.getPropertyResourceValue(RDFS.range); if (value == null) { logger.warn("Ignoring field " + resource.getLocalName() + " on class " + frame.getLocalName() + ": the range is not defined."); return; } range = property.getPropertyResourceValue(RDFS.range).as(OntResource.class); } String comment = restriction.getComment(null); if (priorField != null && priorField.getDeclaringFrame()==frame) { priorField.setComment(comment); priorField.setMinCardinality(minCardinality); priorField.setMaxCardinality(maxCardinality); priorField.setValueRestriction(individualValue); return; } Field field = new Field(frame, property, range, minCardinality, maxCardinality); field.setComment(comment); field.setValueRestriction(individualValue); fieldMap.put(uri, field); frame.getDeclaredFields().add(field); } private Map<String, Field> createFieldMap(Frame frame) { Map<String, Field> map = new HashMap<String, Field>(); for (Field field : frame.listAllFields()) { map.put(field.getURI(), field); } return map; } private void addFields() { List<OntProperty> list = listProperties(); for (OntProperty p : list) { if (isStandard(p.getURI())) continue; addFields(p, p); } } private void addFields(OntProperty p, OntProperty ancestor) { OntResource domainResource = ancestor.getDomain(); if (domainResource == null) { handleNullDomain(p, ancestor); return; } OntClass domain = domainResource.as(OntClass.class); List<OntResource> domainList = listUnionMembers(p, domain); if (domainList.isEmpty()) { domainList.add(domain); } for (OntResource type : domainList) { addField(type, p, ancestor); } } private void handleNullDomain(OntProperty p, OntProperty ancestor) { handlePropertySubclassOf(p); handleSubpropertyOf(p, ancestor); } private void handleSubpropertyOf(OntProperty p, OntProperty ancestor) { List<RDFNode> list = ancestor.listPropertyValues(RDFS.subPropertyOf).toList(); for (RDFNode node : list) { if (!node.canAs(OntProperty.class)) continue; OntProperty superProperty = node.as(OntProperty.class); if (superProperty.equals(ancestor)) continue; addFields(p, superProperty); handleSubpropertyOf(p, superProperty); } } private void handlePropertySubclassOf(OntProperty p) { List<RDFNode> list = p.listPropertyValues(RDFS.subClassOf).toList(); for (RDFNode node : list) { if (!node.canAs(Resource.class)) continue; Resource resource = node.asResource(); Resource onPropertyValue = resource.getPropertyResourceValue(OWL.onProperty); if (!RDFS.domain.equals(onPropertyValue)) continue; Resource someValuesFrom = resource.getPropertyResourceValue(OWL.someValuesFrom); if (someValuesFrom == null) continue; String uri = someValuesFrom.getURI(); if (uri != null) { OntResource type = someValuesFrom.as(OntResource.class); addField(type, p, null); } else { Resource unionList = someValuesFrom.getPropertyResourceValue(OWL.unionOf); while (unionList != null) { Resource first = unionList.getPropertyResourceValue(RDF.first); if (first != null) { String typeURI = first.getURI(); if (typeURI == null) { logger.warn("Cannot handle union that contains an anonymous class in the domain of " + p.getURI()); } else { OntResource type = first.as(OntResource.class); addField(type, p, null); } } unionList = unionList.getPropertyResourceValue(RDF.rest); if (RDF.nil.equals(unionList)) { break; } } } } } private void addField(OntResource type, OntProperty p, OntProperty ancestor) { int minCardinality = 0; int maxCardinality = -1; OntResource range = null; String typeURI = type.getURI(); if (typeURI == null) { // We only add fields to named types. return; } // Do not add abstract properties. if (isAbstract(p)) return; Frame frame = manager.getFrameByUri(typeURI); if (frame == null) { if (isStandard(typeURI)) return; logger.warn("Ignoring property " + p.getLocalName() + " on class " + type.getLocalName() + ": frame not found"); return; } if (frame.getDeclaredFieldByPropertyURI(p.getURI()) != null) return; if (p.hasRDFType(OWL.FunctionalProperty)) { maxCardinality = 1; } OntClass restriction = frame.getRestriction(p.getURI()); range = p.getRange(); if (range == null && ancestor!=null) { range = ancestor.getRange(); } if (range == null) { // logger.warn("Ignoring property " + p.getLocalName() + " on class " + type.getLocalName() + ": range not defined"); // return; range = THING; } if (restriction != null) { Resource onClass = restriction.getPropertyResourceValue(OWL2.onClass); if (onClass != null) { range = onClass.as(OntResource.class); if (restriction.hasProperty(OWL2.minQualifiedCardinality)) { minCardinality = restriction.getProperty(OWL2.minQualifiedCardinality).getInt(); } if (restriction.hasProperty(OWL2.maxQualifiedCardinality)) { maxCardinality = restriction.getProperty(OWL2.maxQualifiedCardinality).getInt(); } } else { if (restriction.hasProperty(OWL.minCardinality)) { minCardinality = restriction.getProperty(OWL.minCardinality).getInt(); } if (restriction.hasProperty(OWL.maxCardinality)) { maxCardinality = restriction.getProperty(OWL.maxCardinality).getInt(); } } } Field field = null; String rangeURI = range.getURI(); if (rangeURI == null) { field = createListField(frame, p, range); if (field == null) { logger.warn("Ignoring property " + p.getLocalName() + " on class " + type.getLocalName() + ": range has no URI"); return; } } else { field = new Field(frame, p, range, minCardinality, maxCardinality); if (field.getRdfType() == null) { logger.warn("Failed to create RdfType for field " + field.getLocalName() + " of type " + field.getType().getURI()); } } Resource rawInverse = p.getPropertyResourceValue(OWL.inverseOf); if (rawInverse != null && rawInverse.canAs(OntProperty.class)) { field.setInverseOf(rawInverse.as(OntProperty.class)); } frame.getDeclaredFields().add(field); } private boolean isAbstract(OntProperty p) { List<RDFNode> list = p.listPropertyValues(RDF.type).toList(); for (RDFNode node : list) { if (node.canAs(Resource.class)) { Resource r = node.asResource(); if (BindVocabulary.AbstractProperty.getURI().equals(r.getURI())) return true; } } return false; } private Field createListField(Frame frame, OntProperty p, OntResource range) { Resource intersection = range.getPropertyResourceValue(OWL.intersectionOf); if (intersection == null) return null; if (intersection.canAs(RDFList.class)) { List<RDFNode> intersectionList = intersection.as(RDFList.class).asJavaList(); for (RDFNode node : intersectionList) { if (node.canAs(OntClass.class)) { OntClass intersectionMember = node.as(OntClass.class); if ( RDF.first.equals(intersectionMember.getPropertyResourceValue(OWL.onProperty)) ) { // The intersectionMember has an owl:onProperty property whose value is rdf:first Resource elementRdfType = intersectionMember.getPropertyResourceValue(OWL.allValuesFrom); if (elementRdfType != null) { String elementTypeURI = elementRdfType.getURI(); if (elementTypeURI != null) { RdfType elementType = manager.getTypeByURI(elementTypeURI); if (elementType != null) { ListType listType = manager.getListTypeByElementUri(elementTypeURI); if (listType == null) { listType = new ListType(manager, intersectionMember, elementType); manager.add(listType); } return new Field(frame, p, listType); } } } } } } } return null; } private List<OntClass> listRestrictions(OntClass type) { List<OntClass> list = new ArrayList<OntClass>(); Iterator<OntClass> sequence = type.listSuperClasses(true); while (sequence.hasNext()) { OntClass supertype = sequence.next(); if (supertype.hasRDFType(OWL.Restriction, false)) { list.add(supertype); } } return list; } private List<OntResource> listUnionMembers(OntProperty p, OntResource domain) { List<OntResource> list = new ArrayList<OntResource>(); Resource union = domain.getPropertyResourceValue(OWL.unionOf); if (union != null && union.canAs(RDFList.class)) { RDFList rdfList = union.as(RDFList.class); Iterator<RDFNode> sequence = rdfList.iterator(); while (sequence.hasNext()) { list.add(sequence.next().as(OntResource.class)); } } return list; } // // private void debugUnion(OntProperty p, List<OntResource> list) { // System.out.println(p.getLocalName() + " unionOf"); // for (int i=0; i<list.size(); i++) { // OntResource r = list.get(i); // System.out.println(" " +(i+1) + ". " + r.getURI()); // Iterator<Statement> sequence = r.listProperties(); // while (sequence.hasNext()) { // System.out.println(" " + sequence.next().getPredicate().getLocalName()); // } // } // // } private List<OntProperty> listProperties() { List<OntProperty> list = new ArrayList<OntProperty>(); Iterator<OntProperty> sequence = model.listAllOntProperties(); while (sequence.hasNext()) { list.add(sequence.next()); } return list; } private void addSupertypesAndRestrictions() { Collection<Frame> frameList = manager.listFrames(); for (Frame frame : frameList) { addSupertypes(frame); addRestrictions(frame); addSubtypes(frame); } } private void addRestrictions(Frame frame) { List<OntClass> restrictionList = listRestrictions(frame.asOntClass()); for (OntClass restriction : restrictionList) { frame.addRestriction(restriction); } } private void addSubtypes(Frame frame) { Iterator<OntClass> sequence = frame.asOntClass().listSubClasses(true); while (sequence.hasNext()) { OntClass type = sequence.next(); String subURI = type.getURI(); if (subURI == null) continue; Frame subFrame = manager.getFrameByUri(subURI); if (subFrame == null) { subFrame = manager.getListTypeByListUri(subURI); } if (subFrame != null) { frame.getSubtypeList().add(subFrame); } else { Datatype datatype = manager.getDatatypeByUri(subURI); if (datatype != null) { frame.addSubdatatype(datatype); continue; } if (isStandard(subURI)) continue; logger.warn("Ignoring supertype of " + type.getLocalName() + " because frame not found: " + subURI); } } } private void addSupertypes(Frame frame) { Iterator<OntClass> sequence = frame.asOntClass().listSuperClasses(true); while (sequence.hasNext()) { OntClass type = sequence.next(); String superURI = type.getURI(); if (superURI == null) continue; Frame superframe = manager.getFrameByUri(superURI); if (superframe != null) { frame.getSupertypeList().add(superframe); } else { if (isStandard(superURI)) continue; logger.warn("Ignoring supertype of " + type.getLocalName() + " because frame not found: " + superURI); } } } private void createFrames() { List<OntClass> list = listNamedClasses(); for (OntClass type : list) { if (isProperty(type)) { continue; } Frame frame = createFrame(type); if (frame == null) continue; setRestCategory(frame); setAbstract(frame); } addStandardFrames(); } private boolean isProperty(OntClass type) { return type.canAs(OntProperty.class); } private List<OntClass> listNamedClasses() { List<OntClass> result = new ArrayList<OntClass>(); StmtIterator classes = model.listStatements(null, RDF.type, OWL.Class); while (classes.hasNext()) { Resource type = classes.next().getSubject(); String uri = type.getURI(); if (uri == null || manager.getDatatypeByUri(uri)!=null) continue; if (!isStandard(uri)) { result.add(type.as(OntClass.class)); } } classes = model.listStatements(null, RDF.type, RDFS.Class); while (classes.hasNext()) { Resource type = classes.next().getSubject(); String uri = type.getURI(); if (uri == null || manager.getDatatypeByUri(uri)!=null) continue; if (!isStandard(uri)) { result.add(type.as(OntClass.class)); } } List<Resource> rdfsClassList = model.listResourcesWithProperty(RDF.type, RDFS.Class).toList(); for (Resource r : rdfsClassList) { if ( r.canAs(OntClass.class) && r.getURI()!=null && !isStandard(r.getURI()) && manager.getDatatypeByUri(r.getURI())==null ) { result.add(r.as(OntClass.class)); } } return result; } private Frame createFrame(OntClass type) { if (manager.isStandardDatatype(type.getNameSpace())) { manager.getDatatypeByUri(type.getURI()); return null; } if (manager.isStandard(type.getNameSpace())) { return null; } String uri = type.getURI(); if (uri == null) { throw new RuntimeException("URI of type is not defined"); } Frame frame = manager.getFrameByUri(uri); if (frame != null) { return frame; } OntClass elemType = manager.getElementType(type); if (elemType != null) { String elemURI = elemType.getURI(); ListType listType = manager.getListTypeByElementUri(elemURI); if (listType == null) { RdfType elemRdfType = manager.getTypeByURI(elemURI); if (elemRdfType == null) { elemRdfType = createFrame(elemType); } listType = new ListType(manager, type, elemRdfType); manager.add(listType); } return null; } List<OntResource> individuals = getEnumeratedIndividuals(type); if (individuals == null) { frame = new Frame(manager, type); } else { Enumeration enumFrame = new Enumeration(manager, type); frame = enumFrame; for (OntResource item : individuals) { NamedIndividual value = new NamedIndividual(item); enumFrame.add(value); } } manager.add(frame); return frame; } private List<OntResource> getEnumeratedIndividuals(OntClass type) { Resource equivalentClass = type.getPropertyResourceValue(OWL.equivalentClass); if (equivalentClass == null) { return null; } Resource oneOf = equivalentClass.getPropertyResourceValue(OWL.oneOf); if (oneOf == null) return null; List<RDFNode> nodeList = oneOf.as(RDFList.class).asJavaList(); List<OntResource> result = new ArrayList<OntResource>(); for (RDFNode node : nodeList) { result.add(node.as(OntResource.class)); } return result; } private void setAbstract(Frame frame) { if (frame.getType().hasRDFType(BindVocabulary.AbstractClass, true)) { frame.setAbstract(true); } } private void addStandardFrames() { addOwlClass(); } private void addOwlClass() { String typeURI = OWL2.Class.getURI(); OntClass type = manager.getOntModel().getOntClass(typeURI); if (type == null) { OntModel model = ModelFactory.createOntologyModel(); type = model.createClass(typeURI); } Frame frame = new Frame(manager, type); frame.setCategory(RestCategory.ADDRESSABLE); manager.add(frame); } private void setRestCategory(Frame frame) { OntClass type = frame.asOntClass(); Iterator<Resource> sequence = type.listRDFTypes(false); while (sequence.hasNext()) { Resource resource = sequence.next(); if (BindVocabulary.Addressable.equals(resource)) { frame.setCategory(RestCategory.ADDRESSABLE); } else if (BindVocabulary.Enum.equals(resource)) { frame.setCategory(RestCategory.ENUMERABLE); } else if (BindVocabulary.EmbeddableClass.equals(resource)) { frame.setCategory(RestCategory.EMBEDDABLE); } } } public void loadDir(File dir) throws IOException, ParserConfigurationException, SAXException { File[] list = dir.listFiles(); if (list == null) { logger.warn("no files found in directory: " + dir.getPath()); return; } for (int i=0; i<list.length; i++) { loadFile(list[i]); } buildFrames(model); } private void loadFile(File file) throws IOException, ParserConfigurationException, SAXException { String name = file.getName(); if (name.endsWith(".ttl")) { readOntology(file, "TURTLE"); } if (name.endsWith(".xsd")) { readXmlSchema(file); } } private void readXmlSchema(File file) throws IOException, ParserConfigurationException, SAXException { FileInputStream input = new FileInputStream(file); try { datatypeReader.read(input); } finally { input.close(); } } private void readOntology(File file, String format) throws IOException { FileInputStream input = new FileInputStream(file); try { if (model==null) { model = ModelFactory.createOntologyModel(); } model.read(input, null, format); } finally { input.close(); } } }