From 35020b8944bb54938a35e65fe4db27e3fc9b34c6 Mon Sep 17 00:00:00 2001 From: Dennis Schiese Date: Mon, 11 Sep 2023 15:11:20 +0200 Subject: [PATCH 01/15] Work in progress --- .../controller/ExplanationController.java | 9 +++ .../services/ExplanationService.java | 75 ++++++++++++++++++- src/main/resources/application.properties | 2 +- .../annotations_of_spot_intance_query.rq | 21 ++++++ .../fetch_all_annotation_types.rq | 10 +++ 5 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 src/main/resources/queries/queries_for_annotation_types/annotations_of_spot_intance_query.rq create mode 100644 src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq diff --git a/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java b/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java index 0e83f73..957b842 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java +++ b/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java @@ -10,6 +10,8 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; +import java.io.IOException; + @RestController @ControllerAdvice public class ExplanationController { @@ -54,7 +56,14 @@ public ResponseEntity getExplanations( else return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); } + } + @GetMapping("/explain/{graphURI}/{componentURI}") + public ResponseEntity explain( + @PathVariable String graphURI, + @PathVariable String componentURI + ) throws IOException { + return new ResponseEntity<>(this.explanationService.fetchAllAnnotation(graphURI, componentURI), HttpStatus.OK); } } diff --git a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java index dd54112..311b102 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java +++ b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java @@ -20,20 +20,26 @@ import java.io.IOException; import java.io.StringWriter; import java.text.DecimalFormat; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.util.*; @Service public class ExplanationService { + // Query files private static final String QUESTION_QUERY = "/queries/question_query.rq"; + private static final String ANNOTATIONS_QUERY = "/queries/queries_for_annotation_types/fetch_all_annotation_types.rq"; + + // Mappings private static final Map headerFormatMap = new HashMap<>() {{ put("application/rdf+xml", "RDFXML"); put("application/ld+json", "JSONLD"); put("text/turtle", "TURTLE"); }}; + private static final Map annotationsTypeAndQuery = new HashMap<>() {{ + // AnnotationOfInstance + put("annotationofspotinstance", "/queries/queries_for_annotation_types/annotations_of_spot_intance_query.rq"); + }}; + final String EXPLANATION_NAMESPACE = "urn:qanary:explanations#"; private final ObjectMapper objectMapper; Logger logger = LoggerFactory.getLogger(ExplanationService.class); @@ -350,4 +356,65 @@ public Model createSystemModel(Map models, ComponentPojo[] compon return systemExplanationModel; } + public List fetchAllAnnotation(String graphURI, String componentURI) throws IOException { + String query = buildSparqlQuery(graphURI, componentURI, ANNOTATIONS_QUERY); + + JsonNode results = this.explanationSparqlRepository.executeSparqlQuery(query); + ArrayNode resultsAsArray = (ArrayNode) results.get("bindings"); + logger.info("ArrayNode: {}", resultsAsArray); + ArrayList types = new ArrayList<>(); + for (JsonNode node : resultsAsArray + ) { + // Resource resource = (Resource) node.get("annotationType").get("value"); + } + + List explanations = createSpecificExplanations( + types.toArray(String[]::new), + graphURI + ); + + return explanations; + + } + + // Create a specific explanation for every annotation + + public List createSpecificExplanations(String[] usedTypes, String graphURI) throws IOException { + + logger.info("TypAndId-List: {}", usedTypes); + + List explanations = new ArrayList<>(); + + for (String type : usedTypes + ) { + explanations.addAll(createSpecificExplanation(type, graphURI)); + } + + return explanations; + } + + public List createSpecificExplanation(String type, String graphURI) throws IOException { + String query = buildSparqlQuery(graphURI, null, annotationsTypeAndQuery.get(type)); + List explanationsForCurrentType = new ArrayList<>(); + + if(Objects.equals(type, "annotationofspotinstance")) { + JsonNode result = this.explanationSparqlRepository.executeSparqlQuery(query); + ArrayNode resultsAsArrayNode = (ArrayNode) result.get("bindings"); + + Iterator arrayNodeIterator = resultsAsArrayNode.iterator(); + while(arrayNodeIterator.hasNext()) { + JsonNode currentObject = arrayNodeIterator.next(); + explanationsForCurrentType.add( + "At " + currentObject.get("annotatedAt").get("value") + " the component found a entity starting from position " + + currentObject.get("start").get("value") + " and ending at position " + currentObject.get("end").get("value") + " in the origin question." + ); + } + } + + logger.info("Explanations: {}", explanationsForCurrentType); + + return explanationsForCurrentType; + } + + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1ebb772..bd4d771 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,6 @@ spring.application.name=Qanary explanation service server.port=4000 -sparqlEndpoint=http://demos.swe.htwk-leipzig.de:40111/sparql +sparqlEndpoint=http://localhost:8080/sparql # swagger-ui custom path springdoc.swagger-ui.path=/swagger-ui.html # api-docs custom path diff --git a/src/main/resources/queries/queries_for_annotation_types/annotations_of_spot_intance_query.rq b/src/main/resources/queries/queries_for_annotation_types/annotations_of_spot_intance_query.rq new file mode 100644 index 0000000..43d7a84 --- /dev/null +++ b/src/main/resources/queries/queries_for_annotation_types/annotations_of_spot_intance_query.rq @@ -0,0 +1,21 @@ +PREFIX rdf: +PREFIX oa: +PREFIX qa: + +SELECT * +FROM ?graphURI +WHERE { + ?annotationId rdf:type qa:AnnotationOfSpotInstance + ?annotationId oa:hasTarget [ + a oa:SpecificResource; + oa:hasSource ?source; + oa:hasSelector [ + a oa:TextPositionSelector; + oa:start ?start; + oa:end ?end; + ] + ]. + ?annotationId oa:annotatedAt ?createdAt ; + oa:annotatedBy ?componentURI . +} + diff --git a/src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq b/src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq new file mode 100644 index 0000000..2038bfa --- /dev/null +++ b/src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq @@ -0,0 +1,10 @@ +PREFIX rdf: +PREFIX oa: +PREFIX qa: + +SELECT DISTINCT ?annotationType +FROM ?graphURI +WHERE { + ?annotationId rdf:type ?annotationType . + ?annotationId oa:annotatedBy ?annotatedBy +} \ No newline at end of file From 54f8260fd4721fc0c3076fa803127058b5dd8cdb Mon Sep 17 00:00:00 2001 From: Dennis Schiese Date: Mon, 11 Sep 2023 21:33:58 +0200 Subject: [PATCH 02/15] Refactored old functionalities --- .../controller/ExplanationController.java | 11 +- .../repositories/AbstractRepository.java | 11 ++ .../services/ExplanationService.java | 100 ++++++++++++------ .../annotations_of_spot_intance_query.rq | 2 +- .../fetch_all_annotation_types.rq | 4 +- .../controller/ExplanationControllerTest.java | 4 +- 6 files changed, 87 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java b/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java index 957b842..4a5b1d8 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java +++ b/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java @@ -44,13 +44,13 @@ public ResponseEntity getExplanations( @PathVariable(required = false) String componentURI, @RequestHeader(value = "accept", required = false) String acceptHeader) throws Exception { if (componentURI == null) { - String result = explanationService.explainQaSystem(graphURI, GENERAL_EXPLANATION_SPARQL_QUERY, acceptHeader); + String result = explanationService.explainQaSystem(graphURI, acceptHeader); if (result != null) return new ResponseEntity<>(result, HttpStatus.OK); else return new ResponseEntity<>(null, HttpStatus.NOT_ACCEPTABLE); } else { - String result = this.explanationService.explainSpecificComponent(graphURI, componentURI, GENERAL_EXPLANATION_SPARQL_QUERY, acceptHeader); + String result = this.explanationService.explainSpecificComponent(graphURI, componentURI, acceptHeader); if (result != null) return new ResponseEntity<>(result, HttpStatus.OK); else @@ -61,9 +61,10 @@ public ResponseEntity getExplanations( @GetMapping("/explain/{graphURI}/{componentURI}") public ResponseEntity explain( @PathVariable String graphURI, - @PathVariable String componentURI - ) throws IOException { - return new ResponseEntity<>(this.explanationService.fetchAllAnnotation(graphURI, componentURI), HttpStatus.OK); + @PathVariable String componentURI, + @RequestHeader(value = "accept", required = false) String acceptHeader + ) throws Exception { + return new ResponseEntity<>(this.explanationService.explainSpecificComponent(graphURI, componentURI, acceptHeader), HttpStatus.OK); } } diff --git a/src/main/java/com/wse/qanaryexplanationservice/repositories/AbstractRepository.java b/src/main/java/com/wse/qanaryexplanationservice/repositories/AbstractRepository.java index 23abbc7..8272f8f 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/repositories/AbstractRepository.java +++ b/src/main/java/com/wse/qanaryexplanationservice/repositories/AbstractRepository.java @@ -3,6 +3,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.wse.qanaryexplanationservice.services.ParameterStringBuilder; +import org.apache.jena.query.QueryExecution; +import org.apache.jena.query.ResultSet; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdfconnection.RDFConnection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.http.MediaType; @@ -15,6 +19,7 @@ import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -27,6 +32,7 @@ public abstract class AbstractRepository implements SparqlRepositoryIF { protected WebClient webClient; protected Environment environment; + private final RDFConnection rdfConnection = RDFConnection.connect("http://localhost:8080/sparql"); @Autowired protected AbstractRepository(Environment environment) throws MalformedURLException { @@ -82,4 +88,9 @@ public String fetchQuestion(String questionURI) { .bodyToMono(String.class) .block(); } + + public ResultSet executeSparqlQueryWithResultSet(String executableQuery) throws RuntimeException { + QueryExecution queryExecution = rdfConnection.query(executableQuery); + return queryExecution.execSelect(); + } } diff --git a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java index 311b102..302c311 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java +++ b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java @@ -8,7 +8,10 @@ import com.wse.qanaryexplanationservice.pojos.ExplanationObject; import com.wse.qanaryexplanationservice.repositories.ExplanationSparqlRepository; import eu.wdaqua.qanary.commons.triplestoreconnectors.QanaryTripleStoreConnector; +import org.apache.commons.lang3.StringUtils; +import org.apache.jena.query.QuerySolution; import org.apache.jena.query.QuerySolutionMap; +import org.apache.jena.query.ResultSet; import org.apache.jena.rdf.model.*; import org.apache.jena.vocabulary.RDF; import org.apache.jena.vocabulary.RDFS; @@ -21,6 +24,8 @@ import java.io.StringWriter; import java.text.DecimalFormat; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; @Service public class ExplanationService { @@ -28,6 +33,8 @@ public class ExplanationService { // Query files private static final String QUESTION_QUERY = "/queries/question_query.rq"; private static final String ANNOTATIONS_QUERY = "/queries/queries_for_annotation_types/fetch_all_annotation_types.rq"; + private Map stringResultSetMap = new HashMap<>(); + // Mappings private static final Map headerFormatMap = new HashMap<>() {{ @@ -57,21 +64,21 @@ public ExplanationService() { * * @param graphUri specific graphURI * @param componentUri specific componentURI - * @param fetchQueryEmpty Used query to fetch needed information * @return explanation as RDF Turtle */ - public String explainSpecificComponent(String graphUri, String componentUri, String fetchQueryEmpty, String header) throws Exception { + public String explainSpecificComponent(String graphUri, String componentUri, String header) throws Exception { logger.info("Passed header: {}", header); - Model model = createModel(graphUri, componentUri, fetchQueryEmpty); + Model model = createModel(graphUri, componentUri); return convertToDesiredFormat(header, model); } // Returns a model for a specific component - public Model createModel(String graphUri, String componentUri, String fetchQueryEmpty) throws Exception { - ExplanationObject[] explanationObjects = computeExplanationObjects(graphUri, componentUri, fetchQueryEmpty); - String contentDe = convertToTextualExplanation(explanationObjects, "de", componentUri); - String contentEn = convertToTextualExplanation(explanationObjects, "en", componentUri); + public Model createModel(String graphUri, String componentUri) throws Exception { + String contentDe = createTextualExplanation(graphUri, "de", componentUri); + String contentEn = createTextualExplanation(graphUri, "en", componentUri); + + String contentde = createTextualExplanation(graphUri, componentUri, "de"); return createModelForSpecificComponent(contentDe, contentEn, componentUri); } @@ -265,14 +272,14 @@ public String convertToTextualExplanation(ExplanationObject[] explanationObjects * * @param graphURI the only parameter given for a qa-system */ - public String explainQaSystem(String graphURI, String specificComponentQuery, String header) throws Exception { + public String explainQaSystem(String graphURI, String header) throws Exception { ComponentPojo[] components = annotationsService.getUsedComponents(graphURI); Map models = new HashMap<>(); for (ComponentPojo component : components ) { models.put(component.getComponent().getValue(), - createModel(graphURI, component.getComponent().getValue(), specificComponentQuery)); + createModel(graphURI, component.getComponent().getValue())); } String questionURI = fetchQuestionUri(graphURI); @@ -356,65 +363,88 @@ public Model createSystemModel(Map models, ComponentPojo[] compon return systemExplanationModel; } + public List createComponentExplanation(String graphURI, String componentURI, String lang) throws IOException { + + List types = new ArrayList<>(); + if(stringResultSetMap.isEmpty()) + types = fetchAllAnnotation(graphURI, componentURI); + + return createSpecificExplanations( + types.toArray(String[]::new), + graphURI, + lang + ); + + } + public List fetchAllAnnotation(String graphURI, String componentURI) throws IOException { String query = buildSparqlQuery(graphURI, componentURI, ANNOTATIONS_QUERY); - JsonNode results = this.explanationSparqlRepository.executeSparqlQuery(query); - ArrayNode resultsAsArray = (ArrayNode) results.get("bindings"); - logger.info("ArrayNode: {}", resultsAsArray); ArrayList types = new ArrayList<>(); - for (JsonNode node : resultsAsArray - ) { - // Resource resource = (Resource) node.get("annotationType").get("value"); + ResultSet resultSet = this.explanationSparqlRepository.executeSparqlQueryWithResultSet(query); + + while(resultSet.hasNext()) { + QuerySolution result = resultSet.next(); + RDFNode type = result.get("annotationType"); + String typeLocalName = type.asResource().getLocalName(); + logger.info("Annotation-Type found: {}", typeLocalName); + types.add(typeLocalName.toLowerCase()); // lower case for equality comparison w/ map keys } - List explanations = createSpecificExplanations( - types.toArray(String[]::new), - graphURI - ); - - return explanations; + return types; } // Create a specific explanation for every annotation - - public List createSpecificExplanations(String[] usedTypes, String graphURI) throws IOException { - - logger.info("TypAndId-List: {}", usedTypes); + public List createSpecificExplanations(String[] usedTypes, String graphURI, String lang) throws IOException { List explanations = new ArrayList<>(); for (String type : usedTypes ) { - explanations.addAll(createSpecificExplanation(type, graphURI)); + explanations.addAll(createSpecificExplanation(type, graphURI, lang)); } return explanations; } - public List createSpecificExplanation(String type, String graphURI) throws IOException { + public List createSpecificExplanation(String type, String graphURI, String lang) throws IOException { String query = buildSparqlQuery(graphURI, null, annotationsTypeAndQuery.get(type)); List explanationsForCurrentType = new ArrayList<>(); + ResultSet results = null; + if(!stringResultSetMap.containsKey(type)) + results = this.explanationSparqlRepository.executeSparqlQueryWithResultSet(query); + // TODO: Something similar to the Mapping approach? More generalization? if(Objects.equals(type, "annotationofspotinstance")) { - JsonNode result = this.explanationSparqlRepository.executeSparqlQuery(query); - ArrayNode resultsAsArrayNode = (ArrayNode) result.get("bindings"); - Iterator arrayNodeIterator = resultsAsArrayNode.iterator(); - while(arrayNodeIterator.hasNext()) { - JsonNode currentObject = arrayNodeIterator.next(); + while(results.hasNext()) { + QuerySolution currentObject = results.next(); explanationsForCurrentType.add( - "At " + currentObject.get("annotatedAt").get("value") + " the component found a entity starting from position " - + currentObject.get("start").get("value") + " and ending at position " + currentObject.get("end").get("value") + " in the origin question." + "At " + currentObject.get("createdAt").asLiteral().getString() + " it found an entity starting from position " + + currentObject.get("start").asLiteral().getInt() + " and ending at position " + currentObject.get("end").asLiteral().getInt() + + " in the origin question." ); } } - logger.info("Explanations: {}", explanationsForCurrentType); + logger.info("Created explanations: {}", explanationsForCurrentType); return explanationsForCurrentType; } + public String createTextualExplanation(String graphURI, String componentURI, String lang) throws IOException { + + List createdExplanations = createComponentExplanation(graphURI, componentURI, lang); + + AtomicInteger i = new AtomicInteger(); + List explanations = createdExplanations.stream().map((explanation) -> String.valueOf(i.incrementAndGet()) + " " + explanation + "\n").toList(); + + String result = "The component " + componentURI + " has added " + explanations.size() + " annotation(s) to the triplestore: " + + StringUtils.join(explanations, "\n"); + stringResultSetMap.clear(); + return result; + } + } diff --git a/src/main/resources/queries/queries_for_annotation_types/annotations_of_spot_intance_query.rq b/src/main/resources/queries/queries_for_annotation_types/annotations_of_spot_intance_query.rq index 43d7a84..4c46826 100644 --- a/src/main/resources/queries/queries_for_annotation_types/annotations_of_spot_intance_query.rq +++ b/src/main/resources/queries/queries_for_annotation_types/annotations_of_spot_intance_query.rq @@ -5,7 +5,7 @@ PREFIX qa: SELECT * FROM ?graphURI WHERE { - ?annotationId rdf:type qa:AnnotationOfSpotInstance + ?annotationId rdf:type qa:AnnotationOfSpotInstance. ?annotationId oa:hasTarget [ a oa:SpecificResource; oa:hasSource ?source; diff --git a/src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq b/src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq index 2038bfa..47befa0 100644 --- a/src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq +++ b/src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq @@ -5,6 +5,6 @@ PREFIX qa: SELECT DISTINCT ?annotationType FROM ?graphURI WHERE { - ?annotationId rdf:type ?annotationType . - ?annotationId oa:annotatedBy ?annotatedBy + ?annotationId rdf:type ?annotationType. + ?annotationId oa:annotatedBy ?annotatedBy. } \ No newline at end of file diff --git a/src/test/java/com/wse/qanaryexplanationservice/controller/ExplanationControllerTest.java b/src/test/java/com/wse/qanaryexplanationservice/controller/ExplanationControllerTest.java index 60f557c..f970ae6 100644 --- a/src/test/java/com/wse/qanaryexplanationservice/controller/ExplanationControllerTest.java +++ b/src/test/java/com/wse/qanaryexplanationservice/controller/ExplanationControllerTest.java @@ -70,7 +70,7 @@ class QaSystemExplanationTest { @BeforeEach void setup() throws Exception { - Mockito.when(explanationService.explainQaSystem(any(), any(), any())).thenReturn(testReturn); + Mockito.when(explanationService.explainQaSystem(any(), any())).thenReturn(testReturn); } @Test @@ -82,7 +82,7 @@ public void givenGraphURICallsSystemExplanation() throws Exception { assertEquals(200, mvcResult.getResponse().getStatus()); // check if SystemExplanation method was called once - verify(explanationService, times(1)).explainQaSystem(any(),any(),any()); + verify(explanationService, times(1)).explainQaSystem(any(),any()); } @Test From ad59ec33b7a0e945af578e646d65cfac07caf6d3 Mon Sep 17 00:00:00 2001 From: dschiese Date: Mon, 11 Sep 2023 23:06:52 +0200 Subject: [PATCH 03/15] Refactored creation of explanations --- .../services/ExplanationService.java | 58 +++++++++++-------- src/main/resources/application.properties | 2 +- .../explanations/annotation_of_instance/de | 1 + .../explanations/annotation_of_instance/en | 1 + 4 files changed, 38 insertions(+), 24 deletions(-) create mode 100644 src/main/resources/explanations/annotation_of_instance/de create mode 100644 src/main/resources/explanations/annotation_of_instance/en diff --git a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java index 302c311..2d8384b 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java +++ b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java @@ -18,14 +18,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; +import java.io.File; import java.io.IOException; import java.io.StringWriter; +import java.nio.file.Files; import java.text.DecimalFormat; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; @Service public class ExplanationService { @@ -33,23 +35,25 @@ public class ExplanationService { // Query files private static final String QUESTION_QUERY = "/queries/question_query.rq"; private static final String ANNOTATIONS_QUERY = "/queries/queries_for_annotation_types/fetch_all_annotation_types.rq"; - private Map stringResultSetMap = new HashMap<>(); - - // Mappings private static final Map headerFormatMap = new HashMap<>() {{ put("application/rdf+xml", "RDFXML"); put("application/ld+json", "JSONLD"); put("text/turtle", "TURTLE"); }}; - private static final Map annotationsTypeAndQuery = new HashMap<>() {{ + private static final Map annotationsTypeAndQuery = new HashMap<>() {{ // AnnotationOfInstance put("annotationofspotinstance", "/queries/queries_for_annotation_types/annotations_of_spot_intance_query.rq"); }}; + // Holds the annotationtype with path + private static final Map annotationTypeExplanationTemplate = new HashMap<>() {{ + put("annotationofspotinstance", "/explanations/annotation_of_instance/"); + }}; final String EXPLANATION_NAMESPACE = "urn:qanary:explanations#"; private final ObjectMapper objectMapper; Logger logger = LoggerFactory.getLogger(ExplanationService.class); + private Map stringResultSetMap = new HashMap<>(); @Autowired private ExplanationSparqlRepository explanationSparqlRepository; @Autowired @@ -62,8 +66,8 @@ public ExplanationService() { /** * Computes a textual explanation for a specific component on a specific graphURI * - * @param graphUri specific graphURI - * @param componentUri specific componentURI + * @param graphUri specific graphURI + * @param componentUri specific componentURI * @return explanation as RDF Turtle */ public String explainSpecificComponent(String graphUri, String componentUri, String header) throws Exception { @@ -75,12 +79,11 @@ public String explainSpecificComponent(String graphUri, String componentUri, Str // Returns a model for a specific component public Model createModel(String graphUri, String componentUri) throws Exception { - String contentDe = createTextualExplanation(graphUri, "de", componentUri); - String contentEn = createTextualExplanation(graphUri, "en", componentUri); String contentde = createTextualExplanation(graphUri, componentUri, "de"); + String contenten = createTextualExplanation(graphUri, componentUri, "en"); - return createModelForSpecificComponent(contentDe, contentEn, componentUri); + return createModelForSpecificComponent(contentde, contenten, componentUri); } // Creating the query, executing it and transform the response to an array of ExplanationObject objects @@ -366,7 +369,7 @@ public Model createSystemModel(Map models, ComponentPojo[] compon public List createComponentExplanation(String graphURI, String componentURI, String lang) throws IOException { List types = new ArrayList<>(); - if(stringResultSetMap.isEmpty()) + if (stringResultSetMap.isEmpty()) types = fetchAllAnnotation(graphURI, componentURI); return createSpecificExplanations( @@ -383,7 +386,7 @@ public List fetchAllAnnotation(String graphURI, String componentURI) thr ArrayList types = new ArrayList<>(); ResultSet resultSet = this.explanationSparqlRepository.executeSparqlQueryWithResultSet(query); - while(resultSet.hasNext()) { + while (resultSet.hasNext()) { QuerySolution result = resultSet.next(); RDFNode type = result.get("annotationType"); String typeLocalName = type.asResource().getLocalName(); @@ -401,7 +404,7 @@ public List createSpecificExplanations(String[] usedTypes, String graphU List explanations = new ArrayList<>(); for (String type : usedTypes - ) { + ) { explanations.addAll(createSpecificExplanation(type, graphURI, lang)); } @@ -412,19 +415,22 @@ public List createSpecificExplanation(String type, String graphURI, Stri String query = buildSparqlQuery(graphURI, null, annotationsTypeAndQuery.get(type)); List explanationsForCurrentType = new ArrayList<>(); ResultSet results = null; - if(!stringResultSetMap.containsKey(type)) + if (!stringResultSetMap.containsKey(type)) results = this.explanationSparqlRepository.executeSparqlQueryWithResultSet(query); // TODO: Something similar to the Mapping approach? More generalization? - if(Objects.equals(type, "annotationofspotinstance")) { + if (Objects.equals(type, "annotationofspotinstance")) { + + File templateFile = new ClassPathResource(annotationTypeExplanationTemplate.get(type) + lang).getFile(); + String template = new String(Files.readAllBytes(templateFile.toPath())); - while(results.hasNext()) { + while (results.hasNext()) { + String filledTemplate = template; QuerySolution currentObject = results.next(); - explanationsForCurrentType.add( - "At " + currentObject.get("createdAt").asLiteral().getString() + " it found an entity starting from position " - + currentObject.get("start").asLiteral().getInt() + " and ending at position " + currentObject.get("end").asLiteral().getInt() - + " in the origin question." - ); + filledTemplate = filledTemplate.replace("$createdAt", currentObject.get("createdAt").asLiteral().getString()); + filledTemplate = filledTemplate.replace("$start", String.valueOf(currentObject.get("start").asLiteral().getInt())); + filledTemplate = filledTemplate.replace("$end", String.valueOf(currentObject.get("end").asLiteral().getInt())); + explanationsForCurrentType.add(filledTemplate); } } @@ -434,14 +440,20 @@ public List createSpecificExplanation(String type, String graphURI, Stri } + /** + * Creates a textual explanation for all annotations made by the componentURI for a language lang. The explanation for the annotations are formatted as a list + * + * @param lang Currently supported en and de + * @return Complete explanation for the componentURI including all information to each annotation + */ public String createTextualExplanation(String graphURI, String componentURI, String lang) throws IOException { List createdExplanations = createComponentExplanation(graphURI, componentURI, lang); AtomicInteger i = new AtomicInteger(); - List explanations = createdExplanations.stream().map((explanation) -> String.valueOf(i.incrementAndGet()) + " " + explanation + "\n").toList(); + List explanations = createdExplanations.stream().map((explanation) -> String.valueOf(i.incrementAndGet()) + ". " + explanation).toList(); - String result = "The component " + componentURI + " has added " + explanations.size() + " annotation(s) to the triplestore: " + String result = "The component " + componentURI + " has added " + explanations.size() + " annotation(s) to the triplestore: " + StringUtils.join(explanations, "\n"); stringResultSetMap.clear(); return result; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index bd4d771..c017daa 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ spring.application.name=Qanary explanation service -server.port=4000 +server.port=4001 sparqlEndpoint=http://localhost:8080/sparql # swagger-ui custom path springdoc.swagger-ui.path=/swagger-ui.html diff --git a/src/main/resources/explanations/annotation_of_instance/de b/src/main/resources/explanations/annotation_of_instance/de new file mode 100644 index 0000000..fdd605b --- /dev/null +++ b/src/main/resources/explanations/annotation_of_instance/de @@ -0,0 +1 @@ +am $createdAt wurde eine Entität beginnend bei $start und Endposition $end aus der Ursprungsfrage gefunden. \ No newline at end of file diff --git a/src/main/resources/explanations/annotation_of_instance/en b/src/main/resources/explanations/annotation_of_instance/en new file mode 100644 index 0000000..1d2e603 --- /dev/null +++ b/src/main/resources/explanations/annotation_of_instance/en @@ -0,0 +1 @@ +at $createdAt it found an entity from position $start and ending at position $end in the origin question \ No newline at end of file From 6b636d35982e9107f67941ccd1e20b7485e67ded Mon Sep 17 00:00:00 2001 From: Dennis Schiese Date: Tue, 12 Sep 2023 09:34:41 +0200 Subject: [PATCH 04/15] Added prefixes for explanations --- .../services/ExplanationService.java | 36 +++++++++++++------ .../explanations/annotation_of_instance/de | 1 - .../explanations/annotation_of_instance/en | 1 - .../annotation_of_spot_instance/de_list_item | 1 + .../annotation_of_spot_instance/de_prefix | 1 + .../annotation_of_spot_instance/en_list_item | 1 + .../annotation_of_spot_instance/en_prefix | 1 + .../services/ServiceDataForTests.java | 2 +- 8 files changed, 31 insertions(+), 13 deletions(-) delete mode 100644 src/main/resources/explanations/annotation_of_instance/de delete mode 100644 src/main/resources/explanations/annotation_of_instance/en create mode 100644 src/main/resources/explanations/annotation_of_spot_instance/de_list_item create mode 100644 src/main/resources/explanations/annotation_of_spot_instance/de_prefix create mode 100644 src/main/resources/explanations/annotation_of_spot_instance/en_list_item create mode 100644 src/main/resources/explanations/annotation_of_spot_instance/en_prefix diff --git a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java index 2d8384b..adc1862 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java +++ b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java @@ -48,7 +48,7 @@ public class ExplanationService { // Holds the annotationtype with path private static final Map annotationTypeExplanationTemplate = new HashMap<>() {{ - put("annotationofspotinstance", "/explanations/annotation_of_instance/"); + put("annotationofspotinstance", "/explanations/annotation_of_spot_instance/"); }}; final String EXPLANATION_NAMESPACE = "urn:qanary:explanations#"; private final ObjectMapper objectMapper; @@ -391,7 +391,7 @@ public List fetchAllAnnotation(String graphURI, String componentURI) thr RDFNode type = result.get("annotationType"); String typeLocalName = type.asResource().getLocalName(); logger.info("Annotation-Type found: {}", typeLocalName); - types.add(typeLocalName.toLowerCase()); // lower case for equality comparison w/ map keys + types.add(typeLocalName.toLowerCase()); } return types; @@ -421,8 +421,9 @@ public List createSpecificExplanation(String type, String graphURI, Stri // TODO: Something similar to the Mapping approach? More generalization? if (Objects.equals(type, "annotationofspotinstance")) { - File templateFile = new ClassPathResource(annotationTypeExplanationTemplate.get(type) + lang).getFile(); - String template = new String(Files.readAllBytes(templateFile.toPath())); + String langExplanationPrefix = getStringFromFile(annotationTypeExplanationTemplate.get(type) + lang + "_prefix"); + explanationsForCurrentType.add(langExplanationPrefix); + String template = getStringFromFile(annotationTypeExplanationTemplate.get(type) + lang + "_list_item"); while (results.hasNext()) { String filledTemplate = template; @@ -433,17 +434,20 @@ public List createSpecificExplanation(String type, String graphURI, Stri explanationsForCurrentType.add(filledTemplate); } } - logger.info("Created explanations: {}", explanationsForCurrentType); - return explanationsForCurrentType; } + public String getStringFromFile(String path) throws IOException { + File file = new ClassPathResource(path).getFile(); + return new String(Files.readAllBytes(file.toPath())); + } + /** * Creates a textual explanation for all annotations made by the componentURI for a language lang. The explanation for the annotations are formatted as a list * - * @param lang Currently supported en and de + * @param lang Currently supported en_list_item and de_list_item * @return Complete explanation for the componentURI including all information to each annotation */ public String createTextualExplanation(String graphURI, String componentURI, String lang) throws IOException { @@ -451,12 +455,24 @@ public String createTextualExplanation(String graphURI, String componentURI, Str List createdExplanations = createComponentExplanation(graphURI, componentURI, lang); AtomicInteger i = new AtomicInteger(); - List explanations = createdExplanations.stream().map((explanation) -> String.valueOf(i.incrementAndGet()) + ". " + explanation).toList(); + List explanations = createdExplanations.stream().skip(1).map((explanation) -> String.valueOf(i.incrementAndGet()) + ". " + explanation).toList(); - String result = "The component " + componentURI + " has added " + explanations.size() + " annotation(s) to the triplestore: " - + StringUtils.join(explanations, "\n"); + String result = getResult(componentURI, lang, explanations, createdExplanations.get(0)); stringResultSetMap.clear(); return result; } + private static String getResult(String componentURI, String lang, List explanations, String prefix) { + String result = null; + if(lang == "en") { + result = "The component " + componentURI + " has added " + String.valueOf(explanations.size()) + " annotation(s) to the triplestore " + + prefix + "\n" + StringUtils.join(explanations, "\n"); + } + else if(lang == "de") { + result = "Die Komponente " + componentURI + "hat " + String.valueOf(explanations.size()) + " annotationen zum Triplestore hinzugefügt " + + prefix + "\n" + StringUtils.join(explanations, "\n"); + } + return result; + } + } diff --git a/src/main/resources/explanations/annotation_of_instance/de b/src/main/resources/explanations/annotation_of_instance/de deleted file mode 100644 index fdd605b..0000000 --- a/src/main/resources/explanations/annotation_of_instance/de +++ /dev/null @@ -1 +0,0 @@ -am $createdAt wurde eine Entität beginnend bei $start und Endposition $end aus der Ursprungsfrage gefunden. \ No newline at end of file diff --git a/src/main/resources/explanations/annotation_of_instance/en b/src/main/resources/explanations/annotation_of_instance/en deleted file mode 100644 index 1d2e603..0000000 --- a/src/main/resources/explanations/annotation_of_instance/en +++ /dev/null @@ -1 +0,0 @@ -at $createdAt it found an entity from position $start and ending at position $end in the origin question \ No newline at end of file diff --git a/src/main/resources/explanations/annotation_of_spot_instance/de_list_item b/src/main/resources/explanations/annotation_of_spot_instance/de_list_item new file mode 100644 index 0000000..b6ba62f --- /dev/null +++ b/src/main/resources/explanations/annotation_of_spot_instance/de_list_item @@ -0,0 +1 @@ +am $createdAt beginnend bei $start und endend an Position $end \ No newline at end of file diff --git a/src/main/resources/explanations/annotation_of_spot_instance/de_prefix b/src/main/resources/explanations/annotation_of_spot_instance/de_prefix new file mode 100644 index 0000000..5820b53 --- /dev/null +++ b/src/main/resources/explanations/annotation_of_spot_instance/de_prefix @@ -0,0 +1 @@ +und dabei wurden folgende Entitäten aus der Ursprungsfrage gefunden: \ No newline at end of file diff --git a/src/main/resources/explanations/annotation_of_spot_instance/en_list_item b/src/main/resources/explanations/annotation_of_spot_instance/en_list_item new file mode 100644 index 0000000..65c664a --- /dev/null +++ b/src/main/resources/explanations/annotation_of_spot_instance/en_list_item @@ -0,0 +1 @@ +at $createdAt starting from position $start and ending at position $end \ No newline at end of file diff --git a/src/main/resources/explanations/annotation_of_spot_instance/en_prefix b/src/main/resources/explanations/annotation_of_spot_instance/en_prefix new file mode 100644 index 0000000..af99380 --- /dev/null +++ b/src/main/resources/explanations/annotation_of_spot_instance/en_prefix @@ -0,0 +1 @@ +and each annotation from type AnnotationOfSpotInstance found the following entities from the origin question: \ No newline at end of file diff --git a/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java b/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java index c658845..25b82ba 100644 --- a/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java +++ b/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java @@ -3,7 +3,7 @@ public class ServiceDataForTests { private final String jsonExplanationObjects = "{\"bindings\":[{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.7885976199321202\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/String_theory\"},\"source\":{\"type\":\"uri\",\"value\":\"http://demos.swe.htwk-leipzig.de:40111/question/stored-question__text_0d5a7c26-ec6a-4702-8d7f-999849938d0e\"},\"start\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"0\"},\"end\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"4\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.615Z\"},\"score\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#decimal\",\"type\":\"typed-literal\",\"value\":\"0.9347568085631697\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.12726840785317994\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Real_number\"},\"source\":{\"type\":\"uri\",\"value\":\"http://demos.swe.htwk-leipzig.de:40111/question/stored-question__text_0d5a7c26-ec6a-4702-8d7f-999849938d0e\"},\"start\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"12\"},\"end\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"16\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.826Z\"},\"score\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#decimal\",\"type\":\"typed-literal\",\"value\":\"0.977747974809564\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.0010094414975868604\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Batman\"},\"source\":{\"type\":\"uri\",\"value\":\"http://demos.swe.htwk-leipzig.de:40111/question/stored-question__text_0d5a7c26-ec6a-4702-8d7f-999849938d0e\"},\"start\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"25\"},\"end\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"31\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:33.034Z\"},\"score\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#decimal\",\"type\":\"typed-literal\",\"value\":\"0.9999536254316278\"}}]}"; - private final String jsonResultObjects = "{\"bindings\":[{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.7885976199321202\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/String_theory\"},\"target\":{\"type\":\"bnode\",\"value\":\"b0\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.615Z\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.12726840785317994\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Real_number\"},\"target\":{\"type\":\"bnode\",\"value\":\"b1\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.826Z\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.0010094414975868604\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Batman\"},\"target\":{\"type\":\"bnode\",\"value\":\"b2\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:33.034Z\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.9600168603465082\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfAnswerSPARQL\"},\"body\":{\"type\":\"literal\",\"value\":\"PREFIX rdfs: \\nPREFIX dct: \\nPREFIX dbr: \\nPREFIX rdf: \\nPREFIX foaf: \\n\\nSELECT *\\nWHERE\\n { ?resource foaf:name ?answer ;\\n rdfs:label ?label\\n FILTER ( lang(?label) = \\\"en\\\" )\\n ?resource dct:subject dbr:Category:Superheroes_with_alter_egos\\n FILTER ( ! strstarts(lcase(?label), lcase(?answer)) )\\n VALUES ?resource { dbr:Batman }\\n }\\nORDER BY ?resource\\n\"},\"target\":{\"type\":\"uri\",\"value\":\"http://demos.swe.htwk-leipzig.de:40111/question/stored-question__text_0d5a7c26-ec6a-4702-8d7f-999849938d0e\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:QB-SimpleRealNameOfSuperHero\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:33.888Z\"}}]}"; + private final String jsonResultObjects = "{\"bindings\":[{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.7885976199321202\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/String_theory\"},\"target\":{\"type\":\"bnode\",\"value\":\"b0\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.615Z\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.12726840785317994\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Real_number\"},\"target\":{\"type\":\"bnode\",\"value\":\"b1\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.826Z\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.0010094414975868604\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Batman\"},\"target\":{\"type\":\"bnode\",\"value\":\"b2\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:33.034Z\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.9600168603465082\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfAnswerSPARQL\"},\"body\":{\"type\":\"literal\",\"value\":\"PREFIX rdfs: \\nPREFIX dct: \\nPREFIX dbr: \\nPREFIX rdf: \\nPREFIX foaf: \\n\\nSELECT *\\nWHERE\\n { ?resource foaf:name ?answer ;\\n rdfs:label ?label\\n FILTER ( lang(?label) = \\\"en_list_item\\\" )\\n ?resource dct:subject dbr:Category:Superheroes_with_alter_egos\\n FILTER ( ! strstarts(lcase(?label), lcase(?answer)) )\\n VALUES ?resource { dbr:Batman }\\n }\\nORDER BY ?resource\\n\"},\"target\":{\"type\":\"uri\",\"value\":\"http://demos.swe.htwk-leipzig.de:40111/question/stored-question__text_0d5a7c26-ec6a-4702-8d7f-999849938d0e\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:QB-SimpleRealNameOfSuperHero\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:33.888Z\"}}]}"; public String getJsonForExplanationObjects() { From f2cff8f5e41655f2bf1800f88490b7895a31dc0e Mon Sep 17 00:00:00 2001 From: Dennis Schiese Date: Tue, 12 Sep 2023 10:29:15 +0200 Subject: [PATCH 05/15] Adopted new structure to old functionalities and annotation-type AnnotationOfInstance --- .../services/ExplanationService.java | 38 ++++++++++++++----- .../annotation_of_instance/de_list_item | 1 + .../annotation_of_instance/de_prefix | 0 .../annotation_of_instance/en_list_item | 1 + .../annotation_of_instance/en_prefix | 0 .../resources/queries/general_explanation.rq | 2 +- .../annotations_of_instance_query.rq | 13 +++++++ 7 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 src/main/resources/explanations/annotation_of_instance/de_list_item create mode 100644 src/main/resources/explanations/annotation_of_instance/de_prefix create mode 100644 src/main/resources/explanations/annotation_of_instance/en_list_item create mode 100644 src/main/resources/explanations/annotation_of_instance/en_prefix create mode 100644 src/main/resources/queries/queries_for_annotation_types/annotations_of_instance_query.rq diff --git a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java index adc1862..e67d4f4 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java +++ b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java @@ -44,11 +44,13 @@ public class ExplanationService { private static final Map annotationsTypeAndQuery = new HashMap<>() {{ // AnnotationOfInstance put("annotationofspotinstance", "/queries/queries_for_annotation_types/annotations_of_spot_intance_query.rq"); + put("annotationofinstance", "/queries/queries_for_annotation_types/annotations_of_instance_query.rq"); }}; // Holds the annotationtype with path private static final Map annotationTypeExplanationTemplate = new HashMap<>() {{ put("annotationofspotinstance", "/explanations/annotation_of_spot_instance/"); + put("annotationofinstance", "/explanations/annotation_of_instance/"); }}; final String EXPLANATION_NAMESPACE = "urn:qanary:explanations#"; private final ObjectMapper objectMapper; @@ -413,18 +415,24 @@ public List createSpecificExplanations(String[] usedTypes, String graphU public List createSpecificExplanation(String type, String graphURI, String lang) throws IOException { String query = buildSparqlQuery(graphURI, null, annotationsTypeAndQuery.get(type)); - List explanationsForCurrentType = new ArrayList<>(); ResultSet results = null; if (!stringResultSetMap.containsKey(type)) results = this.explanationSparqlRepository.executeSparqlQueryWithResultSet(query); - // TODO: Something similar to the Mapping approach? More generalization? - if (Objects.equals(type, "annotationofspotinstance")) { + List explanationsForCurrentType = addingExplanations(type, lang, results); - String langExplanationPrefix = getStringFromFile(annotationTypeExplanationTemplate.get(type) + lang + "_prefix"); - explanationsForCurrentType.add(langExplanationPrefix); - String template = getStringFromFile(annotationTypeExplanationTemplate.get(type) + lang + "_list_item"); + logger.info("Created explanations: {}", explanationsForCurrentType); + return explanationsForCurrentType; + } + public List addingExplanations(String type, String lang, ResultSet results) throws IOException { + + List explanationsForCurrentType = new ArrayList<>(); + String langExplanationPrefix = getStringFromFile(annotationTypeExplanationTemplate.get(type) + lang + "_prefix"); + explanationsForCurrentType.add(langExplanationPrefix); + String template = getStringFromFile(annotationTypeExplanationTemplate.get(type) + lang + "_list_item"); + + if (Objects.equals(type, "annotationofspotinstance")) { while (results.hasNext()) { String filledTemplate = template; QuerySolution currentObject = results.next(); @@ -434,7 +442,17 @@ public List createSpecificExplanation(String type, String graphURI, Stri explanationsForCurrentType.add(filledTemplate); } } - logger.info("Created explanations: {}", explanationsForCurrentType); + else if(Objects.equals(type, "annotationofinstance")) { + while(results.hasNext()) { + String filledTemplate = template; + QuerySolution currentObject = results.next(); + filledTemplate = filledTemplate.replace("$createdAt", currentObject.get("createdAt").asLiteral().getString()); + filledTemplate = filledTemplate.replace("$score", String.valueOf(currentObject.get("score").asLiteral().getDouble())); + filledTemplate = filledTemplate.replace("$body", currentObject.get("body").asResource().toString()); + explanationsForCurrentType.add(filledTemplate); + } + } + return explanationsForCurrentType; } @@ -455,8 +473,8 @@ public String createTextualExplanation(String graphURI, String componentURI, Str List createdExplanations = createComponentExplanation(graphURI, componentURI, lang); AtomicInteger i = new AtomicInteger(); + // skips the first prefix // TODO: needs to be done better since there could me more than one prefix (if several annot. type are provided) List explanations = createdExplanations.stream().skip(1).map((explanation) -> String.valueOf(i.incrementAndGet()) + ". " + explanation).toList(); - String result = getResult(componentURI, lang, explanations, createdExplanations.get(0)); stringResultSetMap.clear(); return result; @@ -465,11 +483,11 @@ public String createTextualExplanation(String graphURI, String componentURI, Str private static String getResult(String componentURI, String lang, List explanations, String prefix) { String result = null; if(lang == "en") { - result = "The component " + componentURI + " has added " + String.valueOf(explanations.size()) + " annotation(s) to the triplestore " + result = "The component " + componentURI + " has added " + String.valueOf(explanations.size()) + " annotation(s) to the graph " + prefix + "\n" + StringUtils.join(explanations, "\n"); } else if(lang == "de") { - result = "Die Komponente " + componentURI + "hat " + String.valueOf(explanations.size()) + " annotationen zum Triplestore hinzugefügt " + result = "Die Komponente " + componentURI + " hat " + String.valueOf(explanations.size()) + " Annotation(en) zum Graph hinzugefügt " + prefix + "\n" + StringUtils.join(explanations, "\n"); } return result; diff --git a/src/main/resources/explanations/annotation_of_instance/de_list_item b/src/main/resources/explanations/annotation_of_instance/de_list_item new file mode 100644 index 0000000..5ccb703 --- /dev/null +++ b/src/main/resources/explanations/annotation_of_instance/de_list_item @@ -0,0 +1 @@ +am $createdAt mit einer Konfidenz von $score die Resource $body \ No newline at end of file diff --git a/src/main/resources/explanations/annotation_of_instance/de_prefix b/src/main/resources/explanations/annotation_of_instance/de_prefix new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/explanations/annotation_of_instance/en_list_item b/src/main/resources/explanations/annotation_of_instance/en_list_item new file mode 100644 index 0000000..57c039f --- /dev/null +++ b/src/main/resources/explanations/annotation_of_instance/en_list_item @@ -0,0 +1 @@ +on $createdAt with a confidence of $score and the resource $body \ No newline at end of file diff --git a/src/main/resources/explanations/annotation_of_instance/en_prefix b/src/main/resources/explanations/annotation_of_instance/en_prefix new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/queries/general_explanation.rq b/src/main/resources/queries/general_explanation.rq index 1954ac7..41838e0 100644 --- a/src/main/resources/queries/general_explanation.rq +++ b/src/main/resources/queries/general_explanation.rq @@ -1,9 +1,9 @@ PREFIX rdf: PREFIX oa: PREFIX qa: + SELECT * FROM ?graphURI - WHERE { ?annotationId oa:annotatedBy ?componentURI . ?annotationId oa:hasBody ?body . diff --git a/src/main/resources/queries/queries_for_annotation_types/annotations_of_instance_query.rq b/src/main/resources/queries/queries_for_annotation_types/annotations_of_instance_query.rq new file mode 100644 index 0000000..5736187 --- /dev/null +++ b/src/main/resources/queries/queries_for_annotation_types/annotations_of_instance_query.rq @@ -0,0 +1,13 @@ +PREFIX rdf: +PREFIX oa: +PREFIX qa: + +SELECT * +FROM ?graphURI +WHERE { + ?annotationId rdf:type qa:AnnotationOfInstance. + ?annotationId oa:annotatedBy ?componentURI . + ?annotationId oa:hasBody ?body . + ?annotationId oa:annotatedAt ?createdAt . + ?annotationId qa:score ?score . +} \ No newline at end of file From b4f351f350c26b6cf1c7c062d74b41b47a009591 Mon Sep 17 00:00:00 2001 From: Dennis Schiese Date: Tue, 12 Sep 2023 15:07:18 +0200 Subject: [PATCH 06/15] Values getting mapped to the templates --- .../services/ExplanationService.java | 74 ++++++++++++------- .../annotation_of_instance/de_list_item | 2 +- .../annotation_of_instance/en_list_item | 2 +- .../annotation_of_spot_instance/de_list_item | 2 +- .../annotation_of_spot_instance/de_prefix | 2 +- .../annotation_of_spot_instance/en_list_item | 2 +- .../annotation_of_spot_instance/en_prefix | 2 +- .../annotations_of_instance_query.rq | 2 +- 8 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java index e67d4f4..7bfa0a4 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java +++ b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java @@ -4,15 +4,19 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.google.protobuf.MapEntry; import com.wse.qanaryexplanationservice.pojos.ComponentPojo; import com.wse.qanaryexplanationservice.pojos.ExplanationObject; import com.wse.qanaryexplanationservice.repositories.ExplanationSparqlRepository; import eu.wdaqua.qanary.commons.triplestoreconnectors.QanaryTripleStoreConnector; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringSubstitutor; import org.apache.jena.query.QuerySolution; import org.apache.jena.query.QuerySolutionMap; import org.apache.jena.query.ResultSet; import org.apache.jena.rdf.model.*; +import org.apache.jena.rdf.model.impl.Util; +import org.apache.jena.shared.PrefixMapping; import org.apache.jena.vocabulary.RDF; import org.apache.jena.vocabulary.RDFS; import org.slf4j.Logger; @@ -28,6 +32,7 @@ import java.text.DecimalFormat; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; @Service public class ExplanationService { @@ -41,20 +46,28 @@ public class ExplanationService { put("application/ld+json", "JSONLD"); put("text/turtle", "TURTLE"); }}; + // Holds request query for declared annotations types private static final Map annotationsTypeAndQuery = new HashMap<>() {{ // AnnotationOfInstance put("annotationofspotinstance", "/queries/queries_for_annotation_types/annotations_of_spot_intance_query.rq"); put("annotationofinstance", "/queries/queries_for_annotation_types/annotations_of_instance_query.rq"); }}; - // Holds the annotationtype with path + // Holds explanation templates for the declared annotation types private static final Map annotationTypeExplanationTemplate = new HashMap<>() {{ put("annotationofspotinstance", "/explanations/annotation_of_spot_instance/"); put("annotationofinstance", "/explanations/annotation_of_instance/"); }}; + final String EXPLANATION_NAMESPACE = "urn:qanary:explanations#"; private final ObjectMapper objectMapper; Logger logger = LoggerFactory.getLogger(ExplanationService.class); + + /* + TODO: Is there a smart approach to avoid global changing variable? + Key = annotation-type, Value = ResultSet + Should avoid multiple execution of same sparql query + */ private Map stringResultSetMap = new HashMap<>(); @Autowired private ExplanationSparqlRepository explanationSparqlRepository; @@ -82,10 +95,10 @@ public String explainSpecificComponent(String graphUri, String componentUri, Str // Returns a model for a specific component public Model createModel(String graphUri, String componentUri) throws Exception { - String contentde = createTextualExplanation(graphUri, componentUri, "de"); - String contenten = createTextualExplanation(graphUri, componentUri, "en"); + String contentDE = createTextualExplanation(graphUri, componentUri, "de"); + String contentEN = createTextualExplanation(graphUri, componentUri, "en"); - return createModelForSpecificComponent(contentde, contenten, componentUri); + return createModelForSpecificComponent(contentDE, contentEN, componentUri); } // Creating the query, executing it and transform the response to an array of ExplanationObject objects @@ -432,30 +445,37 @@ public List addingExplanations(String type, String lang, ResultSet resul explanationsForCurrentType.add(langExplanationPrefix); String template = getStringFromFile(annotationTypeExplanationTemplate.get(type) + lang + "_list_item"); - if (Objects.equals(type, "annotationofspotinstance")) { - while (results.hasNext()) { - String filledTemplate = template; - QuerySolution currentObject = results.next(); - filledTemplate = filledTemplate.replace("$createdAt", currentObject.get("createdAt").asLiteral().getString()); - filledTemplate = filledTemplate.replace("$start", String.valueOf(currentObject.get("start").asLiteral().getInt())); - filledTemplate = filledTemplate.replace("$end", String.valueOf(currentObject.get("end").asLiteral().getInt())); - explanationsForCurrentType.add(filledTemplate); - } - } - else if(Objects.equals(type, "annotationofinstance")) { - while(results.hasNext()) { - String filledTemplate = template; - QuerySolution currentObject = results.next(); - filledTemplate = filledTemplate.replace("$createdAt", currentObject.get("createdAt").asLiteral().getString()); - filledTemplate = filledTemplate.replace("$score", String.valueOf(currentObject.get("score").asLiteral().getDouble())); - filledTemplate = filledTemplate.replace("$body", currentObject.get("body").asResource().toString()); - explanationsForCurrentType.add(filledTemplate); - } + while(results.hasNext()) { + explanationsForCurrentType.add(replaceProperties(results.next(), template)); } return explanationsForCurrentType; } + public String replaceProperties(QuerySolution querySolution, String template) { + QuerySolutionMap querySolutionMap = new QuerySolutionMap(); + querySolutionMap.addAll(querySolution); + Map querySolutionMapAsMap = querySolutionMap.asMap(); + Map convertedMap = convertRdfNodeToStringValue(querySolutionMapAsMap); + + // Replace all placeholders with values from map + template = StringSubstitutor.replace(template, convertedMap, "${", "}"); + logger.info("Template with inserted params: {}", template); + return template; + } + + public Map convertRdfNodeToStringValue(Map map) { + return map.entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + entry -> { + if(entry.getValue().isResource()) + return entry.getValue().toString(); + else + return entry.getValue().asNode().getLiteralValue().toString(); + } + )); + } + public String getStringFromFile(String path) throws IOException { File file = new ClassPathResource(path).getFile(); return new String(Files.readAllBytes(file.toPath())); @@ -483,12 +503,12 @@ public String createTextualExplanation(String graphURI, String componentURI, Str private static String getResult(String componentURI, String lang, List explanations, String prefix) { String result = null; if(lang == "en") { - result = "The component " + componentURI + " has added " + String.valueOf(explanations.size()) + " annotation(s) to the graph " - + prefix + "\n" + StringUtils.join(explanations, "\n"); + result = "The component " + componentURI + " has added " + String.valueOf(explanations.size()) + " annotation(s) to the graph" + + prefix + ":\n" + StringUtils.join(explanations, "\n"); } else if(lang == "de") { - result = "Die Komponente " + componentURI + " hat " + String.valueOf(explanations.size()) + " Annotation(en) zum Graph hinzugefügt " - + prefix + "\n" + StringUtils.join(explanations, "\n"); + result = "Die Komponente " + componentURI + " hat " + String.valueOf(explanations.size()) + " Annotation(en) zum Graph hinzugefügt" + + prefix + ":\n" + StringUtils.join(explanations, "\n"); } return result; } diff --git a/src/main/resources/explanations/annotation_of_instance/de_list_item b/src/main/resources/explanations/annotation_of_instance/de_list_item index 5ccb703..145f9b6 100644 --- a/src/main/resources/explanations/annotation_of_instance/de_list_item +++ b/src/main/resources/explanations/annotation_of_instance/de_list_item @@ -1 +1 @@ -am $createdAt mit einer Konfidenz von $score die Resource $body \ No newline at end of file +am ${createdAt} mit einer Konfidenz von ${score} die Resource ${body}. \ No newline at end of file diff --git a/src/main/resources/explanations/annotation_of_instance/en_list_item b/src/main/resources/explanations/annotation_of_instance/en_list_item index 57c039f..8c46ab7 100644 --- a/src/main/resources/explanations/annotation_of_instance/en_list_item +++ b/src/main/resources/explanations/annotation_of_instance/en_list_item @@ -1 +1 @@ -on $createdAt with a confidence of $score and the resource $body \ No newline at end of file +on ${createdAt} with a confidence of ${score} and the resource ${body} \ No newline at end of file diff --git a/src/main/resources/explanations/annotation_of_spot_instance/de_list_item b/src/main/resources/explanations/annotation_of_spot_instance/de_list_item index b6ba62f..fdbd42e 100644 --- a/src/main/resources/explanations/annotation_of_spot_instance/de_list_item +++ b/src/main/resources/explanations/annotation_of_spot_instance/de_list_item @@ -1 +1 @@ -am $createdAt beginnend bei $start und endend an Position $end \ No newline at end of file +am ${createdAt} beginnend bei ${start} und endend an Position ${end} \ No newline at end of file diff --git a/src/main/resources/explanations/annotation_of_spot_instance/de_prefix b/src/main/resources/explanations/annotation_of_spot_instance/de_prefix index 5820b53..8087832 100644 --- a/src/main/resources/explanations/annotation_of_spot_instance/de_prefix +++ b/src/main/resources/explanations/annotation_of_spot_instance/de_prefix @@ -1 +1 @@ -und dabei wurden folgende Entitäten aus der Ursprungsfrage gefunden: \ No newline at end of file + und dabei wurden folgende Entitäten aus der Ursprungsfrage gefunden \ No newline at end of file diff --git a/src/main/resources/explanations/annotation_of_spot_instance/en_list_item b/src/main/resources/explanations/annotation_of_spot_instance/en_list_item index 65c664a..7466501 100644 --- a/src/main/resources/explanations/annotation_of_spot_instance/en_list_item +++ b/src/main/resources/explanations/annotation_of_spot_instance/en_list_item @@ -1 +1 @@ -at $createdAt starting from position $start and ending at position $end \ No newline at end of file +at ${createdAt} starting from position ${start} and ending at position ${end} \ No newline at end of file diff --git a/src/main/resources/explanations/annotation_of_spot_instance/en_prefix b/src/main/resources/explanations/annotation_of_spot_instance/en_prefix index af99380..9e31867 100644 --- a/src/main/resources/explanations/annotation_of_spot_instance/en_prefix +++ b/src/main/resources/explanations/annotation_of_spot_instance/en_prefix @@ -1 +1 @@ -and each annotation from type AnnotationOfSpotInstance found the following entities from the origin question: \ No newline at end of file + and each annotation from type AnnotationOfSpotInstance found the following entities from the origin question \ No newline at end of file diff --git a/src/main/resources/queries/queries_for_annotation_types/annotations_of_instance_query.rq b/src/main/resources/queries/queries_for_annotation_types/annotations_of_instance_query.rq index 5736187..865c98a 100644 --- a/src/main/resources/queries/queries_for_annotation_types/annotations_of_instance_query.rq +++ b/src/main/resources/queries/queries_for_annotation_types/annotations_of_instance_query.rq @@ -5,7 +5,7 @@ PREFIX qa: SELECT * FROM ?graphURI WHERE { - ?annotationId rdf:type qa:AnnotationOfInstance. + ?annotationId rdf:type qa:AnnotationOfInstance . ?annotationId oa:annotatedBy ?componentURI . ?annotationId oa:hasBody ?body . ?annotationId oa:annotatedAt ?createdAt . From 4b15d8217952ff5f0dc177e675865ed1b5b6b99d Mon Sep 17 00:00:00 2001 From: Dennis Schiese Date: Tue, 12 Sep 2023 15:49:19 +0200 Subject: [PATCH 07/15] Added comments and shifted some variables --- .../services/ExplanationService.java | 85 ++++++++++++++----- 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java index 7bfa0a4..339121e 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java +++ b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.google.protobuf.MapEntry; import com.wse.qanaryexplanationservice.pojos.ComponentPojo; import com.wse.qanaryexplanationservice.pojos.ExplanationObject; import com.wse.qanaryexplanationservice.repositories.ExplanationSparqlRepository; @@ -15,8 +14,6 @@ import org.apache.jena.query.QuerySolutionMap; import org.apache.jena.query.ResultSet; import org.apache.jena.rdf.model.*; -import org.apache.jena.rdf.model.impl.Util; -import org.apache.jena.shared.PrefixMapping; import org.apache.jena.vocabulary.RDF; import org.apache.jena.vocabulary.RDFS; import org.slf4j.Logger; @@ -40,6 +37,8 @@ public class ExplanationService { // Query files private static final String QUESTION_QUERY = "/queries/question_query.rq"; private static final String ANNOTATIONS_QUERY = "/queries/queries_for_annotation_types/fetch_all_annotation_types.rq"; + private static final String TEMPLATE_PLACEHOLDER_PREFIX = "${"; + private static final String TEMPLATE_PLACEHOLDER_SUFFIX = "}"; // Mappings private static final Map headerFormatMap = new HashMap<>() {{ put("application/rdf+xml", "RDFXML"); @@ -95,8 +94,12 @@ public String explainSpecificComponent(String graphUri, String componentUri, Str // Returns a model for a specific component public Model createModel(String graphUri, String componentUri) throws Exception { - String contentDE = createTextualExplanation(graphUri, componentUri, "de"); - String contentEN = createTextualExplanation(graphUri, componentUri, "en"); + List types = new ArrayList<>(); + if (stringResultSetMap.isEmpty()) + types = fetchAllAnnotations(graphUri, componentUri); + + String contentDE = createTextualExplanation(graphUri, componentUri, "de", types); + String contentEN = createTextualExplanation(graphUri, componentUri, "en", types); return createModelForSpecificComponent(contentDE, contentEN, componentUri); } @@ -381,26 +384,32 @@ public Model createSystemModel(Map models, ComponentPojo[] compon return systemExplanationModel; } - public List createComponentExplanation(String graphURI, String componentURI, String lang) throws IOException { - - List types = new ArrayList<>(); - if (stringResultSetMap.isEmpty()) - types = fetchAllAnnotation(graphURI, componentURI); - + /** + * Creates a comprehensive explanation in a given language + * @param types Inherits all annotation-types + * @param lang Given language + * @return List with explanations for the given language. At the end it includes all explanations for every annotation type + */ + public List createComponentExplanation(String graphURI, String componentURI, String lang, ArrayList types) throws IOException { return createSpecificExplanations( types.toArray(String[]::new), graphURI, lang ); - } - public List fetchAllAnnotation(String graphURI, String componentURI) throws IOException { + /** + * Fetching all annotations a component has created. + * @return A list with all different annotation-types + * @throws IOException + */ + public List fetchAllAnnotations(String graphURI, String componentURI) throws IOException { String query = buildSparqlQuery(graphURI, componentURI, ANNOTATIONS_QUERY); ArrayList types = new ArrayList<>(); ResultSet resultSet = this.explanationSparqlRepository.executeSparqlQueryWithResultSet(query); + // Iterate through the QuerySolutions and gather all annotation-types while (resultSet.hasNext()) { QuerySolution result = resultSet.next(); RDFNode type = result.get("annotationType"); @@ -410,34 +419,51 @@ public List fetchAllAnnotation(String graphURI, String componentURI) thr } return types; - } - // Create a specific explanation for every annotation + /** + * Collects the explanation for every annotation type and concat the received lists to its own list + * @param usedTypes Includes all annotation types the given componentURI has created + * @param lang The language for the explanation + * @return List with explanations. At the end it includes all explanations for every annotation type + */ public List createSpecificExplanations(String[] usedTypes, String graphURI, String lang) throws IOException { List explanations = new ArrayList<>(); - for (String type : usedTypes ) { explanations.addAll(createSpecificExplanation(type, graphURI, lang)); } - return explanations; } + /** + * Creates an explanation for the passed type and language + * @param type The annotation type + * @param lang The language for the explanation + * @return A list of explanation containing a prefix explanation and one entry for every annotation of the givent type + */ public List createSpecificExplanation(String type, String graphURI, String lang) throws IOException { String query = buildSparqlQuery(graphURI, null, annotationsTypeAndQuery.get(type)); - ResultSet results = null; + + // For the first language that will be executed, for each annotation-type a component created if (!stringResultSetMap.containsKey(type)) - results = this.explanationSparqlRepository.executeSparqlQueryWithResultSet(query); + stringResultSetMap.put(type, this.explanationSparqlRepository.executeSparqlQueryWithResultSet(query)); - List explanationsForCurrentType = addingExplanations(type, lang, results); + List explanationsForCurrentType = addingExplanations(type, lang, stringResultSetMap.get(type)); logger.info("Created explanations: {}", explanationsForCurrentType); return explanationsForCurrentType; } + /** + * + * @param type The actual type for which the explanation is being generated, relevant for selection of correct template + * @param lang The actual language the explanation is created for + * @param results The ResultSet for the actual type + * @return + * @throws IOException + */ public List addingExplanations(String type, String lang, ResultSet results) throws IOException { List explanationsForCurrentType = new ArrayList<>(); @@ -452,6 +478,12 @@ public List addingExplanations(String type, String lang, ResultSet resul return explanationsForCurrentType; } + /** + * Replaces all placeholders in the template with attributes from the passed QuerySolution + * @param querySolution Contains variables and corresponding RDFNodes + * @param template Template including the defined pre- and suffixes + * @return Template with replaced placeholders + */ public String replaceProperties(QuerySolution querySolution, String template) { QuerySolutionMap querySolutionMap = new QuerySolutionMap(); querySolutionMap.addAll(querySolution); @@ -459,11 +491,16 @@ public String replaceProperties(QuerySolution querySolution, String template) { Map convertedMap = convertRdfNodeToStringValue(querySolutionMapAsMap); // Replace all placeholders with values from map - template = StringSubstitutor.replace(template, convertedMap, "${", "}"); + template = StringSubstitutor.replace(template, convertedMap, TEMPLATE_PLACEHOLDER_PREFIX, TEMPLATE_PLACEHOLDER_SUFFIX); logger.info("Template with inserted params: {}", template); return template; } + /** + * Converts RDFNodes to Strings without the XML datatype declaration and leaves resources as they are. + * @param map Key = variable from sparql-query, Value = its corresponding RDFNode + * @return Map with value::String instead of value::RDFNode + */ public Map convertRdfNodeToStringValue(Map map) { return map.entrySet().stream().collect(Collectors.toMap( Map.Entry::getKey, @@ -476,6 +513,11 @@ public Map convertRdfNodeToStringValue(Map map) { )); } + /** + * Reads a file and parses the content to a string + * @param path Given path + * @return String with the file's content + */ public String getStringFromFile(String path) throws IOException { File file = new ClassPathResource(path).getFile(); return new String(Files.readAllBytes(file.toPath())); @@ -493,7 +535,6 @@ public String createTextualExplanation(String graphURI, String componentURI, Str List createdExplanations = createComponentExplanation(graphURI, componentURI, lang); AtomicInteger i = new AtomicInteger(); - // skips the first prefix // TODO: needs to be done better since there could me more than one prefix (if several annot. type are provided) List explanations = createdExplanations.stream().skip(1).map((explanation) -> String.valueOf(i.incrementAndGet()) + ". " + explanation).toList(); String result = getResult(componentURI, lang, explanations, createdExplanations.get(0)); stringResultSetMap.clear(); From 6f0f09314218ae34db37bda09d144948361deadb Mon Sep 17 00:00:00 2001 From: Dennis Schiese Date: Tue, 12 Sep 2023 17:28:08 +0200 Subject: [PATCH 08/15] Clean Up code --- pom.xml | 7 +- .../controller/ExplanationController.java | 20 +++--- .../services/ExplanationService.java | 68 +++++++++---------- ...ation_for_dbpediaSpotlight_sparql_query.rq | 23 ------- .../queries/explanation_for_query_builder.rq | 13 ---- .../resources/queries/general_explanation.rq | 13 ---- .../services/ExplanationServiceTest.java | 2 - 7 files changed, 48 insertions(+), 98 deletions(-) delete mode 100644 src/main/resources/queries/explanation_for_dbpediaSpotlight_sparql_query.rq delete mode 100644 src/main/resources/queries/explanation_for_query_builder.rq delete mode 100644 src/main/resources/queries/general_explanation.rq diff --git a/pom.xml b/pom.xml index 722b256..fda5cbd 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ eu.wdaqua.qanary qa.commons - 3.5.4 + (3.7.0,4.0.0] org.springframework.boot @@ -63,6 +63,11 @@ 2.2 test + + org.apache.commons + commons-lang3 + 3.12.0 + org.apache.jena apache-jena-libs diff --git a/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java b/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java index 4a5b1d8..a422fb4 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java +++ b/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java @@ -7,17 +7,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; -import java.io.IOException; - @RestController @ControllerAdvice public class ExplanationController { - private static final String DBpediaSpotlight_SPARQL_QUERY = "/queries/explanation_for_dbpediaSpotlight_sparql_query.rq"; - private static final String GENERAL_EXPLANATION_SPARQL_QUERY = "/queries/general_explanation.rq"; private final Logger logger = LoggerFactory.getLogger(ExplanationController.class); @Autowired @@ -33,14 +28,15 @@ public class ExplanationController { @Operation( summary = "Get either the explanation for a the whole QA-system on a graphURI" + "or the explanation for a specific component by attaching the componentURI", - description = "This endpoint currently offers two sort of requests: " - + "\n 1. Explanation for a QA-system by providing a graphURI and " - + "\n 2. Explanation for a component within a QA-process by providing the graphURI" - + "\n as well as the URI for the component" - + "\n Note: You must at least provide a graphURI to use this endpoint" + description = """ + This endpoint currently offers two sort of requests:\s + 1. Explanation for a QA-system by providing a graphURI and\s + 2. Explanation for a component within a QA-process by providing the graphURI + as well as the URI for the component + Note: You must at least provide a graphURI to use this endpoint""" ) public ResponseEntity getExplanations( - @PathVariable(required = true) String graphURI, + @PathVariable String graphURI, @PathVariable(required = false) String componentURI, @RequestHeader(value = "accept", required = false) String acceptHeader) throws Exception { if (componentURI == null) { @@ -54,7 +50,7 @@ public ResponseEntity getExplanations( if (result != null) return new ResponseEntity<>(result, HttpStatus.OK); else - return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST); } } diff --git a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java index 339121e..bd034dd 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java +++ b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java @@ -77,6 +77,18 @@ public ExplanationService() { objectMapper = new ObjectMapper(); } + private static String getResult(String componentURI, String lang, List explanations, String prefix) { + String result = null; + if (Objects.equals(lang, "en")) { + result = "The component " + componentURI + " has added " + explanations.size() + " annotation(s) to the graph" + + prefix + ":\n" + StringUtils.join(explanations, "\n"); + } else if (Objects.equals(lang, "de")) { + result = "Die Komponente " + componentURI + " hat " + explanations.size() + " Annotation(en) zum Graph hinzugefügt" + + prefix + ":\n" + StringUtils.join(explanations, "\n"); + } + return result; + } + /** * Computes a textual explanation for a specific component on a specific graphURI * @@ -111,7 +123,6 @@ public ExplanationObject[] computeExplanationObjects(String graphUri, String com return convertToExplanationObjects(explanationObjectsJsonNode); } - /** * Creating the specific query, execute and transform response to array of ExplanationObject objects * @@ -180,7 +191,6 @@ public String convertToDesiredFormat(String header, Model model) { return writer.toString(); } - // INFO: May be removed since there's a different approach for something like this // e.g. pass the restriction as a parameter to the query public String explainQueryBuilder(String graphURI, String rawQuery) throws IOException { @@ -299,8 +309,8 @@ public String explainQaSystem(String graphURI, String header) throws Exception { Map models = new HashMap<>(); for (ComponentPojo component : components ) { - models.put(component.getComponent().getValue(), - createModel(graphURI, component.getComponent().getValue())); + models.put(component.getComponent().getValue(), // === componentURI + createModel(graphURI, component.getComponent().getValue())); // create a model for that componentURI } String questionURI = fetchQuestionUri(graphURI); @@ -386,11 +396,12 @@ public Model createSystemModel(Map models, ComponentPojo[] compon /** * Creates a comprehensive explanation in a given language + * * @param types Inherits all annotation-types - * @param lang Given language + * @param lang Given language * @return List with explanations for the given language. At the end it includes all explanations for every annotation type */ - public List createComponentExplanation(String graphURI, String componentURI, String lang, ArrayList types) throws IOException { + public List createComponentExplanation(String graphURI, String lang, List types) throws IOException { return createSpecificExplanations( types.toArray(String[]::new), graphURI, @@ -400,8 +411,8 @@ public List createComponentExplanation(String graphURI, String component /** * Fetching all annotations a component has created. + * * @return A list with all different annotation-types - * @throws IOException */ public List fetchAllAnnotations(String graphURI, String componentURI) throws IOException { String query = buildSparqlQuery(graphURI, componentURI, ANNOTATIONS_QUERY); @@ -423,8 +434,9 @@ public List fetchAllAnnotations(String graphURI, String componentURI) th /** * Collects the explanation for every annotation type and concat the received lists to its own list + * * @param usedTypes Includes all annotation types the given componentURI has created - * @param lang The language for the explanation + * @param lang The language for the explanation * @return List with explanations. At the end it includes all explanations for every annotation type */ public List createSpecificExplanations(String[] usedTypes, String graphURI, String lang) throws IOException { @@ -439,6 +451,7 @@ public List createSpecificExplanations(String[] usedTypes, String graphU /** * Creates an explanation for the passed type and language + * * @param type The annotation type * @param lang The language for the explanation * @return A list of explanation containing a prefix explanation and one entry for every annotation of the givent type @@ -457,12 +470,10 @@ public List createSpecificExplanation(String type, String graphURI, Stri } /** - * - * @param type The actual type for which the explanation is being generated, relevant for selection of correct template - * @param lang The actual language the explanation is created for + * @param type The actual type for which the explanation is being generated, relevant for selection of correct template + * @param lang The actual language the explanation is created for * @param results The ResultSet for the actual type - * @return - * @throws IOException + * @return A list of explanations for the given type in the given language */ public List addingExplanations(String type, String lang, ResultSet results) throws IOException { @@ -471,7 +482,7 @@ public List addingExplanations(String type, String lang, ResultSet resul explanationsForCurrentType.add(langExplanationPrefix); String template = getStringFromFile(annotationTypeExplanationTemplate.get(type) + lang + "_list_item"); - while(results.hasNext()) { + while (results.hasNext()) { explanationsForCurrentType.add(replaceProperties(results.next(), template)); } @@ -480,8 +491,9 @@ public List addingExplanations(String type, String lang, ResultSet resul /** * Replaces all placeholders in the template with attributes from the passed QuerySolution + * * @param querySolution Contains variables and corresponding RDFNodes - * @param template Template including the defined pre- and suffixes + * @param template Template including the defined pre- and suffixes * @return Template with replaced placeholders */ public String replaceProperties(QuerySolution querySolution, String template) { @@ -498,14 +510,15 @@ public String replaceProperties(QuerySolution querySolution, String template) { /** * Converts RDFNodes to Strings without the XML datatype declaration and leaves resources as they are. + * * @param map Key = variable from sparql-query, Value = its corresponding RDFNode * @return Map with value::String instead of value::RDFNode */ - public Map convertRdfNodeToStringValue(Map map) { + public Map convertRdfNodeToStringValue(Map map) { return map.entrySet().stream().collect(Collectors.toMap( Map.Entry::getKey, entry -> { - if(entry.getValue().isResource()) + if (entry.getValue().isResource()) return entry.getValue().toString(); else return entry.getValue().asNode().getLiteralValue().toString(); @@ -515,6 +528,7 @@ public Map convertRdfNodeToStringValue(Map map) { /** * Reads a file and parses the content to a string + * * @param path Given path * @return String with the file's content */ @@ -523,35 +537,21 @@ public String getStringFromFile(String path) throws IOException { return new String(Files.readAllBytes(file.toPath())); } - /** * Creates a textual explanation for all annotations made by the componentURI for a language lang. The explanation for the annotations are formatted as a list * * @param lang Currently supported en_list_item and de_list_item * @return Complete explanation for the componentURI including all information to each annotation */ - public String createTextualExplanation(String graphURI, String componentURI, String lang) throws IOException { + public String createTextualExplanation(String graphURI, String componentURI, String lang, List types) throws IOException { - List createdExplanations = createComponentExplanation(graphURI, componentURI, lang); + List createdExplanations = createComponentExplanation(graphURI, lang, types); AtomicInteger i = new AtomicInteger(); - List explanations = createdExplanations.stream().skip(1).map((explanation) -> String.valueOf(i.incrementAndGet()) + ". " + explanation).toList(); + List explanations = createdExplanations.stream().skip(1).map((explanation) -> i.incrementAndGet() + ". " + explanation).toList(); String result = getResult(componentURI, lang, explanations, createdExplanations.get(0)); stringResultSetMap.clear(); return result; } - private static String getResult(String componentURI, String lang, List explanations, String prefix) { - String result = null; - if(lang == "en") { - result = "The component " + componentURI + " has added " + String.valueOf(explanations.size()) + " annotation(s) to the graph" - + prefix + ":\n" + StringUtils.join(explanations, "\n"); - } - else if(lang == "de") { - result = "Die Komponente " + componentURI + " hat " + String.valueOf(explanations.size()) + " Annotation(en) zum Graph hinzugefügt" - + prefix + ":\n" + StringUtils.join(explanations, "\n"); - } - return result; - } - } diff --git a/src/main/resources/queries/explanation_for_dbpediaSpotlight_sparql_query.rq b/src/main/resources/queries/explanation_for_dbpediaSpotlight_sparql_query.rq deleted file mode 100644 index c4aff64..0000000 --- a/src/main/resources/queries/explanation_for_dbpediaSpotlight_sparql_query.rq +++ /dev/null @@ -1,23 +0,0 @@ -PREFIX rdf: -PREFIX oa: -PREFIX qa: -SELECT * -FROM ?graphURI - -WHERE { - ?annotationId a qa:AnnotationOfInstance. - ?annotationId rdf:type ?type. - ?annotationId oa:hasBody ?body. - ?annotationId oa:hasTarget [ - a oa:SpecificResource; - oa:hasSource ?source; - oa:hasSelector [ - a oa:TextPositionSelector; - oa:start ?start; - oa:end ?end; - ] -]. - ?annotationId oa:annotatedBy $createdBy . - ?annotationId oa:annotatedAt $createdAt . - ?annotationId qa:score ?score . -} \ No newline at end of file diff --git a/src/main/resources/queries/explanation_for_query_builder.rq b/src/main/resources/queries/explanation_for_query_builder.rq deleted file mode 100644 index 13c73e1..0000000 --- a/src/main/resources/queries/explanation_for_query_builder.rq +++ /dev/null @@ -1,13 +0,0 @@ -PREFIX rdf: -PREFIX oa: -PREFIX qa: -SELECT * -FROM ?graphURI - -WHERE { - ?annotationId a qa:AnnotationOfAnswerSPARQL . - ?annotationId oa:hasBody ?body . - ?annotationId oa:annotatedBy $createdBy . - ?annotationId oa:annotatedAt $createdAt . - ?annotationId qa:score ?score . -} \ No newline at end of file diff --git a/src/main/resources/queries/general_explanation.rq b/src/main/resources/queries/general_explanation.rq deleted file mode 100644 index 41838e0..0000000 --- a/src/main/resources/queries/general_explanation.rq +++ /dev/null @@ -1,13 +0,0 @@ -PREFIX rdf: -PREFIX oa: -PREFIX qa: - -SELECT * -FROM ?graphURI -WHERE { - ?annotationId oa:annotatedBy ?componentURI . - ?annotationId oa:hasBody ?body . - ?annotationId oa:annotatedAt $createdAt . - ?annotationId qa:score ?score . -} - diff --git a/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java b/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java index 5199b82..eac50f9 100644 --- a/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java +++ b/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java @@ -39,8 +39,6 @@ @SpringBootTest public class ExplanationServiceTest { private static final String EXPLANATION_NAMESPACE = "urn:qanary:explanations"; - - private static final String QUERY = "/queries/explanation_for_query_builder.rq"; protected final Logger logger = LoggerFactory.getLogger(ExplanationService.class); @Nested From 6a1d2af8dd6c545fb56ddd74da6527996d5fe94f Mon Sep 17 00:00:00 2001 From: Dennis Schiese Date: Wed, 13 Sep 2023 10:29:41 +0200 Subject: [PATCH 09/15] Refactored methods and setup tests --- .../services/ExplanationService.java | 23 +++-- .../annotation_of_instance/de_list_item | 2 +- .../annotations_of_instance_query.rq | 17 +++- .../services/ExplanationServiceTest.java | 94 +++++++++++++++++++ .../services/ServiceDataForTests.java | 34 +++++++ .../annotationofinstance/de_list_item | 1 + .../annotationofinstance/en_list_item | 1 + .../annotationofspotinstance/de_list_item | 1 + .../annotationofspotinstance/en_list_item | 1 + 9 files changed, 159 insertions(+), 15 deletions(-) create mode 100644 src/test/resources/expected_list_explanations/annotationofinstance/de_list_item create mode 100644 src/test/resources/expected_list_explanations/annotationofinstance/en_list_item create mode 100644 src/test/resources/expected_list_explanations/annotationofspotinstance/de_list_item create mode 100644 src/test/resources/expected_list_explanations/annotationofspotinstance/en_list_item diff --git a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java index bd034dd..abf5837 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java +++ b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java @@ -81,10 +81,10 @@ private static String getResult(String componentURI, String lang, List e String result = null; if (Objects.equals(lang, "en")) { result = "The component " + componentURI + " has added " + explanations.size() + " annotation(s) to the graph" - + prefix + ":\n" + StringUtils.join(explanations, "\n"); + + prefix + ": " + StringUtils.join(explanations, " "); } else if (Objects.equals(lang, "de")) { result = "Die Komponente " + componentURI + " hat " + explanations.size() + " Annotation(en) zum Graph hinzugefügt" - + prefix + ":\n" + StringUtils.join(explanations, "\n"); + + prefix + ": " + StringUtils.join(explanations, " "); } return result; } @@ -483,7 +483,8 @@ public List addingExplanations(String type, String lang, ResultSet resul String template = getStringFromFile(annotationTypeExplanationTemplate.get(type) + lang + "_list_item"); while (results.hasNext()) { - explanationsForCurrentType.add(replaceProperties(results.next(), template)); + QuerySolution querySolution = results.next(); + explanationsForCurrentType.add(replaceProperties(convertQuerySolutionToMap(querySolution), template)); } return explanationsForCurrentType; @@ -492,15 +493,10 @@ public List addingExplanations(String type, String lang, ResultSet resul /** * Replaces all placeholders in the template with attributes from the passed QuerySolution * - * @param querySolution Contains variables and corresponding RDFNodes - * @param template Template including the defined pre- and suffixes + * @param template Template including the defined pre- and suffixes * @return Template with replaced placeholders */ - public String replaceProperties(QuerySolution querySolution, String template) { - QuerySolutionMap querySolutionMap = new QuerySolutionMap(); - querySolutionMap.addAll(querySolution); - Map querySolutionMapAsMap = querySolutionMap.asMap(); - Map convertedMap = convertRdfNodeToStringValue(querySolutionMapAsMap); + public String replaceProperties(Map convertedMap, String template) { // Replace all placeholders with values from map template = StringSubstitutor.replace(template, convertedMap, TEMPLATE_PLACEHOLDER_PREFIX, TEMPLATE_PLACEHOLDER_SUFFIX); @@ -508,6 +504,13 @@ public String replaceProperties(QuerySolution querySolution, String template) { return template; } + public Map convertQuerySolutionToMap(QuerySolution querySolution) { + QuerySolutionMap querySolutionMap = new QuerySolutionMap(); + querySolutionMap.addAll(querySolution); + Map querySolutionMapAsMap = querySolutionMap.asMap(); + return convertRdfNodeToStringValue(querySolutionMapAsMap); + } + /** * Converts RDFNodes to Strings without the XML datatype declaration and leaves resources as they are. * diff --git a/src/main/resources/explanations/annotation_of_instance/de_list_item b/src/main/resources/explanations/annotation_of_instance/de_list_item index 145f9b6..9960a1d 100644 --- a/src/main/resources/explanations/annotation_of_instance/de_list_item +++ b/src/main/resources/explanations/annotation_of_instance/de_list_item @@ -1 +1 @@ -am ${createdAt} mit einer Konfidenz von ${score} die Resource ${body}. \ No newline at end of file +am ${createdAt} mit einer Konfidenz von ${score} die Resource ${body} \ No newline at end of file diff --git a/src/main/resources/queries/queries_for_annotation_types/annotations_of_instance_query.rq b/src/main/resources/queries/queries_for_annotation_types/annotations_of_instance_query.rq index 865c98a..480998e 100644 --- a/src/main/resources/queries/queries_for_annotation_types/annotations_of_instance_query.rq +++ b/src/main/resources/queries/queries_for_annotation_types/annotations_of_instance_query.rq @@ -6,8 +6,17 @@ SELECT * FROM ?graphURI WHERE { ?annotationId rdf:type qa:AnnotationOfInstance . - ?annotationId oa:annotatedBy ?componentURI . - ?annotationId oa:hasBody ?body . - ?annotationId oa:annotatedAt ?createdAt . - ?annotationId qa:score ?score . + ?annotationId oa:hasTarget [ + a oa:SpecificResource; + oa:hasSource ?source; + oa:hasSelector [ + a oa:TextPositionSelector; + oa:start ?start; + oa:end ?end; + ] + ]. + ?annotationId oa:hasBody ?body ; + oa:annotatedBy ?componentURI ; + oa:annotatedAt ?createdAt ; + qa:score ?score . } \ No newline at end of file diff --git a/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java b/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java index eac50f9..60ba194 100644 --- a/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java +++ b/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java @@ -12,10 +12,13 @@ import org.apache.jena.rdf.model.Literal; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.RDFNode; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,9 +27,12 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.junit.jupiter.SpringExtension; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.StringReader; +import java.nio.file.Files; +import java.util.HashMap; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -239,4 +245,92 @@ void createSystemModelTest() throws IOException { } } + @Nested + class ComponentExplanationTests { + + private static final Map annotationTypeExplanationTemplate = new HashMap<>() {{ + put("annotationofspotinstance", "/explanations/annotation_of_spot_instance/"); + put("annotationofinstance", "/explanations/annotation_of_instance/"); + }}; + private ServiceDataForTests serviceDataForTests; + @Autowired + private ExplanationService explanationService; + + @BeforeEach + public void setup() { + serviceDataForTests = new ServiceDataForTests(); + } + + + @Test + public void createTextualRepresentationTest() { + + } + + /* + Converts a given Map to a Map + */ + @Test + public void convertRdfNodeToStringValue() { + Map toBeConvertedMap = serviceDataForTests.getMapWithRdfNodeValues(); + Map comparingMap = serviceDataForTests.getConvertedMapWithStringValues(); + + Map comparedMap = explanationService.convertRdfNodeToStringValue(toBeConvertedMap); + + assertEquals(comparingMap, comparedMap); + } + + /* + Given a set of (key, value) the result should be the template without any more placeholders, + TODO: For further annotation types this test can be easily extended by adding values to the map within the serviceDataForTests as well as + TODO: adding a test-case with the corresponding template + */ + @ParameterizedTest + @ValueSource(strings = {"annotationofinstance", "annotationofspotinstance"}) + public void replacePropertiesTest(String type) { + + Map convertedMap = serviceDataForTests.getConvertedMapWithStringValues(); + ClassLoader classLoader = this.getClass().getClassLoader(); + + assertAll("Testing correct replacement for templates", + () -> { + String computedTemplate = explanationService.replaceProperties(convertedMap, explanationService.getStringFromFile(annotationTypeExplanationTemplate.get(type) + "de" + "_list_item")); + String expectedOutcomeFilePath = "expected_list_explanations/" + type + "/de_list_item"; + File file = new File(classLoader.getResource(expectedOutcomeFilePath).getFile()); + String expectedOutcome = new String(Files.readAllBytes(file.toPath())); + assertEquals(expectedOutcome, computedTemplate); + }, + () -> { + String computedTemplate = explanationService.replaceProperties(convertedMap, explanationService.getStringFromFile(annotationTypeExplanationTemplate.get(type) + "en" + "_list_item")); + String expectedOutcomeFilePath = "expected_list_explanations/" + type + "/en_list_item"; + File file = new File(classLoader.getResource(expectedOutcomeFilePath).getFile()); + String expectedOutcome = new String(Files.readAllBytes(file.toPath())); + assertEquals(expectedOutcome, computedTemplate); + } + ); + } + + @Test + public void addingExplanationsTest() { + + } + + @Test + public void createSpecificExplanationTest() { + + } + + @Test + public void createSpecificExplanationsTest() { + + } + + @Test + public void fetchAllAnnotationsTest() { + + } + + + } + } diff --git a/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java b/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java index 25b82ba..0a6bcb4 100644 --- a/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java +++ b/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java @@ -1,10 +1,44 @@ package com.wse.qanaryexplanationservice.services; +import org.apache.jena.datatypes.xsd.XSDDatatype; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.ResourceFactory; + +import java.util.HashMap; +import java.util.Map; + public class ServiceDataForTests { private final String jsonExplanationObjects = "{\"bindings\":[{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.7885976199321202\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/String_theory\"},\"source\":{\"type\":\"uri\",\"value\":\"http://demos.swe.htwk-leipzig.de:40111/question/stored-question__text_0d5a7c26-ec6a-4702-8d7f-999849938d0e\"},\"start\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"0\"},\"end\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"4\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.615Z\"},\"score\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#decimal\",\"type\":\"typed-literal\",\"value\":\"0.9347568085631697\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.12726840785317994\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Real_number\"},\"source\":{\"type\":\"uri\",\"value\":\"http://demos.swe.htwk-leipzig.de:40111/question/stored-question__text_0d5a7c26-ec6a-4702-8d7f-999849938d0e\"},\"start\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"12\"},\"end\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"16\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.826Z\"},\"score\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#decimal\",\"type\":\"typed-literal\",\"value\":\"0.977747974809564\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.0010094414975868604\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Batman\"},\"source\":{\"type\":\"uri\",\"value\":\"http://demos.swe.htwk-leipzig.de:40111/question/stored-question__text_0d5a7c26-ec6a-4702-8d7f-999849938d0e\"},\"start\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"25\"},\"end\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"31\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:33.034Z\"},\"score\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#decimal\",\"type\":\"typed-literal\",\"value\":\"0.9999536254316278\"}}]}"; private final String jsonResultObjects = "{\"bindings\":[{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.7885976199321202\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/String_theory\"},\"target\":{\"type\":\"bnode\",\"value\":\"b0\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.615Z\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.12726840785317994\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Real_number\"},\"target\":{\"type\":\"bnode\",\"value\":\"b1\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.826Z\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.0010094414975868604\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Batman\"},\"target\":{\"type\":\"bnode\",\"value\":\"b2\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:33.034Z\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.9600168603465082\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfAnswerSPARQL\"},\"body\":{\"type\":\"literal\",\"value\":\"PREFIX rdfs: \\nPREFIX dct: \\nPREFIX dbr: \\nPREFIX rdf: \\nPREFIX foaf: \\n\\nSELECT *\\nWHERE\\n { ?resource foaf:name ?answer ;\\n rdfs:label ?label\\n FILTER ( lang(?label) = \\\"en_list_item\\\" )\\n ?resource dct:subject dbr:Category:Superheroes_with_alter_egos\\n FILTER ( ! strstarts(lcase(?label), lcase(?answer)) )\\n VALUES ?resource { dbr:Batman }\\n }\\nORDER BY ?resource\\n\"},\"target\":{\"type\":\"uri\",\"value\":\"http://demos.swe.htwk-leipzig.de:40111/question/stored-question__text_0d5a7c26-ec6a-4702-8d7f-999849938d0e\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:QB-SimpleRealNameOfSuperHero\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:33.888Z\"}}]}"; + private final Map mapWithRdfNodeValues = new HashMap<>() {{ + put("body", ResourceFactory.createResource("http://dbpedia.org/resource/example")); + put("value2", ResourceFactory.createResource("resource2")); + put("value3", ResourceFactory.createTypedLiteral("3.0", XSDDatatype.XSDdouble)); + put("createdAt", ResourceFactory.createTypedLiteral("2023-09-13T06:38:13.114944Z", XSDDatatype.XSDdateTime)); + put("score", ResourceFactory.createTypedLiteral("0.33", XSDDatatype.XSDfloat)); + put("start", ResourceFactory.createTypedLiteral("1", XSDDatatype.XSDinteger)); + put("end", ResourceFactory.createTypedLiteral("3", XSDDatatype.XSDinteger)); + }}; + private final Map convertedMapWithStringValues = new HashMap<>() {{ + put("body", "http://dbpedia.org/resource/example"); + put("value2", "resource2"); + put("value3", "3.0"); + put("createdAt", "2023-09-13T06:38:13.114944Z"); + put("score", "0.33"); + put("start", "1"); + put("end", "3"); + }}; + + + public Map getMapWithRdfNodeValues() { + return mapWithRdfNodeValues; + } + + public Map getConvertedMapWithStringValues() { + return convertedMapWithStringValues; + } public String getJsonForExplanationObjects() { return jsonExplanationObjects; diff --git a/src/test/resources/expected_list_explanations/annotationofinstance/de_list_item b/src/test/resources/expected_list_explanations/annotationofinstance/de_list_item new file mode 100644 index 0000000..7cc75f8 --- /dev/null +++ b/src/test/resources/expected_list_explanations/annotationofinstance/de_list_item @@ -0,0 +1 @@ +am 2023-09-13T06:38:13.114944Z mit einer Konfidenz von 0.33 die Resource http://dbpedia.org/resource/example \ No newline at end of file diff --git a/src/test/resources/expected_list_explanations/annotationofinstance/en_list_item b/src/test/resources/expected_list_explanations/annotationofinstance/en_list_item new file mode 100644 index 0000000..a5bc46c --- /dev/null +++ b/src/test/resources/expected_list_explanations/annotationofinstance/en_list_item @@ -0,0 +1 @@ +on 2023-09-13T06:38:13.114944Z with a confidence of 0.33 and the resource http://dbpedia.org/resource/example \ No newline at end of file diff --git a/src/test/resources/expected_list_explanations/annotationofspotinstance/de_list_item b/src/test/resources/expected_list_explanations/annotationofspotinstance/de_list_item new file mode 100644 index 0000000..54a374a --- /dev/null +++ b/src/test/resources/expected_list_explanations/annotationofspotinstance/de_list_item @@ -0,0 +1 @@ +am 2023-09-13T06:38:13.114944Z beginnend bei 1 und endend an Position 3 \ No newline at end of file diff --git a/src/test/resources/expected_list_explanations/annotationofspotinstance/en_list_item b/src/test/resources/expected_list_explanations/annotationofspotinstance/en_list_item new file mode 100644 index 0000000..02239e5 --- /dev/null +++ b/src/test/resources/expected_list_explanations/annotationofspotinstance/en_list_item @@ -0,0 +1 @@ +at 2023-09-13T06:38:13.114944Z starting from position 1 and ending at position 3 \ No newline at end of file From fda436809e75dc928d9121c71f9a15c4697da648 Mon Sep 17 00:00:00 2001 From: Dennis Schiese Date: Wed, 13 Sep 2023 11:29:09 +0200 Subject: [PATCH 10/15] Added tests --- .../services/ExplanationServiceTest.java | 31 ++++++-- .../services/ServiceDataForTests.java | 74 ++++++++++++++++++- 2 files changed, 97 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java b/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java index 60ba194..ead43cb 100644 --- a/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java +++ b/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java @@ -33,6 +33,7 @@ import java.io.StringReader; import java.nio.file.Files; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -46,6 +47,8 @@ public class ExplanationServiceTest { private static final String EXPLANATION_NAMESPACE = "urn:qanary:explanations"; protected final Logger logger = LoggerFactory.getLogger(ExplanationService.class); + @MockBean + ExplanationSparqlRepository explanationSparqlRepository; @Nested public class ConversionTests { @@ -116,7 +119,7 @@ void setup() { } @Test - void createRdfRepresentationTest() throws Exception { + void createRdfRepresentationTest() { String result = explanationService.convertToDesiredFormat(null, explanationService.createModelForSpecificComponent(languageContentProvider.getContentDe(), languageContentProvider.getContentEn(), componentURI)); assertAll("String contains content elements as well as componentURI", @@ -148,7 +151,7 @@ void createRdfRepresentationTest() throws Exception { } @Test - public void compareRepresentationModels() throws Exception { + public void compareRepresentationModels() { // Create Strings with different format (plain == turtle, turtle, rdfxml,jsonld) String resultEmptyHeader = explanationService.convertToDesiredFormat(null, explanationService.createModelForSpecificComponent(languageContentProvider.getContentDe(), languageContentProvider.getContentEn(), componentURI)); String resultTurtleHeader = explanationService.convertToDesiredFormat("text/turtle", explanationService.createModelForSpecificComponent(languageContentProvider.getContentDe(), languageContentProvider.getContentEn(), componentURI)); @@ -201,8 +204,6 @@ class QaSystemExplanationTest { final String graphID = "http://exampleQuestionURI.a/question"; final String questionURI = "http://question-example.com/123/32a"; - @MockBean - ExplanationSparqlRepository explanationSparqlRepository; JsonNode jsonNode; ControllerDataForTests controllerDataForTests; ObjectMapper objectMapper = new ObjectMapper(); @@ -220,7 +221,7 @@ void setup() throws IOException { // Testing if a wrong JsonNode leads to an error @Test - void fetchQuestionUriFailingTest() throws Exception { + void fetchQuestionUriFailingTest() { Throwable exception = assertThrows(Exception.class, () -> explanationService.fetchQuestionUri(graphID)); assertEquals("Couldn't fetch the question!", exception.getMessage()); } @@ -310,14 +311,30 @@ public void replacePropertiesTest(String type) { ); } - @Test - public void addingExplanationsTest() { + // Paramterized ? // Create .ttl-files parse them into a model, set RDFConnection, execute w/ repository + // just several maps with different values -> increased testability for other tests + @ParameterizedTest + @ValueSource(strings = {"annotationofinstance", "annotationofspotinstance"}) + public void addingExplanationsTest(String type) throws IOException { + List querySolutionMapList = serviceDataForTests.getQuerySolutionMapList(); + ResultSet resultSet = serviceDataForTests.createResultSet(querySolutionMapList); + + List computedExplanations = explanationService.addingExplanations(type, "de", resultSet); + + // Should contain the (if existing) prefix (is an empty element) and one explanation + assertEquals(2, computedExplanations.size()); + assertNotEquals("", computedExplanations.get(1)); + for (String expl : computedExplanations + ) { + assertFalse(expl.contains("$")); + } } @Test public void createSpecificExplanationTest() { + } @Test diff --git a/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java b/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java index 0a6bcb4..0400317 100644 --- a/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java +++ b/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java @@ -1,17 +1,25 @@ package com.wse.qanaryexplanationservice.services; import org.apache.jena.datatypes.xsd.XSDDatatype; +import org.apache.jena.query.QuerySolution; +import org.apache.jena.query.QuerySolutionMap; +import org.apache.jena.query.ResultSet; +import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.rdf.model.ResourceFactory; +import org.apache.jena.sparql.engine.binding.Binding; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class ServiceDataForTests { private final String jsonExplanationObjects = "{\"bindings\":[{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.7885976199321202\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/String_theory\"},\"source\":{\"type\":\"uri\",\"value\":\"http://demos.swe.htwk-leipzig.de:40111/question/stored-question__text_0d5a7c26-ec6a-4702-8d7f-999849938d0e\"},\"start\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"0\"},\"end\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"4\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.615Z\"},\"score\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#decimal\",\"type\":\"typed-literal\",\"value\":\"0.9347568085631697\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.12726840785317994\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Real_number\"},\"source\":{\"type\":\"uri\",\"value\":\"http://demos.swe.htwk-leipzig.de:40111/question/stored-question__text_0d5a7c26-ec6a-4702-8d7f-999849938d0e\"},\"start\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"12\"},\"end\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"16\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.826Z\"},\"score\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#decimal\",\"type\":\"typed-literal\",\"value\":\"0.977747974809564\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.0010094414975868604\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Batman\"},\"source\":{\"type\":\"uri\",\"value\":\"http://demos.swe.htwk-leipzig.de:40111/question/stored-question__text_0d5a7c26-ec6a-4702-8d7f-999849938d0e\"},\"start\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"25\"},\"end\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#integer\",\"type\":\"typed-literal\",\"value\":\"31\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:33.034Z\"},\"score\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#decimal\",\"type\":\"typed-literal\",\"value\":\"0.9999536254316278\"}}]}"; private final String jsonResultObjects = "{\"bindings\":[{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.7885976199321202\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/String_theory\"},\"target\":{\"type\":\"bnode\",\"value\":\"b0\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.615Z\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.12726840785317994\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Real_number\"},\"target\":{\"type\":\"bnode\",\"value\":\"b1\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:32.826Z\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.0010094414975868604\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfInstance\"},\"body\":{\"type\":\"uri\",\"value\":\"http://dbpedia.org/resource/Batman\"},\"target\":{\"type\":\"bnode\",\"value\":\"b2\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:NED-DBpediaSpotlight\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:33.034Z\"}},{\"annotationId\":{\"type\":\"uri\",\"value\":\"tag:stardog:api:0.9600168603465082\"},\"type\":{\"type\":\"uri\",\"value\":\"http://www.wdaqua.eu/qa#AnnotationOfAnswerSPARQL\"},\"body\":{\"type\":\"literal\",\"value\":\"PREFIX rdfs: \\nPREFIX dct: \\nPREFIX dbr: \\nPREFIX rdf: \\nPREFIX foaf: \\n\\nSELECT *\\nWHERE\\n { ?resource foaf:name ?answer ;\\n rdfs:label ?label\\n FILTER ( lang(?label) = \\\"en_list_item\\\" )\\n ?resource dct:subject dbr:Category:Superheroes_with_alter_egos\\n FILTER ( ! strstarts(lcase(?label), lcase(?answer)) )\\n VALUES ?resource { dbr:Batman }\\n }\\nORDER BY ?resource\\n\"},\"target\":{\"type\":\"uri\",\"value\":\"http://demos.swe.htwk-leipzig.de:40111/question/stored-question__text_0d5a7c26-ec6a-4702-8d7f-999849938d0e\"},\"createdBy\":{\"type\":\"uri\",\"value\":\"urn:qanary:QB-SimpleRealNameOfSuperHero\"},\"createdAt\":{\"datatype\":\"http://www.w3.org/2001/XMLSchema#dateTime\",\"type\":\"typed-literal\",\"value\":\"2023-08-09T06:39:33.888Z\"}}]}"; - private final Map mapWithRdfNodeValues = new HashMap<>() {{ put("body", ResourceFactory.createResource("http://dbpedia.org/resource/example")); put("value2", ResourceFactory.createResource("resource2")); @@ -30,7 +38,18 @@ public class ServiceDataForTests { put("start", "1"); put("end", "3"); }}; + private Logger logger = LoggerFactory.getLogger(ServiceDataForTests.class); + private List querySolutionMapList; + + public List getQuerySolutionMapList() { + List querySolutionMapArrayList = new ArrayList<>(); + QuerySolutionMap querySolutionMap = new QuerySolutionMap(); + mapWithRdfNodeValues.forEach(querySolutionMap::add); + + querySolutionMapArrayList.add(querySolutionMap); + return querySolutionMapArrayList; + } public Map getMapWithRdfNodeValues() { return mapWithRdfNodeValues; @@ -48,4 +67,57 @@ public String getJsonForResultObjects() { return jsonResultObjects; } + ResultSet createResultSet(List querySolutionMapList) { + return new ResultSet() { + final List querySolutionMaps = querySolutionMapList; + int rowIndex = -1; + + @Override + public boolean hasNext() { + return rowIndex < querySolutionMaps.size() - 1; + } + + @Override + public QuerySolution next() { + rowIndex++; + return querySolutionMaps.get(rowIndex); + } + + @Override + public QuerySolution nextSolution() { + return null; + } + + @Override + public Binding nextBinding() { + return null; + } + + @Override + public int getRowNumber() { + return 0; + } + + @Override + public List getResultVars() { + return null; + } + + @Override + public Model getResourceModel() { + return null; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove() is not supported."); + } + + @Override + public void close() { + } + }; + } + + } From 7b56bab643c90a763399ceecf434fe24efe56ca7 Mon Sep 17 00:00:00 2001 From: Dennis Schiese Date: Wed, 13 Sep 2023 11:32:28 +0200 Subject: [PATCH 11/15] Adjusted commons version interval --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fda5cbd..3f831fb 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ eu.wdaqua.qanary qa.commons - (3.7.0,4.0.0] + [3.5.4,4.0.0] org.springframework.boot From f1961a1b2a56d6f90c566f31870af23d62eeeea3 Mon Sep 17 00:00:00 2001 From: Dennis Schiese Date: Wed, 13 Sep 2023 11:47:41 +0200 Subject: [PATCH 12/15] Refactored query selector, server.port --- src/main/resources/application.properties | 2 +- .../fetch_all_annotation_types.rq | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c017daa..bd4d771 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ spring.application.name=Qanary explanation service -server.port=4001 +server.port=4000 sparqlEndpoint=http://localhost:8080/sparql # swagger-ui custom path springdoc.swagger-ui.path=/swagger-ui.html diff --git a/src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq b/src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq index 47befa0..9f58f66 100644 --- a/src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq +++ b/src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq @@ -5,6 +5,6 @@ PREFIX qa: SELECT DISTINCT ?annotationType FROM ?graphURI WHERE { - ?annotationId rdf:type ?annotationType. - ?annotationId oa:annotatedBy ?annotatedBy. + ?annotationId rdf:type ?annotationType . + ?annotationId oa:annotatedBy ?componentURI . } \ No newline at end of file From b3b2a451c6fb60a0cd3aeaed9f298ba9e03b71c4 Mon Sep 17 00:00:00 2001 From: dschiese Date: Wed, 13 Sep 2023 13:36:23 +0200 Subject: [PATCH 13/15] Edited README, incremented version -> 1.1.0 --- README.adoc | 44 +++++++++++++++++++++++++++++++------------- pom.xml | 2 +- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/README.adoc b/README.adoc index 96210a5..4829f2a 100644 --- a/README.adoc +++ b/README.adoc @@ -20,6 +20,18 @@ This webservice provides some functionalities to compute explanations for some Q toc::[] +== Supported Annotation Types + +The explanation's creation follows a rule-based approach, what means that each explanation for a component is created different depending on the annotationType of its written output. + +Therefor not all Annotation types (=components) are supported yet. +The supported ones are the following: + +. AnnotationOfInstance +. AnnotationOfSpotInstance + +You can check the output annotation type for existing components in the component's README at https://github.com/WDAqua/Qanary-question-answering-component . + == Build and Run === Build @@ -38,22 +50,21 @@ sparqlEndpoint = http://demos.swe.htwk-leipzig.de:40111/sparql ==== *from scratch using Java* * create a executable -jar-file like so: - - ** `mvn clean install` + +** `mvn clean install` ==== *by using Docker* . *_locally:_* - - - build a Docker image with the existing Dockerfile by using the following command inside the projects folder - - `docker build . -t qanary-explanation-service` - + +- build a Docker image with the existing Dockerfile by using the following command inside the projects folder +- `docker build . -t qanary-explanation-service` + . *_from Dockerhub:_* - - pull the repository *wseresearch/qanary-explanation-service* with `docker pull wseresearch/qanary-explanation-service:latest` - - - you might replace the tag `latest` with your desired release-tag from `https://hub.docker.com/repository/docker/wseresearch/qanary-explanation-service/tags` - +- pull the repository *wseresearch/qanary-explanation-service* with `docker pull wseresearch/qanary-explanation-service:latest` + +- you might replace the tag `latest` with your desired release-tag from `https://hub.docker.com/repository/docker/wseresearch/qanary-explanation-service/tags` === Running the application @@ -69,7 +80,7 @@ sparqlEndpoint = http://demos.swe.htwk-leipzig.de:40111/sparql * run the built image with `docker run -p 12345:4000 qanary-explanation-service` with parameters: ** *_"p":_* is the optional port mapping if you wish to run the application on a different port on your machine, e.g. in this case the *applications port (default: 4000) is mapped to port 12345* -. *_from Dockerhub:_* +. *_from Dockerhub:_* * run the pulled image with `docker run -p 12345:4000 qanary-explanation-service:latest` with: ** your previous selected tag (replace *latest* with *your tag*) @@ -84,9 +95,11 @@ Currently, there are several endpoints with different tasks, as of the latest ve -- *Path variables:* -- + -- ** required: graphURI::String -- + -- *Returns:* @@ -117,11 +130,14 @@ WHERE { -- *Path Variables:* -- + -- ** required: graphURI::String ** optional: componentURI::String -- + *Returns:* + -- ** with only the graphURI given: a formatted explanation for the QA-process on the graphURI depending on the following `Accept` header: * none: Turtle @@ -136,9 +152,11 @@ WHERE { * `text/turtle`: Turtle * other: no response, NOT_ACCEPTABLE (406) -- + provides two endpoints to either request an explanation for a QA process with a given graphURI or a specific explanation for one componentURI (added as another path variable). -In both cases, the explanation format depends on the Accept-Header: RDF/XML, JSONLD, or RDF Turtle. +In both cases, the explanation format depends on the Accept-Header: RDF/XML, JSONLD, or RDF Turtle. If there`s no `Accept` header provided in the request, then the format will be RDF Turtle. + -- .*Example Return for QA system:* [%collapsible] @@ -232,7 +250,6 @@ explanations:hasExplanationForCreatedData ==== -- - == Example . Firstly we start a QA process with the Question "What is the real name of Superman?" and the components @@ -336,6 +353,7 @@ explanations:hasExplanationForCreatedData ==== == SpringDocs & SwaggerUI + Swagger UI is available at http://localhost:40190/swagger-ui/index.html API Docs are available at http://localhost:40190/api-docs diff --git a/pom.xml b/pom.xml index 3f831fb..8b17803 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.wse qanary-explanation-service - 1.0.2 + 1.1.0 Qanary explanation service Webservice for rule-based explanation of QA-Systems as well as specific components From 677efbfd8ad6e1a6571bf03059a6078a249c6d06 Mon Sep 17 00:00:00 2001 From: dschiese Date: Wed, 13 Sep 2023 17:07:10 +0200 Subject: [PATCH 14/15] Fixed bug that multiple components with same annotations lead to wrong explanation --- .../services/ExplanationService.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java index abf5837..9ed1261 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java +++ b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java @@ -401,11 +401,12 @@ public Model createSystemModel(Map models, ComponentPojo[] compon * @param lang Given language * @return List with explanations for the given language. At the end it includes all explanations for every annotation type */ - public List createComponentExplanation(String graphURI, String lang, List types) throws IOException { + public List createComponentExplanation(String graphURI, String lang, List types, String componentURI) throws IOException { return createSpecificExplanations( types.toArray(String[]::new), graphURI, - lang + lang, + componentURI ); } @@ -439,12 +440,12 @@ public List fetchAllAnnotations(String graphURI, String componentURI) th * @param lang The language for the explanation * @return List with explanations. At the end it includes all explanations for every annotation type */ - public List createSpecificExplanations(String[] usedTypes, String graphURI, String lang) throws IOException { + public List createSpecificExplanations(String[] usedTypes, String graphURI, String lang, String componentURI) throws IOException { List explanations = new ArrayList<>(); for (String type : usedTypes ) { - explanations.addAll(createSpecificExplanation(type, graphURI, lang)); + explanations.addAll(createSpecificExplanation(type, graphURI, lang, componentURI)); } return explanations; } @@ -456,8 +457,8 @@ public List createSpecificExplanations(String[] usedTypes, String graphU * @param lang The language for the explanation * @return A list of explanation containing a prefix explanation and one entry for every annotation of the givent type */ - public List createSpecificExplanation(String type, String graphURI, String lang) throws IOException { - String query = buildSparqlQuery(graphURI, null, annotationsTypeAndQuery.get(type)); + public List createSpecificExplanation(String type, String graphURI, String lang, String componentURI) throws IOException { + String query = buildSparqlQuery(graphURI, componentURI, annotationsTypeAndQuery.get(type)); // For the first language that will be executed, for each annotation-type a component created if (!stringResultSetMap.containsKey(type)) @@ -548,7 +549,7 @@ public String getStringFromFile(String path) throws IOException { */ public String createTextualExplanation(String graphURI, String componentURI, String lang, List types) throws IOException { - List createdExplanations = createComponentExplanation(graphURI, lang, types); + List createdExplanations = createComponentExplanation(graphURI, lang, types, componentURI); AtomicInteger i = new AtomicInteger(); List explanations = createdExplanations.stream().skip(1).map((explanation) -> i.incrementAndGet() + ". " + explanation).toList(); From d714ea9f865660f5d8205f45e9a667928b8c61f7 Mon Sep 17 00:00:00 2001 From: dschiese Date: Thu, 14 Sep 2023 13:01:54 +0200 Subject: [PATCH 15/15] Edit sparqlEndoint --- src/main/resources/application.properties | 2 +- .../services/ExplanationServiceTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index bd4d771..1ebb772 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,6 @@ spring.application.name=Qanary explanation service server.port=4000 -sparqlEndpoint=http://localhost:8080/sparql +sparqlEndpoint=http://demos.swe.htwk-leipzig.de:40111/sparql # swagger-ui custom path springdoc.swagger-ui.path=/swagger-ui.html # api-docs custom path diff --git a/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java b/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java index ead43cb..cae5e53 100644 --- a/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java +++ b/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java @@ -331,6 +331,7 @@ public void addingExplanationsTest(String type) throws IOException { } + /* @Test public void createSpecificExplanationTest() { @@ -346,7 +347,7 @@ public void createSpecificExplanationsTest() { public void fetchAllAnnotationsTest() { } - + */ }