/******************************************************************************* * Copyright 2014 Miami-Dade County * * 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.sharegov.cirm; import static org.sharegov.cirm.OWL.businessObject; import static org.sharegov.cirm.OWL.dataProperty; import static org.sharegov.cirm.OWL.fullIri; //import static org.sharegov.cirm.OWL.individual; import static org.sharegov.cirm.OWL.objectProperty; import static org.sharegov.cirm.OWL.owlClass; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; import javax.xml.datatype.DatatypeConstants; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar; import mjson.Json; import org.semanticweb.owlapi.model.AddAxiom; import org.semanticweb.owlapi.model.IRI; import org.semanticweb.owlapi.model.OWLAxiom; import org.semanticweb.owlapi.model.OWLClass; import org.semanticweb.owlapi.model.OWLClassExpression; import org.semanticweb.owlapi.model.OWLDataFactory; import org.semanticweb.owlapi.model.OWLDataProperty; import org.semanticweb.owlapi.model.OWLDataRange; import org.semanticweb.owlapi.model.OWLDatatype; import org.semanticweb.owlapi.model.OWLIndividual; import org.semanticweb.owlapi.model.OWLLiteral; import org.semanticweb.owlapi.model.OWLNamedIndividual; import org.semanticweb.owlapi.model.OWLObjectProperty; import org.semanticweb.owlapi.model.OWLOntology; import org.semanticweb.owlapi.model.OWLOntologyChange; import org.semanticweb.owlapi.model.OWLOntologyCreationException; import org.semanticweb.owlapi.model.OWLOntologyManager; import org.semanticweb.owlapi.model.OWLProperty; import org.semanticweb.owlapi.model.RemoveAxiom; import org.semanticweb.owlapi.vocab.OWL2Datatype; import org.sharegov.cirm.utils.GenUtils; import org.sharegov.cirm.utils.JsonSerializable; import org.sharegov.cirm.utils.JsonUtil; import org.sharegov.cirm.utils.Mapping; /** * * <p> * Represents a business object ontology. Essentially wrap a * <code>OWLOntology</code> and provide utility methods to manipulate it. * </p> * * <p> * This is a very lightweight object, a thin wrapper so that many be created * frequently and on demand. * </p> * * @author Borislav Iordanov * */ public class BOntology implements JsonSerializable { private OWLOntology O; private volatile int tempIndividualCounter = 0; /** * On the client-side, we need those temporary individuals. But when the ontology is saved, * the temporary individuals are removed by the ontology transformer. When loading form * the store, the temporary individuals get generated for the UI JSON representation. * @return */ IRI getTempIRI() { return fullIri("temp" + tempIndividualCounter++); } boolean isTempIRI(String iri) { return iri.startsWith(fullIri("temp").toString()); } public BOntology(OWLOntology O) { this.O = O; } /** * Checks if the passed in IRI is a valid BOntology or not * @param entityIRI * @return true/false */ public static boolean isValidBO(IRI entityIRI) { if(entityIRI != null && entityIRI.toString().startsWith(Refs.boIriPrefix.resolve())) return true; else return false; } public static BOntology makeNewBusinessObject(OWLClass type) throws OWLOntologyCreationException { OWLOntologyManager manager = Refs.tempOntoManager.resolve(); IRI iriPrefix = IRI.create(Refs.boIriPrefix.resolve() + "/" + type.getIRI().getFragment()); IRI ontologyIRI = iriPrefix.resolve(Refs.idFactory.resolve().newId( type.getIRI().getFragment())); OWLOntology o = manager.createOntology(IRI.create(ontologyIRI .getStart().substring(0, ontologyIRI.getStart().length() - 1))); OWLNamedIndividual businessObject = businessObject(o); OWLAxiom axiom = manager.getOWLDataFactory().getOWLClassAssertionAxiom( type, businessObject); manager.applyChange(new AddAxiom(o, axiom)); return new BOntology(o); } /** * Creates an in-memory BOntology based on the Json data representation. * This ontology will reside in its own OWLOntologyManager and its intended * to be transient, perhaps living during the lifetime of a single client * request. * * @param data * @return * @throws OWLOntologyCreationException */ public static BOntology makeRuntimeBOntology(Json data) { try { OWLOntologyManager manager = Refs.tempOntoManager.resolve(); IRI ontologyIRI = IRI.create(Refs.boIriPrefix.resolve() + "/" + data.at("type").asString().replace("legacy:", "") + "/" + data.at("boid").asString()); OWLOntology o = manager.getOntology(ontologyIRI); if (o != null) manager.removeOntology(o); o = manager.createOntology(ontologyIRI); BOntology result = new BOntology(o); result.prefix(data, new IdentityHashMap<Json, Boolean>()); result.setProperties(data.at("properties")); OWLNamedIndividual businessObject = businessObject(o); OWLAxiom axiom = manager.getOWLDataFactory() .getOWLClassAssertionAxiom(owlClass(data.at("type").asString()), businessObject); manager.applyChange(new AddAxiom(o, axiom)); return result; } catch (Exception ex) { throw new RuntimeException(ex); } } public OWLOntology getOntology() { return O; } public String getObjectId() { String A[] = O.getOntologyID().getOntologyIRI().toString().split("/"); return A[A.length - 1]; } public IRI getTypeIRI() { return OWL.fullIri(getObjectIRI().toURI().getPath().split("/")[2]); } public IRI getTypeIRI(String prefix) { return OWL.fullIri(prefix + ":" + getObjectIRI().toURI().getPath().split("/")[2]); } public IRI getObjectIRI() { return O.getOntologyID().getOntologyIRI().resolve("#bo"); } public OWLNamedIndividual getBusinessObject() { return O.getOWLOntologyManager().getOWLDataFactory().getOWLNamedIndividual(getObjectIRI()); } public OWLLiteral getDataProperty(String name) { OWLNamedIndividual object = getBusinessObject(); Set<OWLLiteral> S = object.getDataPropertyValues(dataProperty(name), O); return S.isEmpty() ? null : S.iterator().next(); } public OWLNamedIndividual getObjectProperty(String name) { OWLNamedIndividual object = getBusinessObject(); Set<OWLIndividual> S = object.getObjectPropertyValues(objectProperty(name), O); return S.isEmpty() ? null : S.iterator().next().asOWLNamedIndividual(); } public Set<OWLIndividual> getObjectProperties(String name) { OWLNamedIndividual object = getBusinessObject(); Set<OWLIndividual> S = object.getObjectPropertyValues(objectProperty(name), O); return S; } /** * @param indIri * - if null a "temp" individual will be returned. As soon as * toJson spports anonymous ind, OWLAnonymousIndividual should be * returned; * @param properties * - the properties to create axioms for. * @return */ protected OWLIndividual makeObjectIndividual(IRI indIri, Json properties) { OWLOntologyManager manager = O.getOWLOntologyManager(); OWLDataFactory df = manager.getOWLDataFactory(); OWLIndividual result = df.getOWLNamedIndividual(indIri == null ? getTempIRI() : indIri); for (Map.Entry<String, Json> e : properties.asJsonMap().entrySet()) { if (e.getKey().equals("label") || e.getKey().equals("iri") || e.getKey().equals("type")) { if (e.getKey().equals("type")) manager.addAxiom(O, df.getOWLClassAssertionAxiom( owlClass(fullIri(e.getValue().asString())), result)); continue; } IRI propIri = fullIri(e.getKey()); if (OWL.isObjectProperty(propIri)) { if (e.getValue().isArray()) { for (int i = 0; i < e.getValue().asList().size(); i++) { addObjectProperty(result, OWL.dataFactory().getOWLObjectProperty(propIri), e.getValue().at(i)); } } else addObjectProperty(result, OWL.dataFactory().getOWLObjectProperty(propIri), e.getValue()); } else if (OWL.isDataProperty(propIri)) { if (e.getValue().isArray()) { for (int i = 0; i < e.getValue().asList().size(); i++) { addDataProperty(result, OWL.dataFactory().getOWLDataProperty(propIri), e.getValue().at(i)); } } else addDataProperty(result, OWL.dataFactory().getOWLDataProperty(propIri), e.getValue()); } else if (!OWL.isAnnotation(propIri)) throw new RuntimeException("Undeclared OWL property or annotation: " + propIri); } return result; } /** * <p> * First remove all properties of the this individual, then assign new properties * from the <code>properties</code> parameter. * </p> * * @param ind * @param properties * @return The <code>ind</code> parameter. */ public OWLNamedIndividual setPropertiesFor(OWLNamedIndividual ind, Json properties) { // Remove all data and object properties currently declared in the // ontology. ArrayList<OWLOntologyChange> L = new ArrayList<OWLOntologyChange>(); for (OWLAxiom a : O.getDataPropertyAssertionAxioms(ind)) L.add(new RemoveAxiom(O, a)); for (OWLAxiom a : O.getObjectPropertyAssertionAxioms(ind)) L.add(new RemoveAxiom(O, a)); O.getOWLOntologyManager().applyChanges(L); makeObjectIndividual(ind.getIRI(), properties); return ind; } public void setProperties(Json properties) { OWLNamedIndividual boInd = businessObject(O); setPropertiesFor(boInd, properties); } public OWLLiteral addDataProperty(OWLIndividual ind, OWLDataProperty prop, Json value) { OWLLiteral literal; String valueStr; OWL2Datatype xsdType; if (value.isObject()) { String typeStr = value.at("type").asString(); if (value.at("literal").isArray()) throw new RuntimeException( "The value of the dataProperty cannot be an Array, individual is: " + ind.asOWLNamedIndividual().getIRI() + " property is: " + prop.getIRI() + " value is: " + value.at("literal")); valueStr = value.at("literal").asString(); xsdType = OWL2Datatype.getDatatype(OWL.fullIri(typeStr));// IRI.create(typeStr)); if (xsdType == null) throw new IllegalArgumentException( "Unable to read type for Jason value." + value); } else { xsdType = null; if (!value.isNull()) { //TODO: add asString() in BooleanJson function and remove this if condition if(value.isBoolean()) valueStr = value.toString(); else valueStr = value.asString(); } else { valueStr = null; } } if (valueStr == null || valueStr.isEmpty()) { System.err.println("Empty or null string JSON detected. No BO axiom created. Individual was: " + ind + " dataproperty was: " + prop); return null; } OWLOntologyManager manager = O.getOWLOntologyManager(); OWLDataFactory df = manager.getOWLDataFactory(); literal = toLiteral(df, prop, valueStr, xsdType); if (literal == null) literal = df.getOWLLiteral(valueStr); OWLAxiom axiom = df.getOWLDataPropertyAssertionAxiom(prop, ind, literal); manager.applyChange(new AddAxiom(O, axiom)); return literal; } private OWLClass getValueType(OWLObjectProperty prop, Json value) { if (value.has("type")) return owlClass(value.at("type").asString()); else for (OWLClassExpression range : prop.getRanges(OWL.ontologies())) if (range instanceof OWLClass) return (OWLClass) range; return null; // // TODO: we need to somehow pass the actual type of the property // // value // // to the client (hidden form field perhaps) and back to the server // // this is a hack because PromptUserTask currently uses the first // // range that has a form // if (new UiService().getForm(range) != null) // { // objectType = range; // break; // } // } public OWLIndividual addObjectProperty(OWLIndividual ind, String prop, Json value) { return addObjectProperty(ind, O.getOWLOntologyManager().getOWLDataFactory().getOWLObjectProperty(fullIri(prop)), value); } public OWLIndividual addObjectProperty(OWLIndividual ind, OWLObjectProperty prop, Json value) { OWLOntologyManager manager = O.getOWLOntologyManager(); OWLDataFactory df = manager.getOWLDataFactory(); OWLIndividual object = null; if (value.isObject()) { if (value.has("iri")) object = setPropertiesFor(df.getOWLNamedIndividual(fullIri(value.at("iri").asString())), value); else { IRI indIri = null; if (!Refs.ontologyTransformer.resolve().isTransformProperty(prop)) { OWLClass objectType = getValueType(prop, value); if (objectType == null) throw new RuntimeException( "Couldn't figure out the type of " + value + " as object property " + prop); String type = objectType.asOWLClass().getIRI().getFragment(); indIri = fullIri(type + Refs.idFactory.resolve().newId(null)); } if (indIri == null) { do { indIri = getTempIRI(); } while(O.containsIndividualInSignature(indIri)); } object = setPropertiesFor(df.getOWLNamedIndividual(indIri), value); } } else object = df.getOWLNamedIndividual(fullIri(value.asString())); manager.applyChange(new AddAxiom(O, df.getOWLObjectPropertyAssertionAxiom(prop, ind, object))); return object; } public void deleteObjectProperty(OWLIndividual ind, String prop) { OWLOntologyManager manager = O.getOWLOntologyManager(); OWLDataFactory df = manager.getOWLDataFactory(); OWLObjectProperty property = df.getOWLObjectProperty(fullIri(prop)); Set<OWLIndividual> all = ind.getObjectPropertyValues(property, O); for (OWLIndividual x : all) { OWLAxiom axiom = df.getOWLObjectPropertyAssertionAxiom(property, ind, x); manager.applyChange(new RemoveAxiom(O, axiom)); } } public void deleteDataProperty(OWLIndividual ind, String prop) { OWLOntologyManager manager = O.getOWLOntologyManager(); OWLDataFactory df = manager.getOWLDataFactory(); OWLDataProperty property = df.getOWLDataProperty(fullIri(prop)); Set<OWLLiteral> all = ind.getDataPropertyValues(property, O); for (OWLLiteral x : all) { OWLAxiom axiom = df.getOWLDataPropertyAssertionAxiom(property, ind, x); manager.applyChange(new RemoveAxiom(O, axiom)); } } public void deleteObjectProperty(OWLIndividual ind, OWLObjectProperty prop) { OWLOntologyManager manager = O.getOWLOntologyManager(); OWLDataFactory df = manager.getOWLDataFactory(); Set<OWLIndividual> all = ind.getObjectPropertyValues(prop, O); for (OWLIndividual x : all) { OWLAxiom axiom = df.getOWLObjectPropertyAssertionAxiom(prop, ind, x); manager.applyChange(new RemoveAxiom(O, axiom)); } } public void deleteDataProperty(OWLIndividual ind, OWLDataProperty prop) { OWLOntologyManager manager = O.getOWLOntologyManager(); OWLDataFactory df = manager.getOWLDataFactory(); Set<OWLLiteral> all = ind.getDataPropertyValues(prop, O); for (OWLLiteral x : all) { OWLAxiom axiom = df.getOWLDataPropertyAssertionAxiom(prop, ind, x); manager.applyChange(new RemoveAxiom(O, axiom)); } } public void deleteProperty(OWLIndividual ind, @SuppressWarnings("rawtypes") OWLProperty prop) { if (prop instanceof OWLDataProperty) deleteDataProperty(ind, (OWLDataProperty) prop); else deleteObjectProperty(ind, (OWLObjectProperty) prop); } private static final Set<String> toprefix = GenUtils.set( "hasServiceAnswer", "hasAnswerValue", "hasServiceField", "hasServiceCaseActor", "ServiceAnswer", "hasServiceActor", "ServiceCaseActor", "hasServiceActivity", "hasActivity", "hasCompletedTimestamp", "hasUpdatedDate", "hasDetails", "hasDueDate", "isAssignedTo", "hasOutcome", "Outcome", "ServiceActivity", "hasOldData", "isAccepted", "hasStatus", "hasPriority", "hasIntakeMethod", "hasAnswerObject", "hasCaseNumber", "hasParentCaseNumber", "hasGisDataId", "hasLocationDetails", "hasDepartmentError", "hasLegacyEvent"); private static final Set<String> toignore = GenUtils.set("hasLegacyCode", "hasLegacyId", "addressType", "label", "hasChoiceValueList", "hasDataType", "hasOrderBy", "hasAnswerUpdateTimeout","description", "description2", "description3", "description4", "description5", "description6", "comment", "isOldData", "participantEntityTable", "hasBusinessCodes", "hasAllowableModules", "folio", "isDisabled", "transient$protected", "isAlwaysPublic", "isHighlighted", "fromDiffSRType", "hasLegacyInterface", "hasLegacyEvent"); private static final Set<String> toValueprefix = GenUtils.set("type"); private static final Set<String> toValueExcludePrefix = GenUtils.set("Street_Address"); private void prefix(Json j, IdentityHashMap<Json, Boolean> done) { if (done.containsKey(j)) return; else done.put(j, true); if (j.isArray()) for (Json el : j.asJsonList()) prefix(el, done); else if (j.isObject()) { for (String s : toignore) j.asJsonMap().remove(s); for (String s : toValueprefix) if (j.has(s) && j.at(s).isString() && !j.at(s).asString().matches("\\w+:\\w+|http://.+") && !toValueExcludePrefix.contains(j.at(s).asString())) j.set(s, "legacy:" + j.at(s).asString()); for (String s : toprefix) if (j.has(s)) j.set("legacy:" + s, j.atDel(s)); for (Map.Entry<String, Json> e : j.asJsonMap().entrySet()) prefix(e.getValue(), done); } } public Json toJSON() { // Json j = MetaService.get().toJSON(O, this.getBusinessObject()); OWLObjectToJson mapper = new OWLObjectToJson(); mapper.setIncludeTypeInfo(true); mapper.getPropertiesToTypeDecorate().add("hasAnswerValue"); Json j = mapper.map(this.getOntology(), this.getBusinessObject(), null); Json result = Json.object().set("boid", this.getObjectId()); result.set("type", j.atDel("type")); result.set("iri", j.atDel("iri")); result.set("properties", j); result = JsonUtil.apply(result, new Mapping<Json, Json>() { public Json eval(Json in) { if (in.isObject() && in.has("iri") && isTempIRI(in.at("iri").asString())) in.delAt("iri"); return in; } }); return result; } /** * Convert a Json value to an OWL literal using the first range of the * the data property. If the dataproperty has no ranges defined, the given OWL2Datatype is used. * If a OWL2Datatype is given and the dataproperty has ranges defined a match is enforced. * If no OWL2Datatype is given, the first range that is an OWLDatatype will be used for the literal. * (see @link #toLiteral(OWLDataRange, Json) for details on how the mapping * is performed). * @param prop * @param value * @param builtinDatatype if null, first range will become literal datatype; if given it must match range. * @return an OWlLiteral or null, if the range does not match the given datatype or no OWLDatatype was found in the range. */ private OWLLiteral toLiteral(OWLDataFactory factory, OWLDataProperty prop, String value, OWL2Datatype builtinDatatype) { // Parse out if the value is an ISO date and convert it to the format XML datafactory accepts. if (builtinDatatype == null || builtinDatatype.equals(OWL2Datatype.XSD_DATE_TIME_STAMP)) { try { return dateLiteral(factory, GenUtils.parseDate(value), OWL2Datatype.XSD_DATE_TIME_STAMP); } catch (Throwable t) { } } //TODO we could validate here, if the value string matches the builtinDatatype. Set<OWLDataRange> ranges = prop.getRanges(OWL.ontologies()); if (ranges.isEmpty() && builtinDatatype != null) { return factory.getOWLLiteral(value, builtinDatatype); } for (OWLDataRange range : ranges) { if ((builtinDatatype == null && range instanceof OWLDatatype) || (builtinDatatype != null && range.equals(builtinDatatype))) { return factory.getOWLLiteral(value, (OWLDatatype)range); } } return null; } private OWLLiteral dateLiteral(OWLDataFactory factory, Date date, OWL2Datatype d) { OWLLiteral result = factory.getOWLLiteral("", d); if(date == null) return result; try { //see: //http://download.oracle.com/javase/6/docs/api/javax/xml/datatype/XMLGregorianCalendar.html#getXMLSchemaType() Calendar c = Calendar.getInstance(); c.setTime(date); XMLGregorianCalendar x = DatatypeFactory.newInstance().newXMLGregorianCalendar(); if(c instanceof GregorianCalendar) if(DatatypeConstants.DATE.getNamespaceURI().equals(d.getIRI().toString())) { x.setYear(c.get(Calendar.YEAR)); x.setMonth(c.get(Calendar.MONTH)); x.setDay(c.get(Calendar.DAY_OF_MONTH)); result = factory.getOWLLiteral(x.toXMLFormat(),d); } else result = factory.getOWLLiteral(DatatypeFactory.newInstance().newXMLGregorianCalendar((GregorianCalendar)c).toXMLFormat(), d); } catch(Exception e) { String msg = "Exception occured while attempting to extract a xsd date value"; throw new RuntimeException(msg); } return result; } public OWLLiteral getDataProperty(OWLNamedIndividual individual, String name) { Set<OWLLiteral> S = individual.getDataPropertyValues(dataProperty(name), O); return S.isEmpty() ? null : S.iterator().next(); } public OWLNamedIndividual getObjectProperty(OWLNamedIndividual individual, String name) { Set<OWLIndividual> S = individual.getObjectPropertyValues(objectProperty(name), O); return S.isEmpty() ? null : S.iterator().next().asOWLNamedIndividual(); } }