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 722b256..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 @@ -33,7 +33,7 @@ eu.wdaqua.qanary qa.commons - 3.5.4 + [3.5.4,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 0e83f73..a422fb4 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java +++ b/src/main/java/com/wse/qanaryexplanationservice/controller/ExplanationController.java @@ -7,15 +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.*; @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 @@ -31,30 +28,39 @@ 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) { - 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 - return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST); } + } + @GetMapping("/explain/{graphURI}/{componentURI}") + public ResponseEntity explain( + @PathVariable String graphURI, + @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 dd54112..9ed1261 100644 --- a/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java +++ b/src/main/java/com/wse/qanaryexplanationservice/services/ExplanationService.java @@ -8,35 +8,66 @@ 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.vocabulary.RDF; import org.apache.jena.vocabulary.RDFS; 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.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; @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"; + 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"); 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 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; @Autowired @@ -46,28 +77,43 @@ 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 + ": " + StringUtils.join(explanations, " "); + } else if (Objects.equals(lang, "de")) { + result = "Die Komponente " + componentURI + " hat " + explanations.size() + " Annotation(en) zum Graph hinzugefügt" + + prefix + ": " + StringUtils.join(explanations, " "); + } + return result; + } + /** * Computes a textual explanation for a specific component on a specific graphURI * - * @param graphUri specific graphURI - * @param componentUri specific componentURI - * @param fetchQueryEmpty Used query to fetch needed information + * @param graphUri specific graphURI + * @param componentUri specific componentURI * @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 { + + List types = new ArrayList<>(); + if (stringResultSetMap.isEmpty()) + types = fetchAllAnnotations(graphUri, componentUri); - return createModelForSpecificComponent(contentDe, contentEn, componentUri); + String contentDE = createTextualExplanation(graphUri, componentUri, "de", types); + String contentEN = createTextualExplanation(graphUri, componentUri, "en", types); + + return createModelForSpecificComponent(contentDE, contentEN, componentUri); } // Creating the query, executing it and transform the response to an array of ExplanationObject objects @@ -77,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 * @@ -146,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 { @@ -259,14 +303,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)); + models.put(component.getComponent().getValue(), // === componentURI + createModel(graphURI, component.getComponent().getValue())); // create a model for that componentURI } String questionURI = fetchQuestionUri(graphURI); @@ -350,4 +394,168 @@ public Model createSystemModel(Map models, ComponentPojo[] compon return systemExplanationModel; } + /** + * 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 lang, List types, String componentURI) throws IOException { + return createSpecificExplanations( + types.toArray(String[]::new), + graphURI, + lang, + componentURI + ); + } + + /** + * Fetching all annotations a component has created. + * + * @return A list with all different annotation-types + */ + 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"); + String typeLocalName = type.asResource().getLocalName(); + logger.info("Annotation-Type found: {}", typeLocalName); + types.add(typeLocalName.toLowerCase()); + } + + return types; + } + + /** + * 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, String componentURI) throws IOException { + + List explanations = new ArrayList<>(); + for (String type : usedTypes + ) { + explanations.addAll(createSpecificExplanation(type, graphURI, lang, componentURI)); + } + 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, 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)) + stringResultSetMap.put(type, this.explanationSparqlRepository.executeSparqlQueryWithResultSet(query)); + + 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 A list of explanations for the given type in the given language + */ + 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"); + + while (results.hasNext()) { + QuerySolution querySolution = results.next(); + explanationsForCurrentType.add(replaceProperties(convertQuerySolutionToMap(querySolution), template)); + } + + return explanationsForCurrentType; + } + + /** + * Replaces all placeholders in the template with attributes from the passed QuerySolution + * + * @param template Template including the defined pre- and suffixes + * @return Template with replaced placeholders + */ + 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); + logger.info("Template with inserted params: {}", 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. + * + * @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, + entry -> { + if (entry.getValue().isResource()) + return entry.getValue().toString(); + else + return entry.getValue().asNode().getLiteralValue().toString(); + } + )); + } + + /** + * 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())); + } + + /** + * 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, List types) throws IOException { + + List createdExplanations = createComponentExplanation(graphURI, lang, types, componentURI); + + AtomicInteger i = new AtomicInteger(); + 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; + } + } 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..9960a1d --- /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..8c46ab7 --- /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/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..fdbd42e --- /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..8087832 --- /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..7466501 --- /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..9e31867 --- /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/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/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..480998e --- /dev/null +++ b/src/main/resources/queries/queries_for_annotation_types/annotations_of_instance_query.rq @@ -0,0 +1,22 @@ +PREFIX rdf: +PREFIX oa: +PREFIX qa: + +SELECT * +FROM ?graphURI +WHERE { + ?annotationId rdf:type qa:AnnotationOfInstance . + ?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/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..4c46826 --- /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/general_explanation.rq b/src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq similarity index 63% rename from src/main/resources/queries/general_explanation.rq rename to src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq index 1954ac7..9f58f66 100644 --- a/src/main/resources/queries/general_explanation.rq +++ b/src/main/resources/queries/queries_for_annotation_types/fetch_all_annotation_types.rq @@ -1,13 +1,10 @@ PREFIX rdf: PREFIX oa: PREFIX qa: -SELECT * -FROM ?graphURI +SELECT DISTINCT ?annotationType +FROM ?graphURI WHERE { + ?annotationId rdf:type ?annotationType . ?annotationId oa:annotatedBy ?componentURI . - ?annotationId oa:hasBody ?body . - ?annotationId oa:annotatedAt $createdAt . - ?annotationId qa:score ?score . -} - +} \ 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 diff --git a/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java b/src/test/java/com/wse/qanaryexplanationservice/services/ExplanationServiceTest.java index 5199b82..cae5e53 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,13 @@ 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.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -39,9 +46,9 @@ @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); + @MockBean + ExplanationSparqlRepository explanationSparqlRepository; @Nested public class ConversionTests { @@ -112,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", @@ -144,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)); @@ -197,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(); @@ -216,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()); } @@ -241,4 +246,109 @@ 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); + } + ); + } + + // 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 + 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 c658845..0400317 100644 --- a/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java +++ b/src/test/java/com/wse/qanaryexplanationservice/services/ServiceDataForTests.java @@ -1,10 +1,63 @@ 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\\\" )\\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\"}}]}"; + 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"); + }}; + 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; + } + + public Map getConvertedMapWithStringValues() { + return convertedMapWithStringValues; + } public String getJsonForExplanationObjects() { return jsonExplanationObjects; @@ -14,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() { + } + }; + } + + } 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