/*
* OWL-S Matchmaker
*
* COPYRIGHT NOTICE
*
* Copyright (C) 2005 DFKI GmbH, Germany
* Developed by Benedikt Fries, Matthias Klusch
*
* The code is free for non-commercial use only.
* You can redistribute it and/or modify it under the terms
* of the Mozilla Public License version 1.1 as
* published by the Mozilla Foundation at
* http://www.mozilla.org/MPL/MPL-1.1.txt
*/
package owlsmx;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Vector;
import org.mindswap.owl.OWLConfig;
import org.mindswap.owl.OWLFactory;
import org.mindswap.owls.service.Service;
import org.mindswap.pellet.TuBox.NotUnfoldableException;
import owlsmx.Indexer.SimpleIndex;
import owlsmx.data.ConceptServiceRegistry;
import owlsmx.data.DOM;
import owlsmx.data.ExtendedServiceInformation;
import owlsmx.data.InputServiceContainer;
import owlsmx.data.LocalOntologyContainer;
import owlsmx.data.MatchingResult;
import owlsmx.data.OutputServiceContainer;
import owlsmx.data.ServiceInformation;
import owlsmx.exceptions.ConceptNotFoundException;
import owlsmx.exceptions.MatchingException;
import owlsmx.io.ErrorLog;
import owlsmx.reasoning.PelletReasoner;
import owlsmx.similaritymeasures.SimilarityMeasure;
import owlsmx.utils.MatchmakerUtils;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.rdf.model.RDFNode;
/**
* OWLSMXMatchmaker is the core class of the entire matchmaker.
* It implements the matching algorithm of the same name.
*
* @author bEn Fries
*
*/
public class OWLSMXMatchmaker extends SimilarityMatchmaker {
private org.mindswap.owl.OWLKnowledgeBase base = OWLFactory.createKB();
private LocalOntologyContainer localOntologyContainer = new LocalOntologyContainer();
private ConceptServiceRegistry registry;
private PelletReasoner reason;
private void init(SimilarityMeasure similar) {
reason = new PelletReasoner();
registry= ConceptServiceRegistry.instanceOf();
sim = similar;
}
/**
* Initializes the matchmaker without any similarity measure and
* hence purely semenatic matching
*/
public OWLSMXMatchmaker() {
init(null);
}
/**
* Initializes the matchmaker with the given similarity measure
*
* @param similar Similarity measure to be used
*/
public OWLSMXMatchmaker(SimilarityMeasure similar){
init(similar);
}
/**
* Initializes the matchmaker with the given similarity measure
*
* @param sim Similarity measure to be used
*/
public OWLSMXMatchmaker(short sim) {
SimilarityMeasure similar = switchSimilarityMeasure(sim);
init(similar);
}
public String toString() {
return registry.toString();
}
public void print() {
System.err.println(this.getClass().toString());
System.err.println(toString());
}
/**
* Retrieves Services in the registry that are registered at certain concepts
*
* @param isInput if it's an input concept
* @param classes Set of OWLClass(es) with the concepts to be retrieved
* @param concept concept in the query we want candidate matches for
* @param degreeOfMatch degree of match of the concepts to be retrieved
* @return SortedSet of ExtendedServiceInformation(s) with the candidate services for these concepts
* @throws URISyntaxException
* @throws ConceptNotFoundException
*/
private SortedSet getServicesFromSet(boolean isInput, Set classes, OntClass concept, int degreeOfMatch) throws URISyntaxException, ConceptNotFoundException {
this.save();
SortedSet result = new TreeSet();
Iterator iter = classes.iterator();
Set services;
ServiceInformation info;
Iterator serviceIterator;
RDFNode clazz;
while (iter.hasNext()) {
clazz=(RDFNode)iter.next();
//toConcept=(OntClass) reason.getClass(,this.localOntology);
services = registry.getServices(isInput, clazz.toString() );
serviceIterator = services.iterator();
while(serviceIterator.hasNext()) {
info = (ServiceInformation) serviceIterator.next();
try {
if (useSyntacticFilter())
result.add(new ExtendedServiceInformation(info, degreeOfMatch, reason.unfoldTerm(clazz)));
else {
//purely semantic matching doesn't need the unfolded concept
result.add(new ExtendedServiceInformation(info, degreeOfMatch, ""));
}
}
catch(Exception e) {
e.printStackTrace();
}
}
}
return result;
}
/**
* Computes the set of candidates for a given service output concept
*
* @param concept URI (String) of the given concept
* @return SortedSet of ExtendedServiceInformation items
* @throws URISyntaxException If the URI is invalid
* @throws ConceptNotFoundException If the concept is not found
*/
private SortedSet getServicesForOutputConcept(String concept) throws URISyntaxException, ConceptNotFoundException {
OntClass clazz = localOntologyContainer.getOntClass(concept);
Set equivalent = reason.retrieveEquivalentClasses(clazz);
Set parents = reason.retrieveDirectParentClasses(clazz,equivalent);
Set ancestors = reason.retrieveAncestorClasses(clazz,equivalent,parents);
Set childs = reason.retrieveDirectSubClasses(clazz,equivalent);
Set decendants = reason.retrieveDescendantClasses(clazz,equivalent,childs);
SortedSet candidates = new TreeSet();
candidates.addAll(getServicesFromSet(false,equivalent,clazz,SimilarityMatchmaker.EXACT) );
candidates.addAll(getServicesFromSet(false,childs,clazz,SimilarityMatchmaker.PLUGIN) );
candidates.addAll(getServicesFromSet(false,decendants,clazz,SimilarityMatchmaker.SUBSUMES) );
candidates.addAll(getServicesFromSet(false,parents,clazz,SimilarityMatchmaker.SUBSUMED_BY) );
candidates.addAll(getServicesFromSet(false, reason.retrieveRemainingClasses(clazz, equivalent, parents, ancestors, childs, decendants),clazz,SimilarityMatchmaker.NEAREST_NEIGHBOUR) );
return candidates;
}
/**
* Computes the set of candidates for a given service input concept
*
* @param concept URI (String) of the given concept
* @return SortedSet of ExtendedServiceInformation items
* @throws URISyntaxException If the URI is invalid
* @throws ConceptNotFoundException If the concept is not found
*/
private SortedSet getServicesForInputConcept(String concept) throws URISyntaxException, ConceptNotFoundException {
OntClass clazz = localOntologyContainer.getOntClass(concept);
Set equivalent = reason.retrieveEquivalentClasses(clazz);
Set ancestors = reason.retrieveAllAncestorClasses(clazz);
SortedSet candidates = new TreeSet();
candidates.addAll(getServicesFromSet(true,equivalent,clazz,SimilarityMatchmaker.EXACT));
candidates.addAll(getServicesFromSet(true,ancestors,clazz,SimilarityMatchmaker.PLUGIN));
candidates.addAll(getServicesFromSet(true, reason.retrieveAllRemainingClasses(clazz, equivalent, ancestors),clazz,SimilarityMatchmaker.NEAREST_NEIGHBOUR) );
return candidates;
}
/**
* Computes the set of candidates for multiple service output concepts
*
* @param outConcepts Vector of URIs of service output concepts
* @return Map Service ID -> DOM
* @throws URISyntaxException If a URI is malformed
* @throws MatchingException If something else goes wrong
*/
private Map getOutputCandidates(Vector outConcepts) throws URISyntaxException, MatchingException {
if (outConcepts.size()==0) {
return registry.getAllServices(false);
}
OutputServiceContainer resultContainer= new OutputServiceContainer();
resultContainer.addServices(getServicesForOutputConcept(((URI) outConcepts.get(0)).toString()));
OutputServiceContainer serviceContainer;
for (int i = 1; i<outConcepts.size();i++) {
serviceContainer=new OutputServiceContainer();
serviceContainer.addServices(getServicesForOutputConcept(((URI) outConcepts.get(i)).toString()));
resultContainer.merge(serviceContainer);
}
System.out.println("Output candidates: " + resultContainer.getServiceMap().values().toString());
return resultContainer.getServiceMap();
}
/**
* Adds services without inputs to the set of input candidates
*
* @param inputCandidates
* @return
*/
private Map addEmptyInputs(Map inputCandidates) {
Map ServicesWithoutInput=registry.getAllServicesWithoutInput();
Map.Entry me;
ExtendedServiceInformation exInfo;
Iterator iter = ServicesWithoutInput.entrySet().iterator();
while (iter.hasNext()) {
me = (Map.Entry) iter.next();
exInfo = (ExtendedServiceInformation)me.getValue();
inputCandidates.put((Integer)me.getKey(),new ExtendedServiceInformation(exInfo.serviceID, true, exInfo.conceptID, exInfo.noConcepts, SimilarityMatchmaker.EXACT, 0.0));
}
return inputCandidates;
}
/**
* Computes the set of candidates for multiple service intput concepts
*
* @param inConcepts Vector of URIs of service output concepts
* @return Map Service ID -> DOM
* @throws URISyntaxException If a URI is malformed
* @throws MatchingException If something else goes wrong
*/
protected Map getInputCandidates(Vector inConcepts) throws URISyntaxException, ConceptNotFoundException {
if (inConcepts.size()<=0)
return registry.getAllServicesWithoutInput();
InputServiceContainer inputs = new InputServiceContainer();
SortedSet sort;
for (int i = 0; i<inConcepts.size();i++) {
sort = getServicesForInputConcept(((URI) inConcepts.get(i)).toString() );
inputs.addServices(sort);
}
System.out.println("Input candidates: " + inputs.getServices().values().toString());
return addEmptyInputs(inputs.getServices());
}
/**
* Converts a Set of DOM to a Set of ExtendedServiceInformation with the best degrees
* of each DOM
*
* @param serviceDOMs Set of DOM
* @return Set of ExtendedServiceInformation
*/
protected Set getServiceInformationFromDOM(Set serviceDOMs) {
Set result = new HashSet();
Iterator iter = serviceDOMs.iterator();
while (iter.hasNext()) {
result.add(((DOM)iter.next()).getBestDegree());
}
return result;
}
/**
* Converts the input candidates into a result set
* (necessary if a request has no outputs)
*
* @param inputCandidates
* @return
*/
private SortedSet inputCandidatesToResult(Map inputCandidates){
SortedSet result = new TreeSet();
ExtendedServiceInformation inInfo;
Map.Entry me;
Iterator iter = inputCandidates.entrySet().iterator();
while (iter.hasNext()) {
me = (Map.Entry) iter.next();
inInfo=(ExtendedServiceInformation) me.getValue();
result.add(new MatchingResult(inInfo , inInfo.unfoldedconcept, "") );
}
return result;
}
/**
* Performs purely semantic matching
* Basically it performs a intersection of the candidates for input- and output concepts
* The only difference is that the worst of both degree is used for the intersection
*
* @param inConcepts Vector of URIs of input concepts of the request
* @param outConcepts Vector of URIs of output concepts of the request
* @return SortedSet of relevant services
* @throws URISyntaxException Thrown if the in/outconcepts are not real URIs
* @throws MatchingException If something else goes wrong
*/
protected SortedSet semanticMatch(Vector inConcepts, Vector outConcepts) throws URISyntaxException, MatchingException {
Map input = getInputCandidates(inConcepts);
if ( (outConcepts==null) || (outConcepts.size()<=0) )
return inputCandidatesToResult(input);
Map output = getOutputCandidates(outConcepts);
SortedSet result = new TreeSet();
Map.Entry me;
Integer ID;
ExtendedServiceInformation inInfo,outInfo;
DOM degree;
Iterator iter = input.entrySet().iterator();
while (iter.hasNext()) {
me = (Map.Entry) iter.next();
ID = (Integer)me.getKey();
if (output.containsKey(ID)) {
inInfo=(ExtendedServiceInformation) me.getValue();
degree = ((DOM) output.get(ID));
outInfo=degree.getBestDegree();
if ( (inInfo.degreeOfMatch>outInfo.degreeOfMatch) ||
( (inInfo.degreeOfMatch==outInfo.degreeOfMatch) &&
(inInfo.similarity<outInfo.similarity) ) )
result.add(new MatchingResult(inInfo , inInfo.unfoldedconcept, outInfo.unfoldedconcept) );
else
result.add(new MatchingResult(outInfo , inInfo.unfoldedconcept, outInfo.unfoldedconcept) );
}
}
return result;
}
/**
* Syntactic Filter that adjusts the semantic degree of match according to the actual similarity
*
* @param queryService name of the query service
* @param queryInputs input concepts (URL strings)
* @param queryOutputs output concepts (URL strings)
* @param semanticResults Results of the semantic Filter (MatchingResult)s
* @return SortedSet of MatchingResult items that match the request according to the hybrid filter
* @throws NotUnfoldableException If the TBox can't be unfolded
* @throws URISyntaxException If the input/output URI's are invalid
* @throws MatchingException If the similarity can't be computed
*/
protected SortedSet syntacticFilter(Service queryService, Vector queryInputs, Vector queryOutputs, SortedSet semanticResults) throws NotUnfoldableException, URISyntaxException, MatchingException {
SortedSet result = new TreeSet();
String input = reason.unfoldURIs(queryInputs);
String output = reason.unfoldURIs(queryOutputs);
double sim_input, sim_output;
MatchingResult info;
Iterator iter = semanticResults.iterator();
while(iter.hasNext()) {
sim_input=0.0;
sim_output=0.0;
info = (MatchingResult) iter.next();
if (!info.unfoldedInput.equals(""))
sim_input=sim.computeSimilarity(queryService.getURI().toString(),input,""+info.serviceID,info.unfoldedInput);
if (queryOutputs.size()>0)
sim_output = sim.computeSimilarity(queryService.getURI().toString(),output,""+info.serviceID,info.unfoldedOutput);
info.similarity = (sim_input+sim_output)/2;
if (info.similarity < getSyntacticTreshold()) {
if ( (info.degreeOfMatch == SimilarityMatchmaker.NEAREST_NEIGHBOUR) ||
(info.degreeOfMatch == SimilarityMatchmaker.SUBSUMED_BY) )
info.degreeOfMatch = SimilarityMatchmaker.FAIL;
else
result.add(info);
}
else
result.add(info);
}
// PassedTime.getTime(this.getClass().toString() + " syntacticFilter:");
return result;
}
/* (non-Javadoc)
* @see owlsmx.Matchmaker#addService(java.lang.Integer, org.mindswap.owls.service.Service)
*/
public void addService(Integer integer, URI profileURI){
try {
org.mindswap.owl.OWLOntology onto = base.read(profileURI);
Service service = onto.getService();
addService(integer, onto, MatchmakerUtils.getURIList(service.getProfile().getInputs()), MatchmakerUtils.getURIList(service.getProfile().getOutputs()) );
} catch (FileNotFoundException e) {
ErrorLog.instanceOf().report(this.getClass().toString() + "|addService: Could not add service from this URI" + profileURI.toString());
e.printStackTrace();
}
base.unload(profileURI);
}
/**
* Updates the reasoner with the local ontology
*/
private void updateReasoner() {
reason.clear();
reason.load((OntModel)localOntologyContainer.getOntology().getImplementation());
}
/**
* Little helper function which performs the actuall adding of a service
*
* @param serviceID
* @param onto
* @param inputurilist
* @param outputurilist
*/
private void addService(Integer serviceID,org.mindswap.owl.OWLOntology onto, Vector inputurilist, Vector outputurilist) {
try {
Set conceptsToAdd = new HashSet();
conceptsToAdd.addAll(inputurilist);
conceptsToAdd.addAll(outputurilist);
localOntologyContainer.processClasses(base, conceptsToAdd);
updateReasoner();
if (useSyntacticFilter())
SimpleIndex.instanceOf().addDocument(""+serviceID,reason.unfoldURIs(inputurilist)+ " " +reason.unfoldURIs(outputurilist));
registry.addConcepts(true,serviceID.intValue(), inputurilist );
registry.addConcepts(false, serviceID.intValue(), outputurilist );
} catch (Exception e) {e.printStackTrace();}
}
/* (non-Javadoc)
* @see owlsmx.Matchmaker#matchRequest(org.mindswap.owls.service.Service)
*/
public SortedSet matchRequest(URI profileURI) throws MatchingException{
try {
// System.err.println("Matching request: " + profileURI);
org.mindswap.owl.OWLOntology onto = base.read(profileURI);
Service service = onto.getService();
// System.err.println("Processing request" + service.getURI());
Vector inputurilist=MatchmakerUtils.getURIList(service.getProfile().getInputs());
Vector outputurilist=MatchmakerUtils.getURIList(service.getProfile().getOutputs());
Set conceptsToAdd = new HashSet();
conceptsToAdd.addAll(inputurilist);
conceptsToAdd.addAll(outputurilist);
// System.err.println("Parameters " + conceptsToAdd.toString());
localOntologyContainer.processClasses(base, conceptsToAdd);
updateReasoner();
base.unload(profileURI);
if (useSyntacticFilter()) {
SimpleIndex.instanceOf().addDocument(service.getURI().toString(),reason.unfoldURIs(inputurilist)+ " " + reason.unfoldURIs(outputurilist));
return syntacticFilter(service, inputurilist, outputurilist, semanticMatch(inputurilist, outputurilist) );
}
else {
return semanticMatch(inputurilist, outputurilist);
}
}
catch (Exception e){
e.printStackTrace();
throw new MatchingException(e.toString());
}
}
/* (non-Javadoc)
* @see owlsmx.Matchmaker#removeService(java.lang.Integer)
*/
public void removeService(Integer integer) {
registry.removeService(integer.intValue());
}
/* (non-Javadoc)
* @see owlsmx.Matchmaker#load()
*/
public boolean load() {
try {
boolean result = LocalOntologyContainer.load();
File file = new File("registry.data");
if (file.exists()) {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
registry = (ConceptServiceRegistry) in.readObject();
}
return result;
}
catch (Exception e) {
e.printStackTrace();
ErrorLog.instanceOf().report(e.toString());
}
return false;
}
/* (non-Javadoc)
* @see owlsmx.Matchmaker#save()
*/
public boolean save() {
try {
boolean result = localOntologyContainer.save();
ObjectOutput out = new ObjectOutputStream(new FileOutputStream("registry.data"));
out.writeObject(registry);
out.close();
return result;
}
catch (FileNotFoundException e) {
return false;
}
catch (IOException e) {
return false;
}
}
/* (non-Javadoc)
* @see owlsmx.SimilarityMatchmaker#clear()
*/
public void clear() {
File file = new File("registry.data");
if (file.exists()) {
file.delete();
}
file = new File("localOntology.owl");
if (file.exists()) {
file.delete();
}
ConceptServiceRegistry.instanceOf().clear();
SimpleIndex.instanceOf().clear();
}
/**
* Enables the support for profile hierarchies
*
*
* @param speedyButProblematic disables general check if a profile is valid - hence profile hierarchies don't cause problems but might lead to secondary problems
*/
public void enableProfileHierarchies(boolean speedyButProblematic) {
if (speedyButProblematic)
OWLConfig.setStrictConversion(false);
else
base.setReasoner("RDFS");
}
/**
* Disables the support for profile hierarchies
*/
public void disableProfileHierarchies() {
base.setReasoner(null);
OWLConfig.setStrictConversion(true);
}
}