BuildingSemanticWebApplications

From Protege Wiki
Jump to: navigation, search

Main article: ProtegeOWL_API_Programmers_Guide

Building Semantic Web Applications

You can use any of the previously described Protege-OWL API features for stand-alone applications. Such applications can load ontologies from the Semantic Web, perform queries on them, add or edit resources from the ontology, classify instances and classes, and write out resulting ontologies to a file. Some thoughts on the architecture and development methodology for Semantic Web applications were published in a paper by Holger Knublauch. The following figure, taken from this publication, illustrates the typical architecture of such an application:


Architecture.png


Semantic Web applications usually need to make some ontological commitments, i.e., they need to have hard-coded knowledge about a certain domain ontology. In the example above, the application has hard-coded behavior that depends on the travel.owl ontology, which contains classes like Activity and Destination. The application can also operate on extensions of these core concepts, e.g., stemming from dynamic extension ontologies about specific types of activities and destinations. Then, the application can exploit reasoning engines like Racer or rule engines like SWRL to expose "intelligent" behavior. All of this is controlled by some logic (in this example it is Java code), which also interacts with the end user by means of interface technologies like JSPs, Swing applications, or Web Services.

For most such scenarios, it quickly becomes inconvenient to handle ontology concepts using only generic APIs such as Protege-OWL or Jena. Let us look at a scenario that illustrates this point. Assume we have a simple domain model consisting of Products, Customers, and Purchases. Products have a price, Customers have firstName, lastName, zipCode, and a collection of purchases. Each Purchase consists of one product, one customer, and a date.


JavaCodeUML.png


The OWL ontology to represent such a domain model is shown below. It is easy to refine this model to be somehow more interesting. For example, we can add a class GoodCustomer, defined as those customers that have a certain number of purchases in their record.


<?xml version="1.0"?>
<rdf:RDF
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    xmlns:owl="http://www.w3.org/2002/07/owl#"
    xmlns="http://www.owl-ontologies.com/javaDemo.owl#"
  xml:base="http://www.owl-ontologies.com/javaDemo.owl">
  <owl:Ontology rdf:about=""/>
  <owl:Class rdf:ID="VeryGoodCustomer">
    <owl:equivalentClass>
      <owl:Class>
        <owl:intersectionOf rdf:parseType="Collection">
          <owl:Class rdf:ID="Customer"/>
          <owl:Restriction>
            <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
            >100</owl:minCardinality>
            <owl:onProperty>
              <owl:ObjectProperty rdf:ID="purchases"/>
            </owl:onProperty>
          </owl:Restriction>
        </owl:intersectionOf>
      </owl:Class>
    </owl:equivalentClass>
  </owl:Class>
  <owl:Class rdf:ID="GoodCustomer">
    <owl:equivalentClass>
      <owl:Class>
        <owl:intersectionOf rdf:parseType="Collection">
          <owl:Class rdf:about="#Customer"/>
          <owl:Restriction>
            <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
            >2</owl:minCardinality>
            <owl:onProperty>
              <owl:ObjectProperty rdf:about="#purchases"/>
            </owl:onProperty>
          </owl:Restriction>
        </owl:intersectionOf>
      </owl:Class>
    </owl:equivalentClass>
  </owl:Class>
  <owl:Class rdf:ID="Product"/>
  <owl:Class rdf:ID="Purchase">
    <rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
    <rdfs:subClassOf>
      <owl:Restriction>
        <owl:onProperty>
          <owl:FunctionalProperty rdf:ID="product"/>
        </owl:onProperty>
        <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
        >1</owl:cardinality>
      </owl:Restriction>
    </rdfs:subClassOf>
    <rdfs:subClassOf>
      <owl:Restriction>
        <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
        >1</owl:cardinality>
        <owl:onProperty>
          <owl:FunctionalProperty rdf:ID="customer"/>
        </owl:onProperty>
      </owl:Restriction>
    </rdfs:subClassOf>
    <rdfs:subClassOf>
      <owl:Restriction>
        <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
        >1</owl:cardinality>
        <owl:onProperty>
          <owl:FunctionalProperty rdf:ID="date"/>
        </owl:onProperty>
      </owl:Restriction>
    </rdfs:subClassOf>
  </owl:Class>
  <owl:ObjectProperty rdf:about="#purchases">
    <owl:inverseOf>
      <owl:FunctionalProperty rdf:about="#customer"/>
    </owl:inverseOf>
    <rdfs:range rdf:resource="#Purchase"/>
    <rdfs:domain rdf:resource="#Customer"/>
  </owl:ObjectProperty>
  <owl:DatatypeProperty rdf:ID="lastName">
    <rdfs:range rdf:resource="http://www.w3.org/2001/XMLSchema#string"/>
    <rdf:type rdf:resource="http://www.w3.org/2002/07/owl#FunctionalProperty"/>
    <rdfs:domain rdf:resource="#Customer"/>
  </owl:DatatypeProperty>
  <owl:DatatypeProperty rdf:ID="zipCode">
    <rdfs:range rdf:resource="http://www.w3.org/2001/XMLSchema#int"/>
    <rdf:type rdf:resource="http://www.w3.org/2002/07/owl#FunctionalProperty"/>
    <rdfs:domain rdf:resource="#Customer"/>
  </owl:DatatypeProperty>
  <owl:FunctionalProperty rdf:ID="price">
    <rdfs:range rdf:resource="http://www.w3.org/2001/XMLSchema#float"/>
    <rdf:type rdf:resource="http://www.w3.org/2002/07/owl#DatatypeProperty"/>
    <rdfs:domain rdf:resource="#Product"/>
  </owl:FunctionalProperty>
  <owl:FunctionalProperty rdf:about="#product">
    <rdfs:domain rdf:resource="#Purchase"/>
    <rdf:type rdf:resource="http://www.w3.org/2002/07/owl#ObjectProperty"/>
    <rdfs:range rdf:resource="#Product"/>
  </owl:FunctionalProperty>
  <owl:FunctionalProperty rdf:ID="firstName">
    <rdfs:range rdf:resource="http://www.w3.org/2001/XMLSchema#string"/>
    <rdf:type rdf:resource="http://www.w3.org/2002/07/owl#DatatypeProperty"/>
    <rdfs:domain rdf:resource="#Customer"/>
  </owl:FunctionalProperty>
  <owl:FunctionalProperty rdf:about="#date">
    <rdfs:range rdf:resource="http://www.w3.org/2001/XMLSchema#date"/>
    <rdfs:domain rdf:resource="#Purchase"/>
    <rdf:type rdf:resource="http://www.w3.org/2002/07/owl#DatatypeProperty"/>
  </owl:FunctionalProperty>
  <owl:FunctionalProperty rdf:about="#customer">
    <rdfs:domain rdf:resource="#Purchase"/>
    <rdf:type rdf:resource="http://www.w3.org/2002/07/owl#ObjectProperty"/>
    <owl:inverseOf rdf:resource="#purchases"/>
    <rdfs:range rdf:resource="#Customer"/>
  </owl:FunctionalProperty>
</rdf:RDF>


Assuming we want to build a Java application around this model, we need to access the objects in the ontology and the run-time objects, e.g., the individual products and customers. Some generic code to accomplish this is as follows:


OWLNamedClass customerClass = owlModel.getOWLNamedClass("Customer");
OWLDatatypeProperty firstNameProperty = owlModel.getOWLDatatypeProperty("firstName");
OWLIndividual myCustomer = owlModel.getOWLIndividual("Customer42");
String firstName = (String) myCustomer.getPropertyValue(firstNameProperty);


Such code has several weaknesses. Among other things, it is dangerous to operate only on ontological resources in terms of their names as strings, and generic code quickly becomes hard to read. The following method, for example, calculates the sum of all purchases of a given customer:


private static float getPurchasesSum(RDFIndividual customer) {
    OWLModel owlModel = customer.getOWLModel();
    float sum = 0;
    RDFProperty purchasesProperty = owlModel.getRDFProperty("purchases");
    RDFProperty productProperty = owlModel.getRDFProperty("product");
    RDFProperty priceProperty = owlModel.getRDFProperty("price");
    Iterator purchases = customer.listPropertyValues(purchasesProperty);
    while(purchases.hasNext()) {
        RDFIndividual purchase = (RDFIndividual) purchases.next();
        RDFIndividual product = (RDFIndividual) purchase.getPropertyValue(productProperty);
        Float price = (Float) product.getPropertyValue(priceProperty);
        sum += price.floatValue();
    }
    return sum;
}


From an object-oriented perspective, it would be much more convenient to have a class such as:


public interface Customer {
    String getFirstName();
		
    void setFirstName(String value);
    ...
    float getPurchasesSum();
}


... so that we can use code like this:


Customer myCustomer = [somehow get Customer object from the OWLModel];
String firstName = myCustomer.getFirstName();

float sum = myCustomer.getPurchasesSum();


This is possible with the Protege-OWL API. You need to make Customer a subinterface of a suitable Protege-OWL base interface like OWLIndividual:


package com.demo.model;

public interface Customer extends OWLIndividual {
	    
    String getFirstName();
    void setFirstName(String value);
    ...
}


... and then provide a default implementation like the following scheme:


package com.demo.model.impl;
	
public class DefaultCustomer extends DefaultOWLIndividual implements Customer {
	
    public DefaultCustomer(KnowledgeBase kb, FrameID id) {
        super(kb, id);
    }
	    
    public String getFirstName() {
        RDFProperty property = getOWLModel().getRDFProperty("firstName");
        return (String) getPropertyValue(property);
    }
		
    public void setFirstName(String value) {
        RDFProperty property = getOWLModel().getRDFProperty("firstName");
        setPropertyValue(property, value);
    }

    ...
}


Note that these interfaces and their implementations can be easily generated using a code generator (which is in fact available for the Protege-OWL editor). If these classes are part of the project's classpath, then we can already use the following call to "typecast" generic resources into convenience classes:


Customer myCustomer = (Customer) owlModel.getRDFResource("Customer42").as(Customer.class);
String firstName = myCustomer.getFirstName()


The key here is the RDFResource.as() method, which allows programmers to get a different "view" of a given resource. The method RDFResource.canAs() can be used to test whether a given object can be cast into a given interface.

This mechanism allows programmers to build true object-oriented programs out of OWL ontologies. It is possible to add methods to the generated Java interfaces and implementations. In the following example, the interface Customer_ and its implementation DefaultCustomer_ are generated automatically, while the subclasses Customer and DefaultCustomer contain methods added by a programmer. The separation into generated classes and hand-coded classes allows programmers to overwrite the old classes without breaking anything.


package edu.stanford.smi.protegex.owlx.examples.javaDemo.model;

import edu.stanford.smi.protegex.owl.model.RDFIndividual;
import edu.stanford.smi.protegex.owl.model.RDFProperty;

import java.util.Collection;
import java.util.Iterator;

/**
 * Generated by Protégé-OWL  (http://protege.stanford.edu/plugins/owl).
 * Source OWL Class: http://www.owl-ontologies.com/javaDemo.owl#Customer
 *
 * @version generated on Sat Feb 19 17:10:28 EST 2005
 */
public interface Customer_ extends OWLIndividual {

    // Property http://www.owl-ontologies.com/javaDemo.owl#firstName
    String getFirstName();
    RDFProperty getFirstNameProperty();
    boolean hasFirstName();
    void setFirstName(String newFirstName);

    // Property http://www.owl-ontologies.com/javaDemo.owl#lastName
    String getLastName();
    RDFProperty getLastNameProperty();
    boolean hasLastName();
    void setLastName(String newLastName);

    // Property http://www.owl-ontologies.com/javaDemo.owl#purchases
    Collection getPurchases();
    RDFProperty getPurchasesProperty();
    boolean hasPurchases();
    Iterator listPurchases();
    void addPurchases(Purchase newPurchases);
    void removePurchases(Purchase oldPurchases);
    void setPurchases(Collection newPurchases);

    // Property http://www.owl-ontologies.com/javaDemo.owl#zipCode
    int getZipCode();
    RDFProperty getZipCodeProperty();
    boolean hasZipCode();
    void setZipCode(int newZipCode);
}

-----

package edu.stanford.smi.protegex.owlx.examples.javaDemo.model.impl;

import edu.stanford.smi.protege.model.FrameID;
import edu.stanford.smi.protegex.owl.model.RDFProperty;
import edu.stanford.smi.protegex.owl.model.impl.DefaultRDFIndividual;
import edu.stanford.smi.protegex.owlx.examples.javaDemo.model.Customer_;
import edu.stanford.smi.protegex.owlx.examples.javaDemo.model.Purchase;

import java.util.Collection;
import java.util.Iterator;

/**
 * Generated by Protégé-OWL  (http://protege.stanford.edu/plugins/owl).
 * Source OWL Class: http://www.owl-ontologies.com/javaDemo.owl#Customer
 *
 * @version generated on Sat Feb 19 17:10:28 EST 2005
 */
public class DefaultCustomer_ extends DefaultRDFIndividual
        implements Customer_ {

    public DefaultCustomer_(OWLModel owlModel, FrameID id) {
        super(owlModel, id);
    }

    public DefaultCustomer_() {
    }

    // Property http://www.owl-ontologies.com/javaDemo.owl#firstName
    public String getFirstName() {
        return (String) getPropertyValue(getFirstNameProperty());
    }

    public RDFProperty getFirstNameProperty() {
        final String uri = "http://www.owl-ontologies.com/javaDemo.owl#firstName";
        final String name = getOWLModel().getResourceNameForURI(uri);
        return getOWLModel().getRDFProperty(name);
    }

    public boolean hasFirstName() {
        return getPropertyValueCount(getFirstNameProperty()) > 0;
    }

    public void setFirstName(String newFirstName) {
        setPropertyValue(getFirstNameProperty(), newFirstName);
    }

    // Property http://www.owl-ontologies.com/javaDemo.owl#lastName
    public String getLastName() {
        return (String) getPropertyValue(getLastNameProperty());
    }

    public RDFProperty getLastNameProperty() {
        final String uri = "http://www.owl-ontologies.com/javaDemo.owl#lastName";
        final String name = getOWLModel().getResourceNameForURI(uri);
        return getOWLModel().getRDFProperty(name);
    }

    public boolean hasLastName() {
        return getPropertyValueCount(getLastNameProperty()) > 0;
    }

    public void setLastName(String newLastName) {
        setPropertyValue(getLastNameProperty(), newLastName);
    }

    // Property http://www.owl-ontologies.com/javaDemo.owl#purchases
    public Collection getPurchases() {
        return getPropertyValuesAs(getPurchasesProperty(), Purchase.class);
    }

    public RDFProperty getPurchasesProperty() {
        final String uri = "http://www.owl-ontologies.com/javaDemo.owl#purchases";
        final String name = getOWLModel().getResourceNameForURI(uri);
        return getOWLModel().getRDFProperty(name);
    }

    public boolean hasPurchases() {
        return getPropertyValueCount(getPurchasesProperty()) > 0;
    }

    public Iterator listPurchases() {
        return listPropertyValuesAs(getPurchasesProperty(), Purchase.class);
    }

    public void addPurchases(Purchase newPurchases) {
        addPropertyValue(getPurchasesProperty(), newPurchases);
    }

    public void removePurchases(Purchase oldPurchases) {
        removePropertyValue(getPurchasesProperty(), oldPurchases);
    }

    public void setPurchases(Collection newPurchases) {
        setPropertyValues(getPurchasesProperty(), newPurchases);
    }

    // Property http://www.owl-ontologies.com/javaDemo.owl#zipCode
    public int getZipCode() {
        return getPropertyValueLiteral(getZipCodeProperty()).getInt();
    }

    public RDFProperty getZipCodeProperty() {
        final String uri = "http://www.owl-ontologies.com/javaDemo.owl#zipCode";
        final String name = getOWLModel().getResourceNameForURI(uri);
        return getOWLModel().getRDFProperty(name);
    }

    public boolean hasZipCode() {
        return getPropertyValueCount(getZipCodeProperty()) > 0;
    }

    public void setZipCode(int newZipCode) {
        setPropertyValue(getZipCodeProperty(), new Integer(newZipCode));
    }
}

----- 

package edu.stanford.smi.protegex.owlx.examples.javaDemo.model;
 
/**
 * An extension of Customer_ with extra methods.
 */
public interface Customer extends Customer_ {
 
    /**
     * Computes the sum of all products that were purchased by this Customer.
     * @return the sum of all purchases
     */
    public float getPurchasesSum();
}

-----

package edu.stanford.smi.protegex.owlx.examples.javaDemo.model.impl;
 
import edu.stanford.smi.protege.model.FrameID;
import edu.stanford.smi.protegex.owlx.examples.javaDemo.model.Customer;
import edu.stanford.smi.protegex.owlx.examples.javaDemo.model.Product;
import edu.stanford.smi.protegex.owlx.examples.javaDemo.model.Purchase;
 
import java.util.Iterator;
 
public class DefaultCustomer extends DefaultCustomer_
        implements Customer {
 
    public DefaultCustomer(OWLModel owlModel, FrameID id) {
        super(owlModel, id);
    }

    public DefaultCustomer() {
    }

    public String getBrowserText() {
        return getFirstName() + " " + getLastName();
    }

    public float getPurchasesSum() {
        float sum = 0;
        Iterator purchases = listPurchases();
        while (purchases.hasNext()) {
            Purchase purchase = (Purchase) purchases.next();
            Product product = purchase.getProduct();
            sum += product.getPrice();
        }
        return sum;
    }
}


Note that although the implementation classes have constructors, instances of these classes can only be created indirectly, through the OWLModel. For that purpose, the code generator also creates a "factory" class that can be used to create new objects in the OWLModel:


package edu.stanford.smi.protegex.owlx.examples.javaDemo.model;

import edu.stanford.smi.protegex.owl.model.OWLModel;
import edu.stanford.smi.protegex.owl.model.RDFSNamedClass;

/**
 * Generated by Protégé-OWL (http://protege.stanford.edu/plugins/owl).
 *
 * @version generated on Mon Feb 21 10:53:08 EST 2005
 */
public class MyFactory {

    private OWLModel owlModel;

    public MyFactory(OWLModel owlModel) {
        this.owlModel = owlModel;
    }

    public RDFSNamedClass getCustomerClass() {
        final String uri = "http://www.owl-ontologies.com/javaDemo.owl#Customer";
        final String name = owlModel.getResourceNameForURI(uri);
        return owlModel.getRDFSNamedClass(name);
    }

    public Customer createCustomer(String name) {
        final RDFSNamedClass cls = getCustomerClass();
        return (Customer) cls.createInstance(name).as(Customer.class);
    }

    public RDFSNamedClass getPurchaseClass() {
        final String uri = "http://www.owl-ontologies.com/javaDemo.owl#Purchase";
        final String name = owlModel.getResourceNameForURI(uri);
        return owlModel.getRDFSNamedClass(name);
    }

    public Purchase createPurchase(String name) {
        final RDFSNamedClass cls = getPurchaseClass();
        return (Purchase) cls.createInstance(name).as(Purchase.class);
    }

    public RDFSNamedClass getProductClass() {
        final String uri = "http://www.owl-ontologies.com/javaDemo.owl#Product";
        final String name = owlModel.getResourceNameForURI(uri);
        return owlModel.getRDFSNamedClass(name);
    }

    public Product createProduct(String name) {
        final RDFSNamedClass cls = getProductClass();
        return (Product) cls.createInstance(name).as(Product.class);
    }
}


It then becomes straight-forward to create, modify, and query the ontological resources using their object-oriented convenience classes.


private static Purchase createPurchase(Customer customer, Product product, String date) {
    OWLModel owlModel = customer.getOWLModel();
    Purchase purchase = new MyFactory(owlModel).createPurchase(null);
    purchase.setCustomer(customer);
    purchase.setProduct(product);
    RDFSDatatype xsdDate = owlModel.getRDFSDatatypeByName("xsd:date");
    purchase.setDate(owlModel.createRDFSLiteral(date, xsdDate);
    return purchase;
}