package at.ac.univie.mminf.qskos4j.issues.relations;
import at.ac.univie.mminf.qskos4j.issues.Issue;
import at.ac.univie.mminf.qskos4j.issues.concepts.AuthoritativeConcepts;
import at.ac.univie.mminf.qskos4j.util.Tuple;
import at.ac.univie.mminf.qskos4j.util.vocab.SparqlPrefix;
import org.openrdf.OpenRDFException;
import org.openrdf.model.Resource;
import org.openrdf.model.Value;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.query.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* Finds <a href="https://github.com/cmader/qSKOS/wiki/Quality-Issues#wiki-Unidirectionally_Related_Concepts">Unidirectionally Related Concepts</a>.
*/
public class UnidirectionallyRelatedConcepts extends Issue<UnidirectionallyRelatedConceptsResult> {
private String[][] inversePropertyPairs = {
{"skos:broader", "skos:narrower"},
{"skos:broaderTransitive", "skos:narrowerTransitive"},
{"skos:topConceptOf", "skos:hasTopConcept"},
{"skos:narrowMatch", "skos:broadMatch"},
{"skos:related", "skos:related"},
{"skos:relatedMatch", "skos:relatedMatch"},
{"skos:exactMatch", "skos:exactMatch"},
{"skos:closeMatch", "skos:closeMatch"}
};
private final Logger logger = LoggerFactory.getLogger(UnidirectionallyRelatedConcepts.class);
private Map<Tuple<Resource>, String> omittedInverseRelations = new HashMap<>();
private AuthoritativeConcepts authoritativeConcepts;
public UnidirectionallyRelatedConcepts(AuthoritativeConcepts authoritativeConcepts) {
super(authoritativeConcepts,
"urc",
"Unidirectionally Related Concepts",
"Concepts not including reciprocal relations",
IssueType.ANALYTICAL,
new URIImpl("https://github.com/cmader/qSKOS/wiki/Quality-Issues#unidirectionally-related-concepts"));
this.authoritativeConcepts = authoritativeConcepts;
}
@Override
protected UnidirectionallyRelatedConceptsResult invoke() throws OpenRDFException {
String authResourceIdentifier = authoritativeConcepts.getAuthResourceIdentifier();
for (String[] inversePropertyPair : inversePropertyPairs) {
TupleQuery query = repCon.prepareTupleQuery(QueryLanguage.SPARQL, createOmittedRelationsQuery(inversePropertyPair));
addToOmittedInverseRelationsMap(query.evaluate(), inversePropertyPair, authResourceIdentifier);
}
return new UnidirectionallyRelatedConceptsResult(omittedInverseRelations);
}
private String createOmittedRelationsQuery(String[] inverseRelations) {
return SparqlPrefix.SKOS +" "+ SparqlPrefix.RDFS +
"SELECT DISTINCT ?resource1 ?resource2 "+
"WHERE {" +
"{?resource1 " +inverseRelations[0]+ " ?resource2 . "+
"FILTER NOT EXISTS {?resource2 "+inverseRelations[1]+ " ?resource1}}" +
"UNION" +
"{?resource1 " +inverseRelations[1]+ " ?resource2 . "+
"FILTER NOT EXISTS {?resource2 "+inverseRelations[0]+ " ?resource1}}" +
"}";
}
private void addToOmittedInverseRelationsMap(
TupleQueryResult result,
String[] inversePropertyPair,
String authResourceIdentifier) throws QueryEvaluationException
{
while (result.hasNext()) {
BindingSet queryResult = result.next();
Value value1 = queryResult.getValue("resource1");
Value value2 = queryResult.getValue("resource2");
String inverseProperties = inversePropertyPair[0] +"/"+ inversePropertyPair[1];
if (bothResourcesAreAuthoritative(value1, value2, authResourceIdentifier)) {
addToMap(value1, value2, inverseProperties);
}
}
}
private boolean bothResourcesAreAuthoritative(Value res1, Value res2, String authResourceIdentifier) {
if (authResourceIdentifier.isEmpty()) return true;
return res1.stringValue().contains(authResourceIdentifier) &&
res2.stringValue().contains(authResourceIdentifier);
}
private void addToMap(Value value1, Value value2, String inverseProperties)
{
try {
Resource resource1 = (Resource) value1;
Resource resource2 = (Resource) value2;
omittedInverseRelations.put(
new Tuple<>(resource1, resource2),
inverseProperties);
}
catch (ClassCastException e) {
logger.error("Resource expected for relation " +inverseProperties+ " (" +value1+ " <-> " +value2+ ")");
}
}
}