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