/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.stanbol.reasoners.jena;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.stanbol.reasoners.jena.filters.PropertyFilter;
import org.apache.stanbol.reasoners.servicesapi.InconsistentInputException;
import org.apache.stanbol.reasoners.servicesapi.ReasoningService;
import org.apache.stanbol.reasoners.servicesapi.ReasoningServiceException;
import org.apache.stanbol.reasoners.servicesapi.UnsupportedTaskException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hp.hpl.jena.rdf.model.InfModel;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.reasoner.InfGraph;
import com.hp.hpl.jena.reasoner.Reasoner;
import com.hp.hpl.jena.reasoner.ValidityReport;
import com.hp.hpl.jena.reasoner.ValidityReport.Report;
import com.hp.hpl.jena.reasoner.rulesys.FBRuleReasoner;
import com.hp.hpl.jena.reasoner.rulesys.GenericRuleReasoner;
import com.hp.hpl.jena.reasoner.rulesys.Rule;
import com.hp.hpl.jena.vocabulary.RDF;
/**
* Abstract implementation of the {@see JenaReasoningService} interface
*/
public abstract class AbstractJenaReasoningService implements JenaReasoningService {
private Reasoner reasoner;
private static final Logger log = LoggerFactory.getLogger(AbstractJenaReasoningService.class);
/**
* This constructor sets the given reasoner instance as the default shared one.
*
* @param reasoner
*/
protected AbstractJenaReasoningService(Reasoner reasoner) {
this.reasoner = reasoner;
}
/**
* Gets the Jena reasoner instance (to be used by subclasses)
*
* @return
*/
protected Reasoner getReasoner() {
return reasoner;
}
/**
* Generic method to perform inferences
*/
@Override
public InfModel run(Model data) {
log.debug(" run(Model data)");
InfModel im = ModelFactory.createInfModel(this.reasoner, data);
im.prepare();
return im;
}
/**
* This method performs inferences creating a new specialized reasoner, which extends the capabilities of
* the default one and adds the given rule set.
*
* @param data
* @param rules
* @return
*/
@Override
public InfModel run(Model data, List<Rule> rules) {
log.debug(" run(Model data, List<Rule> rules)");
InfGraph inferredGraph = customReasoner(rules).bind(data.getGraph());
return ModelFactory.createInfModel(inferredGraph);
}
/**
* This method provides the default implementation for executing one of the default tasks.
*
* TODO: Add support for the filtered parameter on task 'classify'; TODO: The task 'classify' should also
* return rdfs:subClassOf statements.
*/
@Override
public Set<Statement> runTask(String taskID,
Model data,
List<Rule> rules,
boolean filtered,
Map<String,List<String>> parameters) throws UnsupportedTaskException,
ReasoningServiceException,
InconsistentInputException {
log.debug(" runTask(String taskID,Model data,List<Rule> rules,boolean filtered,Map<String,List<String>> parameters)");
if (taskID.equals(ReasoningService.Tasks.CLASSIFY)) {
if (rules != null) {
return classify(data, rules);
} else {
return classify(data);
}
} else if (taskID.equals(ReasoningService.Tasks.ENRICH)) {
if (rules != null) {
return enrich(data, rules, filtered);
} else {
return enrich(data, filtered);
}
} else throw new UnsupportedTaskException();
}
/**
* This method provides the default implementation for executing one of the default tasks with no
* additional arguments.
*
* TODO: Add support for the filtered parameter on task 'classify'; TODO: The task 'classify' should also
* return rdfs:subClassOf statements.
*/
@Override
public Set<Statement> runTask(String taskID, Model data) throws UnsupportedTaskException,
ReasoningServiceException,
InconsistentInputException {
log.debug(" runTask(String taskID, Model data)");
if (taskID.equals(ReasoningService.Tasks.CLASSIFY)) {
return classify(data);
} else if (taskID.equals(ReasoningService.Tasks.ENRICH)) {
return enrich(data);
} else throw new UnsupportedTaskException();
}
/**
* This method is called to build a custom reasoner to be used with a given rule set. Subclasses may want
* to specialize the default behavior, which simply merge the standard rule set with the given list
*
* Note: to customized the default reasoner instance, we need to create a new instance, to avoid to keep
* the configuration changes in the standard shared instance. This is not much efficient, because we
* create a new reasoner for each call which includes a specified rule set.
*
* In the future, we may want to implement a way to deploy customized shared reasoning services, based on
* a specific recipe.
*
* @param rules
* @return
*/
protected Reasoner customReasoner(List<Rule> customRules) {
log.debug(" customReasoner(List<Rule> customRules)");
List<Rule> standardRules = ((FBRuleReasoner) this.reasoner).getRules();
standardRules.addAll(customRules);
return new GenericRuleReasoner(standardRules);
}
/**
* Default implementation for task {@see ReasoningService.Tasks.CLASSIFY}. Classification: 1) Perform
* reasoning 2) Returns only rdf:type statements.
*
* This is a default implementation of task {@see ReasoningService.Tasks.CLASSIFY}. Subclasses may want to
* change it.
*
* TODO: This method should also return rdfs:subClassOf statements
*
* @param data
* @return
*/
protected Set<Statement> classify(Model data) {
log.debug(" classify(Model data)");
return run(data).listStatements().filterKeep(new PropertyFilter(RDF.type)).toSet();
}
/**
*
* Classification: 1) Perform reasoning on a reasoner customized with the given rule set 2) Returns only
* rdf:type statements
*
* This is a default implementation of task {@see ReasoningService.Tasks.CLASSIFY}. Subclasses may want to
* change it.
*
* TODO: This method should also return rdfs:subClassOf statements
*
* @param data
* @param rules
* @return
*/
protected Set<Statement> classify(Model data, List<Rule> rules) {
log.debug(" classify(Model data, List<Rule> rules)");
return run(data, rules).listStatements().filterKeep(new PropertyFilter(RDF.type)).toSet();
}
/**
* Enriching: 1) Perform reasoning 2) Returns all the statements (filtered = false) or only inferred ones
* (filtered = true)
*
* This is a default implementation of task {@see ReasoningService.Tasks.ENRICH}. Subclasses may want to
* change it.
*
* @param data
* @param rules
* @return
*/
protected Set<Statement> enrich(Model data, boolean filtered) {
log.debug(" enrich(Model data, boolean filtered)");
// Since the input model is modified by the reasoner,
// We keep the original list to prune the data after, if necessary
if(filtered){
Set<Statement> original = new HashSet<Statement>();
original.addAll(data.listStatements().toSet());
log.debug(" original statements are: {}",original.size());
InfModel i = run(data);
Set<Statement> inferred = i.listStatements().toSet();
log.debug(" inferred statements are: {}",inferred.size());
return prune(original, inferred);
}else{
return run(data).listStatements().toSet();
}
}
/**
* Removes the statements in the first set from the second set
*
* @param input
* @param statements
* @return
*/
protected final Set<Statement> prune(Set<Statement> first, Set<Statement> second) {
log.debug(" prune(Set<Statement> first[{}], Set<Statement> second[{}])",first.size(),second.size());
Set<Statement> remove = new HashSet<Statement>();
for (Statement s : second) {
if (first.contains(s)) {
remove.add(s);
}
}
log.debug(" ---- removing {} statements from {}",first.size(),second.size());
second.removeAll(remove);
return second;
}
/**
* Enriching: 1) Perform reasoning 2) Returns the inferred statements only. This is the same as
* enrich(data, true)
*
* This is a default implementation of task {@see ReasoningService.Tasks.ENRICH}. Subclasses may want to
* change it.
*
* @param data
* @return
*/
public Set<Statement> enrich(Model data) {
log.debug(" enrich(Model data)");
return enrich(data, true);
}
/**
* Enriching: 1) Perform reasoning on a reasoner customized with the given rule set 2) Returns all the
* statements (filtered = false) or only inferred ones (filtered = true)
*
* This is a default implementation of task {@see ReasoningService.Tasks.ENRICH} when a set of rules is
* given. Subclasses may want to change it.
*
* @param data
* @param rules
* @param filtered
* @return
*/
protected Set<Statement> enrich(Model data, List<Rule> rules, boolean filtered) {
log.debug(" enrich(Model data, List<Rule> rules, boolean filtered)");
// Since the input model is modified by the reasoner,
// We keep the original list to prune the data after, if necessary
if(filtered){
Set<Statement> original = new HashSet<Statement>();
original.addAll(data.listStatements().toSet());
log.debug(" original statements are: {}",original.size());
InfModel i = run(data, rules);
Set<Statement> inferred = i.listStatements().toSet();
log.debug(" inferred statements are: {}",inferred.size());
return prune(original, inferred);
}else{
return run(data, rules).listStatements().toSet();
}
}
/**
* Enriching: 1) Perform reasoning on a reasoner customized with the given rule set 2) Returns the
* inferred statements only. This is the same as enrich(data, rules, true)
*
* This is a default implementation of task {@see ReasoningService.Tasks.ENRICH} when a set of rules is
* given. Subclasses may want to change it.
*
* @param data
* @param rules
* @return
*/
protected Set<Statement> enrich(Model data, List<Rule> rules) {
log.debug(" enrich(Model data, List<Rule> rules)");
return enrich(data, rules, true);
}
/**
* Consistency check: whether this RDF is consistent or not
*
* @param data
* @return
*/
@Override
public boolean isConsistent(Model data) {
log.debug(" isConsistent(Model data)");
return isConsistent(run(data).validate());
}
/**
* Consistency check: whether this RDF is consistent or not
*
* We decide to apply a strict meaning of consistency. The alternative would be to use isValid() method,
* which tolerates classes that can't be instantiated
*
* @param data
* @param rules
* @return
*/
@Override
public boolean isConsistent(Model data, List<Rule> rules) {
log.debug(" isConsistent(Model data, List<Rule> rules)");
return isConsistent(run(data, rules).validate());
}
/**
* This internal method implements the logic of consistency.
*
* We decide to apply a strict meaning of consistency. The alternative would be to use isValid() method,
* which tolerates classes that can't be instantiated.
*
* Subclasses may want to change this behavior.
*
* @param report
* @return
*/
protected boolean isConsistent(ValidityReport report) {
log.debug(" isConsistent(ValidityReport report)");
if(log.isDebugEnabled()){
Iterator<Report> it = report.getReports();
while (it.hasNext()) {
log.debug("Report: {}", it.next());
}
}
return report.isClean();
}
@Override
public Class<Model> getModelType() {
return Model.class;
}
@Override
public Class<Rule> getRuleType() {
return Rule.class;
}
@Override
public Class<Statement> getStatementType() {
return Statement.class;
}
/**
* Subclasses may want to extend this
*/
@Override
public List<String> getSupportedTasks() {
return ReasoningService.Tasks.DEFAULT_TASKS;
}
/**
* Subclasses may need to change this
*/
public boolean supportsTask(String taskID) {
return getSupportedTasks().contains(taskID);
};
}