package org.aksw.rdfunit.model.impl.shacl; import com.google.common.collect.ImmutableSet; import lombok.*; import org.aksw.rdfunit.enums.ShapeTargetType; import org.aksw.rdfunit.model.impl.ResultAnnotationImpl; import org.aksw.rdfunit.model.interfaces.ResultAnnotation; import org.aksw.rdfunit.model.interfaces.TestCase; import org.aksw.rdfunit.model.interfaces.TestCaseAnnotation; import org.aksw.rdfunit.model.interfaces.shacl.PrefixDeclaration; import org.aksw.rdfunit.model.interfaces.shacl.ShapeTarget; import org.aksw.rdfunit.model.interfaces.shacl.TargetBasedTestCase; import org.aksw.rdfunit.utils.CommonNames; import org.aksw.rdfunit.utils.JenaUtils; import org.aksw.rdfunit.vocabulary.SHACL; import org.apache.jena.query.ParameterizedSparqlString; import org.apache.jena.query.QuerySolution; import org.apache.jena.query.QuerySolutionMap; import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.ResourceFactory; import java.util.Collection; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; /** * Used mainly for SHACL in order to inject a target from a shape to a sparql constraint * * @author Dimitris Kontokostas * @since 14/2/2016 12:55 μμ */ @Builder @ToString @EqualsAndHashCode(exclude = {"resource"}) public class TestCaseWithTarget implements TestCase, TargetBasedTestCase { @Getter @NonNull private final ShapeTarget target; @Getter @NonNull private final String filterSparql; @Getter @NonNull private final TestCase testCase; @Getter @NonNull private final Resource element = ResourceFactory.createProperty(JenaUtils.getUniqueIri()); public TestCaseWithTarget(ShapeTarget target, String filterSparql, TestCase testCase) { this.target = target; this.filterSparql = filterSparql; this.testCase = testCase; } @Override public String getSparqlWhere() { String withFocus = testCase.getSparqlWhere(); if(target instanceof ShapeTargetValueShape) { // inserting the focus node in the group statement withFocus = withFocus.replaceFirst("(?i)GROUP BY\\s+\\?" + CommonNames.This, "GROUP BY ?" + CommonNames.This + " ?focusNode"); } return injectTargetInSparql(withFocus, this.target); } @Override public String getSparqlPrevalence() { if (testCase.getSparqlPrevalence().trim().isEmpty() // skip node targets && (! target.getTargetType().equals(ShapeTargetType.NodeTarget))) { return "SELECT (count(?this) AS ?total) WHERE { " + target.getPattern() + "}"; } return testCase.getSparqlPrevalence(); } @Override public RDFNode getFocusNode(QuerySolution solution) { String focusVar = getVariableAnnotations().stream() .filter(ra -> ra.getAnnotationProperty().equals(SHACL.focusNode)) .map(ResultAnnotation::getAnnotationVarName) .filter(Optional::isPresent) .map(Optional::get) .findFirst() .orElse(CommonNames.This); return solution.get(focusVar); } @Override public TestCaseAnnotation getTestCaseAnnotation() { // inject SHACL annotations // TODO this is not efficient, recreates it on every request but needs major refactoring to improve return createFromReferences(testCase.getTestCaseAnnotation(), target.getRelatedOntologyResources()); } @Override public Set<ResultAnnotation> getVariableAnnotations() { ImmutableSet.Builder<ResultAnnotation> builder = ImmutableSet.builder(); if(ShapeTargetValueShape.class.isAssignableFrom(target.getClass())) { getTestCaseAnnotation().getVariableAnnotations().forEach(va -> { if(! va.getAnnotationProperty().equals(SHACL.focusNode)) { builder.add(va); } }); // add also a new variable annotation for the focus node builder.add(new ResultAnnotationImpl.Builder(ResourceFactory.createResource(), SHACL.focusNode) .setVariableName(SHACL.focusNode.getLocalName()).build()); } else{ builder.addAll(getTestCaseAnnotation().getVariableAnnotations()); } return builder.build(); } @Override public Collection<PrefixDeclaration> getPrefixDeclarations() { return testCase.getPrefixDeclarations(); } private String injectSparqlSnippet(String sparqlQuery, String sparqlSnippet) { int bracketIndex = sparqlQuery.indexOf('{'); return sparqlQuery.substring(0,bracketIndex+1) + sparqlSnippet + sparqlQuery.substring(bracketIndex+1); } private String injectTargetInSparql(String sparqlQuery, ShapeTarget target) { String queryWithFilter = injectSparqlSnippet(sparqlQuery, filterSparql); String queryWithTarget = injectTarget(queryWithFilter, target); return queryWithTarget; } private static TestCaseAnnotation createFromReferences(TestCaseAnnotation annotation, Collection<Resource> references) { ImmutableSet.Builder<String> referencesBuilder = ImmutableSet.builder(); ImmutableSet<String> finalReferences = referencesBuilder .addAll(references.stream().map(Resource::getURI).collect(Collectors.toSet())) .addAll(annotation.getReferences()).build(); ImmutableSet.Builder<ResultAnnotation> resultAnnotationBuilder = ImmutableSet.builder(); resultAnnotationBuilder .addAll(annotation.getResultAnnotations()) .addAll(annotation.getVariableAnnotations()); return new TestCaseAnnotation( annotation.getElement(), annotation.getGenerated(), annotation.getAutoGeneratorURI(), annotation.getAppliesTo(), annotation.getSourceUri(), finalReferences, annotation.getDescription(), annotation.getTestCaseLogLevel(), resultAnnotationBuilder.build() ); } private static final String thisVar = "\\$this_asdf_1234_qwer"; private String injectTarget(String sparqlQuery, ShapeTarget target) { String changedQuery = sparqlQuery; if (target.getTargetType().equals(ShapeTargetType.NodeTarget)) { changedQuery = sparqlQuery .replaceFirst("[\\$\\?]this", thisVar ) .replaceAll("(?i)BOUND\\s*\\(\\s*[\\$\\?]this\\s*\\)", "BOUND\\("+ thisVar+"\\)") .replaceAll("(?i)GROUP\\s+BY\\s+[\\$\\?]this", "GROUP BY "+ thisVar); QuerySolutionMap qsm = new QuerySolutionMap(); qsm.add(CommonNames.This, target.getNode()); ParameterizedSparqlString pq = new ParameterizedSparqlString(changedQuery, qsm); changedQuery = pq.toString(); changedQuery = changedQuery .replaceFirst(thisVar, "\\$this") .replaceAll("(?i)BOUND\\("+ thisVar+"\\)", "BOUND\\(\\?this\\)") .replaceAll("(?i)GROUP BY "+ thisVar, "GROUP BY \\$this"); } return injectSparqlSnippet(changedQuery, target.getPattern()); } }