From e0dbe9802bc05362b88b2f87e5df2c52378866c1 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sun, 18 Aug 2024 12:50:00 +0200 Subject: [PATCH 01/64] added a route /askwithgaps to start implementing return of gaps --- .../api/impl/ProactiveApiServiceImpl.java | 124 +++++++++++++++++- 1 file changed, 122 insertions(+), 2 deletions(-) diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java index f9bc9e91..26e5f530 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java @@ -46,6 +46,126 @@ public class ProactiveApiServiceImpl { private RestKnowledgeBaseManager manager = RestKnowledgeBaseManager.newInstance(); + @POST + @Path("/askwithgaps") + @Consumes({ "application/json; charset=UTF-8" }) + @Produces({ "application/json; charset=UTF-8", "text/plain; charset=UTF-8" }) + public void scAskWithGapsPost( + @Parameter(description = "The Knowledge Base Id for which to execute the ask.", required = true) @HeaderParam("Knowledge-Base-Id") String knowledgeBaseId, + @Parameter(description = "The Ask Knowledge Interaction Id to execute.", required = true) @HeaderParam("Knowledge-Interaction-Id") String knowledgeInteractionId, + + @Parameter(description = "The keys bindings are allowed to be incomplete, but they must correspond to the binding keys that were defined in the knowledge interaction.", required = true) @NotNull @Valid JsonNode recipientAndBindingSet, + @Suspended final AsyncResponse asyncResponse, @Context SecurityContext securityContext) { + + LOG.info("scAskPost called for KB {} and KI {} - {}", knowledgeBaseId, knowledgeInteractionId, + recipientAndBindingSet); + + RecipientAndBindingSet recipientAndBindingSetObject; + try { + recipientAndBindingSetObject = new RecipientAndBindingSet(recipientAndBindingSet); + } catch (IllegalArgumentException e) { + LOG.debug("", e); + var response = new ResponseMessage(); + response.setMessageType("error"); + response.setMessage(e.getMessage()); + asyncResponse.resume(Response.status(Status.BAD_REQUEST).entity(response).build()); + return; + } + + if (knowledgeBaseId == null || knowledgeInteractionId == null) { + var response = new ResponseMessage(); + response.setMessageType("error"); + response.setMessage("Both Knowledge-Base-Id and Knowledge-Interaction-Id headers should be non-null."); + asyncResponse.resume(Response.status(Status.BAD_REQUEST).entity(response).build()); + return; + } + + var kb = this.manager.getKB(knowledgeBaseId); + if (kb == null) { + if (this.manager.hasSuspendedKB(knowledgeBaseId)) { + this.manager.removeSuspendedKB(knowledgeBaseId); + var response = new ResponseMessage(); + response.setMessageType("error"); + response.setMessage( + "This knowledge base has been suspended due to inactivity. Please reregister the knowledge base and its knowledge interactions."); + asyncResponse.resume(Response.status(Status.NOT_FOUND).entity(response).build()); + return; + } else { + var response = new ResponseMessage(); + response.setMessageType("error"); + response.setMessage("Smart connector not found, because its ID is unknown."); + asyncResponse.resume(Response.status(Status.NOT_FOUND).entity(response).build()); + return; + } + } + + try { + new URI(knowledgeInteractionId); + } catch (URISyntaxException e) { + var response = new ResponseMessage(); + response.setMessageType("error"); + response.setMessage("Knowledge interaction not found, because its ID must be a valid URI."); + asyncResponse.resume(Response.status(Status.BAD_REQUEST).entity(response).build()); + return; + } + + if (!kb.hasKnowledgeInteraction(knowledgeInteractionId)) { + var response = new ResponseMessage(); + response.setMessageType("error"); + response.setMessage("Knowledge Interaction not found, because its ID is unknown."); + asyncResponse.resume(Response.status(Status.NOT_FOUND).entity(response).build()); + return; + } + + KnowledgeInteractionWithId ki = kb.getKnowledgeInteraction(knowledgeInteractionId); + if (!ki.getKnowledgeInteractionType().equals("AskKnowledgeInteraction")) { + var response = new ResponseMessage(); + response.setMessageType("error"); + response.setMessage("Given Knowledge Interaction ID should have type AskKnowledgeInteraction and not " + + ki.getKnowledgeInteractionType() + "."); + asyncResponse.resume(Response.status(Status.BAD_REQUEST).entity(response).build()); + return; + } + + try { + var askFuture = kb.ask(knowledgeInteractionId, recipientAndBindingSetObject.recipient, + recipientAndBindingSetObject.bindingSet); + + askFuture.thenAccept(askResult -> { + + LOG.info("AskResult received, resuming async response: {}", askResult); + List infos = askResult.getExchangeInfoPerKnowledgeBase().stream() + .map(aei -> new AskExchangeInfo().bindingSet(this.bindingSetToList(aei.getBindings())) + .knowledgeBaseId(aei.getKnowledgeBaseId().toString()) + .knowledgeInteractionId(aei.getKnowledgeInteractionId().toString()) + .exchangeStart(Date.from(aei.getExchangeStart())) + .exchangeStart(Date.from(aei.getExchangeStart())) + .initiator(toInitiatorEnumAsk(aei.getInitiator())) + .exchangeEnd(Date.from(aei.getExchangeEnd())).status(aei.getStatus().toString()) + .failedMessage(aei.getFailedMessage())) + .collect(Collectors.toList()); + + AskResult ar = new AskResult().bindingSet(this.bindingSetToList(askResult.getBindings())) + .exchangeInfo(infos); + + asyncResponse.resume(Response.status(Status.OK).entity(ar).build()); + }); + + } catch (URISyntaxException | InterruptedException | ExecutionException e) { + LOG.trace("", e); + var response = new ResponseMessage(); + response.setMessageType("error"); + response.setMessage("Something went wrong while sending a POST or while waiting on the REACT."); + asyncResponse.resume(Response.status(Status.INTERNAL_SERVER_ERROR).entity(response).build()); + } catch (IllegalArgumentException e) { + LOG.trace("", e); + var response = new ResponseMessage(); + response.setMessageType("error"); + response.setMessage(e.getMessage()); + asyncResponse.resume(Response.status(Status.BAD_REQUEST).entity(response).build()); + } + } + @POST @Path("/ask") @Consumes({ "application/json; charset=UTF-8" }) @@ -57,7 +177,7 @@ public void scAskPost( @Parameter(description = "The keys bindings are allowed to be incomplete, but they must correspond to the binding keys that were defined in the knowledge interaction.", required = true) @NotNull @Valid JsonNode recipientAndBindingSet, @Suspended final AsyncResponse asyncResponse, @Context SecurityContext securityContext) { - LOG.debug("scAskPost called for KB {} and KI {} - {}", knowledgeBaseId, knowledgeInteractionId, + LOG.info("scAskPost called for KB {} and KI {} - {}", knowledgeBaseId, knowledgeInteractionId, recipientAndBindingSet); RecipientAndBindingSet recipientAndBindingSetObject; @@ -133,7 +253,7 @@ public void scAskPost( askFuture.thenAccept(askResult -> { - LOG.debug("AskResult received, resuming async response: {}", askResult); + LOG.info("AskResult received, resuming async response: {}", askResult); List infos = askResult.getExchangeInfoPerKnowledgeBase().stream() .map(aei -> new AskExchangeInfo().bindingSet(this.bindingSetToList(aei.getBindings())) .knowledgeBaseId(aei.getKnowledgeBaseId().toString()) From 65e1a2b52ab0a1e08a17d4fbea73280dcfffa908 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Tue, 20 Aug 2024 10:19:04 +0200 Subject: [PATCH 02/64] added log info + set reasoner enabled in the SC LC Api service --- .../engine/rest/api/impl/ProactiveApiServiceImpl.java | 2 +- .../api/impl/SmartConnectorLifeCycleApiServiceImpl.java | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java index 26e5f530..819a0c4f 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java @@ -57,7 +57,7 @@ public void scAskWithGapsPost( @Parameter(description = "The keys bindings are allowed to be incomplete, but they must correspond to the binding keys that were defined in the knowledge interaction.", required = true) @NotNull @Valid JsonNode recipientAndBindingSet, @Suspended final AsyncResponse asyncResponse, @Context SecurityContext securityContext) { - LOG.info("scAskPost called for KB {} and KI {} - {}", knowledgeBaseId, knowledgeInteractionId, + LOG.info("scAskWithGapsPost called for KB {} and KI {} - {}", knowledgeBaseId, knowledgeInteractionId, recipientAndBindingSet); RecipientAndBindingSet recipientAndBindingSetObject; diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/SmartConnectorLifeCycleApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/SmartConnectorLifeCycleApiServiceImpl.java index 29d73bcc..41e3f23e 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/SmartConnectorLifeCycleApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/SmartConnectorLifeCycleApiServiceImpl.java @@ -143,10 +143,15 @@ public void scPost(@Parameter(description = "", required = true) @NotNull @Valid return; } + // set reasoner enabled to true for testing + smartConnector.setReasonerEnabled(true); // TODO: this should come from the caller!! + LOG.info("Setting reasoner to enabled in order to activate the creation of a reasoner plan."); + // end set reasoner enabled to true for testing + final boolean reasonerEnabled = smartConnector.getReasonerEnabled() == null ? false : smartConnector.getReasonerEnabled(); - - LOG.info("Creating smart connector with ID {}.", kbId); + + LOG.info("Creating smart connector with ID {} and reasoner enabled {}.", kbId, reasonerEnabled); // Tell the manager to create a KB, store it, and have it set up a SC etc. this.manager.createKB(new SmartConnector().knowledgeBaseId(kbId.toString()).knowledgeBaseName(kbName) From fc8bdd080100e3b4d5706d71594a2581fe9e2bfc Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Tue, 20 Aug 2024 10:19:48 +0200 Subject: [PATCH 03/64] First attempt to get Knowledge Gaps out of the SC --- .../smartconnector/impl/SmartConnectorImpl.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorImpl.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorImpl.java index 74a5efe7..aff9918d 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorImpl.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorImpl.java @@ -10,7 +10,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import eu.knowledge.engine.reasoner.ReasonerPlan; import eu.knowledge.engine.reasoner.Rule; +import eu.knowledge.engine.reasoner.api.TriplePattern; import eu.knowledge.engine.smartconnector.api.AnswerHandler; import eu.knowledge.engine.smartconnector.api.AnswerKnowledgeInteraction; import eu.knowledge.engine.smartconnector.api.AskKnowledgeInteraction; @@ -302,6 +304,17 @@ public void unregister(ReactKnowledgeInteraction aReactKI) { public CompletableFuture ask(AskKnowledgeInteraction anAKI, RecipientSelector aSelector, BindingSet aBindingSet) { + // new code to test gaps + AskPlan plan = this.planAsk(anAKI, aSelector); + ReasonerPlan rn = plan.getReasonerPlan(); + if (rn == null) + LOG.info("ReasonerPlan is empty"); + else { + // check for knowledge gaps + Set> gaps = Util.getKnowledgeGaps(rn.getStartNode()); + LOG.info("Found gaps: " + gaps); } + // end new code to test gaps + return this.planAsk(anAKI, aSelector).execute(aBindingSet).exceptionally((Throwable t) -> { LOG.error("Processing an Ask should not result in errors.", t); return null; From c5061587673e01ce25bbc216b816712b9ee4b77a Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Wed, 21 Aug 2024 16:53:39 +0200 Subject: [PATCH 04/64] added a method findKnowledgeGaps to type AskPlan --- .../knowledge/engine/smartconnector/api/AskPlan.java | 10 ++++++++++ .../engine/smartconnector/impl/AskPlanImpl.java | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskPlan.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskPlan.java index 73289a8a..07e1a864 100644 --- a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskPlan.java +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskPlan.java @@ -1,8 +1,10 @@ package eu.knowledge.engine.smartconnector.api; +import java.util.Set; import java.util.concurrent.CompletableFuture; import eu.knowledge.engine.reasoner.ReasonerPlan; +import eu.knowledge.engine.reasoner.api.TriplePattern; /** * This class contains the plan of the Smart Connector for executing a @@ -30,4 +32,12 @@ public interface AskPlan { * reasoning was disabled. */ public ReasonerPlan getReasonerPlan(); + + /** + * Get the possible knowledge gaps in the reasoning plan + * + * @return A set of knowledge gaps in the reasoning plan, or {@code null} if + * reasoning was disabled. + */ + public Set> findKnowledgeGaps(); } diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/AskPlanImpl.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/AskPlanImpl.java index a473c07c..8b90ecbc 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/AskPlanImpl.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/AskPlanImpl.java @@ -1,8 +1,10 @@ package eu.knowledge.engine.smartconnector.impl; +import java.util.Set; import java.util.concurrent.CompletableFuture; import eu.knowledge.engine.reasoner.ReasonerPlan; +import eu.knowledge.engine.reasoner.api.TriplePattern; import eu.knowledge.engine.smartconnector.api.AskPlan; import eu.knowledge.engine.smartconnector.api.AskResult; import eu.knowledge.engine.smartconnector.api.BindingSet; @@ -29,4 +31,10 @@ public ReasonerPlan getReasonerPlan() { : null); } + @Override + public Set> findKnowledgeGaps() { + return (this.processor instanceof ReasonerProcessor ? Util.getKnowledgeGaps(((ReasonerProcessor) this.processor).getReasonerPlan().getStartNode()) + : null); + } + } From 06de53bcdfcd1d496d5a73487a3b096a35bad784 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Wed, 21 Aug 2024 16:56:46 +0200 Subject: [PATCH 05/64] deleted the reasoner enabled setting again as it is in the RestAPI --- .../api/impl/SmartConnectorLifeCycleApiServiceImpl.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/SmartConnectorLifeCycleApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/SmartConnectorLifeCycleApiServiceImpl.java index 41e3f23e..8fcb8f3a 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/SmartConnectorLifeCycleApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/SmartConnectorLifeCycleApiServiceImpl.java @@ -143,15 +143,10 @@ public void scPost(@Parameter(description = "", required = true) @NotNull @Valid return; } - // set reasoner enabled to true for testing - smartConnector.setReasonerEnabled(true); // TODO: this should come from the caller!! - LOG.info("Setting reasoner to enabled in order to activate the creation of a reasoner plan."); - // end set reasoner enabled to true for testing - final boolean reasonerEnabled = smartConnector.getReasonerEnabled() == null ? false : smartConnector.getReasonerEnabled(); - LOG.info("Creating smart connector with ID {} and reasoner enabled {}.", kbId, reasonerEnabled); + LOG.info("Creating smart connector with ID {} and reasoner enabled '{}'.", kbId, reasonerEnabled); // Tell the manager to create a KB, store it, and have it set up a SC etc. this.manager.createKB(new SmartConnector().knowledgeBaseId(kbId.toString()).knowledgeBaseName(kbName) From 9f96bd3f8387de3dbb477e6d13a4c2600e450619 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Thu, 22 Aug 2024 16:06:25 +0200 Subject: [PATCH 06/64] reset the AskPlan interface and implementation to original --- .../knowledge/engine/smartconnector/api/AskPlan.java | 10 ---------- .../engine/smartconnector/impl/AskPlanImpl.java | 8 -------- 2 files changed, 18 deletions(-) diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskPlan.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskPlan.java index 07e1a864..73289a8a 100644 --- a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskPlan.java +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskPlan.java @@ -1,10 +1,8 @@ package eu.knowledge.engine.smartconnector.api; -import java.util.Set; import java.util.concurrent.CompletableFuture; import eu.knowledge.engine.reasoner.ReasonerPlan; -import eu.knowledge.engine.reasoner.api.TriplePattern; /** * This class contains the plan of the Smart Connector for executing a @@ -32,12 +30,4 @@ public interface AskPlan { * reasoning was disabled. */ public ReasonerPlan getReasonerPlan(); - - /** - * Get the possible knowledge gaps in the reasoning plan - * - * @return A set of knowledge gaps in the reasoning plan, or {@code null} if - * reasoning was disabled. - */ - public Set> findKnowledgeGaps(); } diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/AskPlanImpl.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/AskPlanImpl.java index 8b90ecbc..a473c07c 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/AskPlanImpl.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/AskPlanImpl.java @@ -1,10 +1,8 @@ package eu.knowledge.engine.smartconnector.impl; -import java.util.Set; import java.util.concurrent.CompletableFuture; import eu.knowledge.engine.reasoner.ReasonerPlan; -import eu.knowledge.engine.reasoner.api.TriplePattern; import eu.knowledge.engine.smartconnector.api.AskPlan; import eu.knowledge.engine.smartconnector.api.AskResult; import eu.knowledge.engine.smartconnector.api.BindingSet; @@ -31,10 +29,4 @@ public ReasonerPlan getReasonerPlan() { : null); } - @Override - public Set> findKnowledgeGaps() { - return (this.processor instanceof ReasonerProcessor ? Util.getKnowledgeGaps(((ReasonerProcessor) this.processor).getReasonerPlan().getStartNode()) - : null); - } - } From 7409ef2ef855a4fd932f6f97e826afa06370f6c8 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Thu, 22 Aug 2024 16:14:12 +0200 Subject: [PATCH 07/64] added knowledgeGaps variable to SC AskResult --- .../engine/smartconnector/api/AskResult.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskResult.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskResult.java index c90dfd8a..e078762d 100644 --- a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskResult.java +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskResult.java @@ -9,6 +9,7 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.reasoner.ReasonerPlan; +import eu.knowledge.engine.reasoner.api.TriplePattern; /** * An {@link AskResult} contains the result of the @@ -26,6 +27,8 @@ public class AskResult { * Can be null, if the matcher is used instead of the reasoner. */ private ReasonerPlan reasonerPlan; + + private Set> knowledgeGaps; private final Set exchangeInfos; @@ -38,14 +41,15 @@ public class AskResult { * value for every available variable in the * {@link GraphPattern}. */ - public AskResult(BindingSet someBindings, Set askExchangeInfos, ReasonerPlan aRootNode) { + public AskResult(BindingSet someBindings, Set askExchangeInfos, ReasonerPlan aRootNode, Set> kGaps) { this.bindings = someBindings; this.exchangeInfos = askExchangeInfos; this.reasonerPlan = aRootNode; + this.knowledgeGaps = kGaps; } public AskResult(BindingSet someBindings, Set askExchangeInfos) { - this(someBindings, askExchangeInfos, null); + this(someBindings, askExchangeInfos, null, null); } /** @@ -99,8 +103,12 @@ public ReasonerPlan getReasonerPlan() { return this.reasonerPlan; } + public Set> getKnowledgeGaps() { + return this.knowledgeGaps; + } + @Override public String toString() { - return "AskResult [bindings=" + bindings + ", exchangeInfoPerKnowledgeBase=" + exchangeInfos + "]"; + return "AskResult [bindings=" + bindings + ", exchangeInfoPerKnowledgeBase=" + exchangeInfos + ", knowledgeGaps=" + knowledgeGaps + "]"; } } From 843f5956cddf661a6f256418c966ae334aae0684 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Thu, 22 Aug 2024 16:15:32 +0200 Subject: [PATCH 08/64] reset SmartConnectorImpl to original --- .../smartconnector/impl/SmartConnectorImpl.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorImpl.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorImpl.java index aff9918d..832e7525 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorImpl.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorImpl.java @@ -304,17 +304,6 @@ public void unregister(ReactKnowledgeInteraction aReactKI) { public CompletableFuture ask(AskKnowledgeInteraction anAKI, RecipientSelector aSelector, BindingSet aBindingSet) { - // new code to test gaps - AskPlan plan = this.planAsk(anAKI, aSelector); - ReasonerPlan rn = plan.getReasonerPlan(); - if (rn == null) - LOG.info("ReasonerPlan is empty"); - else { - // check for knowledge gaps - Set> gaps = Util.getKnowledgeGaps(rn.getStartNode()); - LOG.info("Found gaps: " + gaps); } - // end new code to test gaps - return this.planAsk(anAKI, aSelector).execute(aBindingSet).exceptionally((Throwable t) -> { LOG.error("Processing an Ask should not result in errors.", t); return null; From dd28c62abe15fff6f7aadf89893ed6ed22c441bb Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sun, 25 Aug 2024 10:21:10 +0200 Subject: [PATCH 09/64] added get knowledge gaps method and used it in ask execution result --- .../impl/ReasonerProcessor.java | 138 +++++++++++++++++- .../impl/SmartConnectorImpl.java | 2 - 2 files changed, 136 insertions(+), 4 deletions(-) diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java index 973b7d40..f378ced9 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java @@ -2,11 +2,13 @@ import java.io.IOException; import java.time.Instant; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -20,7 +22,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import eu.knowledge.engine.reasoner.AntSide; import eu.knowledge.engine.reasoner.BaseRule; +import eu.knowledge.engine.reasoner.BindingSetHandler; import eu.knowledge.engine.reasoner.BaseRule.MatchStrategy; import eu.knowledge.engine.reasoner.ProactiveRule; import eu.knowledge.engine.reasoner.ReasonerPlan; @@ -29,6 +33,7 @@ import eu.knowledge.engine.reasoner.TaskBoard; import eu.knowledge.engine.reasoner.TransformBindingSetHandler; import eu.knowledge.engine.reasoner.api.TriplePattern; +import eu.knowledge.engine.reasoner.rulenode.RuleNode; import eu.knowledge.engine.reasoner.rulestore.RuleStore; import eu.knowledge.engine.smartconnector.api.AnswerKnowledgeInteraction; import eu.knowledge.engine.smartconnector.api.AskExchangeInfo; @@ -70,6 +75,7 @@ public class ReasonerProcessor extends SingleInteractionProcessor { private final Set postExchangeInfos; private Set additionalDomainKnowledge; private ReasonerPlan reasonerPlan; + private Set> knowledgeGaps; /** * These two bindingset handler are a bit dodgy. We need them to make the post @@ -151,15 +157,37 @@ public void planAskInteraction(MyKnowledgeInteractionInfo aAKI) { } } + /** + * In this ask operation that smart connector should do the following: + * 1: first, make a plan for executing the ask using the planAsk method in this smart connector + * this will return an object of type AskPlan. + * 2: second, execute the plan on the knowledge network to potentially get bindings + * for the pattern in the ask. + * 3: third, as part of the execution also find knowledge gaps in the reasoner plan when the + * resulting binding set is empty. This will result in an object of type AskResult that + * contains the resulting bindings, exchange info and + * optionally the reasoner plan plus knowledge gaps. + * + * This can lead to the following situations: + * 1: a plan, a non-empty binding set and no gaps => ask has a result + * 2: a plan, an empty binding set and no gaps => ask has an empty result + * 3: a plan, an empty binding set with gaps => ask has no result and gaps are found + */ + + @Override public CompletableFuture executeAskInteraction(BindingSet someBindings) { this.finalBindingSetFuture = new CompletableFuture(); // this.reasonerPlan.optimize(); continueReasoningBackward(translateBindingSetTo(someBindings)); - + return this.finalBindingSetFuture.thenApply((bs) -> { - return new AskResult(translateBindingSetFrom(bs), this.askExchangeInfos, this.reasonerPlan); + this.knowledgeGaps = new HashSet<>(); + if (bs.isEmpty()) { + this.knowledgeGaps = getKnowledgeGaps(this.reasonerPlan.getStartNode()); + } + return new AskResult(translateBindingSetFrom(bs), this.askExchangeInfos, this.reasonerPlan, this.knowledgeGaps); }); } @@ -569,4 +597,110 @@ public CompletableFuture handle(eu.knowledge.engine.reasoner.api.BindingSe public ReasonerPlan getReasonerPlan() { return this.reasonerPlan; } + + /** + * Returns the knowledge gap of this reasoning node. A knowledge gap is a subset + * of this node's antecedent triple patterns that do not match any neighbor that + * has no knowledge gaps. + * + * Currently, this method does not show how the knowledge gaps influence each + * other. Some knowledge gaps might have an {@code or}-relation (namely those + * that occur on the same triple) and some might have {@code and}-relations + * (i.e. those that do not occur on the same triple). This information is + * important if you want to know how to solve the gaps because 2 gaps related by + * {@code or} do not both need to be solved, but only one of them. While 2 gaps + * related by {@code and} both need to be solved to solve the gap. + * + * @return returns all triples that have no matching nodes (and for which there + * are no alternatives). Note that it returns a set of sets. Where every + * set in this set represents a single way to resolve the knowledge gaps + * present in this reasoning graph. So, {@code [[A],[B]]} means either + * triple {@code A} OR triple {@code B} needs be added to + * solve the gap or both, while {@code [[A,B]]} means that both + * {@code A} AND {@code B} need to be added to solve the + * gap. + */ + public Set> getKnowledgeGaps(RuleNode plan) { + + assert plan instanceof AntSide; + + Set> existingOrGaps = new HashSet<>(); + + // TODO do we need to include the parent if we are not backward chaining? + Map> nodeCoverage = plan + .findAntecedentCoverage(((AntSide) plan).getAntecedentNeighbours()); + + // collect triple patterns that have an empty set + Set> collectedOrGaps, someGaps = new HashSet<>(); + for (Entry> entry : nodeCoverage.entrySet()) { + + collectedOrGaps = new HashSet<>(); + boolean foundNeighborWithoutGap = false; + for (RuleNode neighbor : entry.getValue()) { + if (!neighbor.getRule().getAntecedent().isEmpty()) { + // make sure neighbor has no knowledge gaps + + // knowledge engine specific code. We ignore meta knowledge interactions when + // looking for knowledge gaps, because they are very generic and make finding + // knowledge gaps nearly impossible. + boolean isMeta = isMetaKI(neighbor); + + //TODO what if the graph contains loops? + if (!isMeta && (someGaps = getKnowledgeGaps(neighbor)).isEmpty()) { + // found neighbor without knowledge gaps for the current triple, so current + // triple is covered. + foundNeighborWithoutGap = true; + break; + } + collectedOrGaps.addAll(someGaps); + } else + foundNeighborWithoutGap = true; + } + + if (!foundNeighborWithoutGap) { + // there is a gap here, either in the current node or in a neighbor. + + if (collectedOrGaps.isEmpty()) { + collectedOrGaps.add(new HashSet<>(Arrays.asList(entry.getKey()))); + } + + Set> newExistingOrGaps = new HashSet<>(); + if (existingOrGaps.isEmpty()) { + existingOrGaps.addAll(collectedOrGaps); + } else { + Set newGap; + for (Set existingOrGap : existingOrGaps) { + for (Set collectedOrGap : collectedOrGaps) { + newGap = new HashSet<>(); + newGap.addAll(existingOrGap); + newGap.addAll(collectedOrGap); + newExistingOrGaps.add(newGap); + } + } + existingOrGaps = newExistingOrGaps; + } + } + } + + return existingOrGaps; + } + + private boolean isMetaKI(RuleNode neighbor) { + + assert neighbor.getRule() instanceof Rule; + + BindingSetHandler bsh = ((Rule) neighbor.getRule()).getBindingSetHandler(); + + if (bsh instanceof ReactBindingSetHandler) { + ReactBindingSetHandler rbsh = (ReactBindingSetHandler) bsh; + return rbsh.getKnowledgeInteractionInfo().isMeta(); + } else if (bsh instanceof AnswerBindingSetHandler) { + AnswerBindingSetHandler absh = (AnswerBindingSetHandler) bsh; + return absh.getKnowledgeInteractionInfo().isMeta(); + } + + return false; + } + + } diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorImpl.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorImpl.java index 832e7525..74a5efe7 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorImpl.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorImpl.java @@ -10,9 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import eu.knowledge.engine.reasoner.ReasonerPlan; import eu.knowledge.engine.reasoner.Rule; -import eu.knowledge.engine.reasoner.api.TriplePattern; import eu.knowledge.engine.smartconnector.api.AnswerHandler; import eu.knowledge.engine.smartconnector.api.AnswerKnowledgeInteraction; import eu.knowledge.engine.smartconnector.api.AskKnowledgeInteraction; From cc9d6413e21ee4274b31261d61f680c938bdc69f Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Wed, 28 Aug 2024 12:06:27 +0200 Subject: [PATCH 10/64] added classes KnowledgeGap and KnowledgeGapSet to replace Set> --- .../smartconnector/api/KnowledgeGap.java | 31 ++++++++++++++ .../smartconnector/api/KnowledgeGapSet.java | 42 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGap.java create mode 100644 smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGapSet.java diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGap.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGap.java new file mode 100644 index 00000000..d1995f58 --- /dev/null +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGap.java @@ -0,0 +1,31 @@ +package eu.knowledge.engine.smartconnector.api; + +import java.util.HashSet; +import java.util.Set; + +import eu.knowledge.engine.reasoner.api.TriplePattern; + +/** + * A knowledge gap consists of a set of {@link TriplePattern}s that are missing and need ALL + * to be present in order to satisfy a ask/post interaction. + * + * Note that there can be multiple knowledge gaps in an ask or post interaction. + * They will be combined in a set of knowledge gaps. + */ +public class KnowledgeGap extends HashSet { + + private static final long serialVersionUID = 1L; + + public KnowledgeGap() { + } + + /** + * Construct a KnowledgeGap from a set of TriplePatterns tps + */ + public KnowledgeGap(Set tps) { + for (TriplePattern tp: tps) { + this.add(tp); + } + } + +} diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGapSet.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGapSet.java new file mode 100644 index 00000000..6b92af73 --- /dev/null +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGapSet.java @@ -0,0 +1,42 @@ +package eu.knowledge.engine.smartconnector.api; + +import java.util.HashSet; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A knowledge gap set contains one or more {@link KnowledgeGap}s in an OR + * fashion. This means that these gaps are present when executing an ask or post interaction. + * + * A knowledge gap consists of a set of {@link TriplePattern}s that are missing and need ALL + * to be present in order to satisfy the ask/post interaction. + * + * */ +public class KnowledgeGapSet extends HashSet { + + private static final Logger LOG = LoggerFactory.getLogger(KnowledgeGapSet.class); + + private static final long serialVersionUID = 1L; + + public KnowledgeGapSet() { + } + + /** + * Construct a KnowledgeGapSet from a single {@link KnowledgeGap} kg. + */ + public KnowledgeGapSet(KnowledgeGap kg) { + this.add(kg); + } + + /** + * Write this BindingSet to the standard output. + * This is convenient for debugging. + */ + public void write() { + for (KnowledgeGap b : this) { + System.out.println(b); + } + + } +} From 6fe8a455fbfb75a3e222607335bdc9f9807a44c2 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Wed, 28 Aug 2024 12:16:15 +0200 Subject: [PATCH 11/64] updated AskResult to except KnowledgeGapSet i.o. Set> --- .../eu/knowledge/engine/smartconnector/api/AskResult.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskResult.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskResult.java index e078762d..185367b5 100644 --- a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskResult.java +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskResult.java @@ -28,7 +28,7 @@ public class AskResult { */ private ReasonerPlan reasonerPlan; - private Set> knowledgeGaps; + private KnowledgeGapSet knowledgeGaps; private final Set exchangeInfos; @@ -41,7 +41,7 @@ public class AskResult { * value for every available variable in the * {@link GraphPattern}. */ - public AskResult(BindingSet someBindings, Set askExchangeInfos, ReasonerPlan aRootNode, Set> kGaps) { + public AskResult(BindingSet someBindings, Set askExchangeInfos, ReasonerPlan aRootNode, KnowledgeGapSet kGaps) { this.bindings = someBindings; this.exchangeInfos = askExchangeInfos; this.reasonerPlan = aRootNode; @@ -103,7 +103,7 @@ public ReasonerPlan getReasonerPlan() { return this.reasonerPlan; } - public Set> getKnowledgeGaps() { + public KnowledgeGapSet getKnowledgeGaps() { return this.knowledgeGaps; } From 098c011d6b8314bd893bc93f3236a4116c71da76 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Wed, 28 Aug 2024 12:18:08 +0200 Subject: [PATCH 12/64] updated getKnowledgeGaps to use KnowledgeGapSet and KnowledgeGap needs to be checked for correctness by @bnouwt --- .../impl/ReasonerProcessor.java | 77 ++++++++++++++++--- 1 file changed, 67 insertions(+), 10 deletions(-) diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java index f378ced9..9a91652b 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java @@ -44,6 +44,8 @@ import eu.knowledge.engine.smartconnector.api.ExchangeInfo.Initiator; import eu.knowledge.engine.smartconnector.api.ExchangeInfo.Status; import eu.knowledge.engine.smartconnector.api.GraphPattern; +import eu.knowledge.engine.smartconnector.api.KnowledgeGap; +import eu.knowledge.engine.smartconnector.api.KnowledgeGapSet; import eu.knowledge.engine.smartconnector.api.KnowledgeInteraction; import eu.knowledge.engine.smartconnector.api.PostExchangeInfo; import eu.knowledge.engine.smartconnector.api.PostKnowledgeInteraction; @@ -75,7 +77,8 @@ public class ReasonerProcessor extends SingleInteractionProcessor { private final Set postExchangeInfos; private Set additionalDomainKnowledge; private ReasonerPlan reasonerPlan; - private Set> knowledgeGaps; + //private Set> knowledgeGaps; + private KnowledgeGapSet knowledgeGaps; /** * These two bindingset handler are a bit dodgy. We need them to make the post @@ -172,9 +175,8 @@ public void planAskInteraction(MyKnowledgeInteractionInfo aAKI) { * 1: a plan, a non-empty binding set and no gaps => ask has a result * 2: a plan, an empty binding set and no gaps => ask has an empty result * 3: a plan, an empty binding set with gaps => ask has no result and gaps are found + * */ - - @Override public CompletableFuture executeAskInteraction(BindingSet someBindings) { @@ -183,7 +185,9 @@ public CompletableFuture executeAskInteraction(BindingSet someBinding continueReasoningBackward(translateBindingSetTo(someBindings)); return this.finalBindingSetFuture.thenApply((bs) -> { - this.knowledgeGaps = new HashSet<>(); + //this.knowledgeGaps = new HashSet<>(); + this.knowledgeGaps = new KnowledgeGapSet(); + // if the binding set is empty, check for knowledge gaps if (bs.isEmpty()) { this.knowledgeGaps = getKnowledgeGaps(this.reasonerPlan.getStartNode()); } @@ -620,11 +624,14 @@ public ReasonerPlan getReasonerPlan() { * {@code A} AND {@code B} need to be added to solve the * gap. */ - public Set> getKnowledgeGaps(RuleNode plan) { + //public Set> getKnowledgeGaps(RuleNode plan) { + public KnowledgeGapSet getKnowledgeGaps(RuleNode plan) { assert plan instanceof AntSide; Set> existingOrGaps = new HashSet<>(); + // new + KnowledgeGapSet existingOrGaps1 = new KnowledgeGapSet(); // TODO do we need to include the parent if we are not backward chaining? Map> nodeCoverage = plan @@ -632,13 +639,25 @@ public Set> getKnowledgeGaps(RuleNode plan) { // collect triple patterns that have an empty set Set> collectedOrGaps, someGaps = new HashSet<>(); + + //new + KnowledgeGapSet collectedOrGaps1, someGaps1 = new KnowledgeGapSet(); + for (Entry> entry : nodeCoverage.entrySet()) { + LOG.info("Entry key is {}", entry.getKey()); + LOG.info("Entry value is {}", entry.getValue()); + collectedOrGaps = new HashSet<>(); + // new + collectedOrGaps1 = new KnowledgeGapSet(); boolean foundNeighborWithoutGap = false; for (RuleNode neighbor : entry.getValue()) { + LOG.info("Neighbor is {}", neighbor); + if (!neighbor.getRule().getAntecedent().isEmpty()) { // make sure neighbor has no knowledge gaps + LOG.info("Neighbor has antecedents, so check if the neighbor has gaps"); // knowledge engine specific code. We ignore meta knowledge interactions when // looking for knowledge gaps, because they are very generic and make finding @@ -646,16 +665,21 @@ public Set> getKnowledgeGaps(RuleNode plan) { boolean isMeta = isMetaKI(neighbor); //TODO what if the graph contains loops? - if (!isMeta && (someGaps = getKnowledgeGaps(neighbor)).isEmpty()) { - // found neighbor without knowledge gaps for the current triple, so current - // triple is covered. + //if (!isMeta && (someGaps = getKnowledgeGaps(neighbor)).isEmpty()) { + if (!isMeta && (someGaps1 = getKnowledgeGaps(neighbor)).isEmpty()) { + // found neighbor without knowledge gaps for the current triple, so current triple is covered. + LOG.info("Neighbor has no gaps"); foundNeighborWithoutGap = true; break; } + LOG.info("Neighbor has someGaps {}", someGaps); collectedOrGaps.addAll(someGaps); + //new + collectedOrGaps1.addAll(someGaps1); } else foundNeighborWithoutGap = true; } + LOG.info("Found a neighbor without gaps is {}", foundNeighborWithoutGap); if (!foundNeighborWithoutGap) { // there is a gap here, either in the current node or in a neighbor. @@ -663,10 +687,19 @@ public Set> getKnowledgeGaps(RuleNode plan) { if (collectedOrGaps.isEmpty()) { collectedOrGaps.add(new HashSet<>(Arrays.asList(entry.getKey()))); } + LOG.info("CollectedOrGaps is {}", collectedOrGaps); + + if (collectedOrGaps1.isEmpty()) { + KnowledgeGap kg = new KnowledgeGap(); + kg.add(entry.getKey()); + collectedOrGaps1.add(kg); + } + LOG.info("CollectedOrGaps1 is {}", collectedOrGaps1); Set> newExistingOrGaps = new HashSet<>(); if (existingOrGaps.isEmpty()) { existingOrGaps.addAll(collectedOrGaps); + LOG.info("Added collectedOrGaps to existingOrGaps"); } else { Set newGap; for (Set existingOrGap : existingOrGaps) { @@ -674,15 +707,39 @@ public Set> getKnowledgeGaps(RuleNode plan) { newGap = new HashSet<>(); newGap.addAll(existingOrGap); newGap.addAll(collectedOrGap); + LOG.info("Found newGap {}", newGap); newExistingOrGaps.add(newGap); } } existingOrGaps = newExistingOrGaps; } + + //new + KnowledgeGapSet newExistingOrGaps1 = new KnowledgeGapSet(); + if (existingOrGaps1.isEmpty()) { + existingOrGaps1.addAll(collectedOrGaps1); + LOG.info("Added collectedOrGaps1 to existingOrGaps1"); + } else { + KnowledgeGap newGap1; + for (KnowledgeGap existingOrGap1 : existingOrGaps1) { + for (KnowledgeGap collectedOrGap1 : collectedOrGaps1) { + newGap1 = new KnowledgeGap(); + newGap1.addAll(existingOrGap1); + newGap1.addAll(collectedOrGap1); + LOG.info("Found newGap1 {}", newGap1); + newExistingOrGaps1.add(newGap1); + } + } + existingOrGaps1 = newExistingOrGaps1; + } + // end new + } } - - return existingOrGaps; + LOG.info("Found existingOrGaps {}", existingOrGaps); + LOG.info("Found existingOrGaps1 {}", existingOrGaps1); + //return existingOrGaps; + return existingOrGaps1; } private boolean isMetaKI(RuleNode neighbor) { From 9991c20e6a654698f80482dc66c641647925c4a7 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Mon, 2 Sep 2024 14:14:13 +0200 Subject: [PATCH 13/64] Replaced KnowledgeGapSet class with Set --- .../engine/smartconnector/api/AskResult.java | 6 +++--- .../smartconnector/impl/ReasonerProcessor.java | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskResult.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskResult.java index 185367b5..28e4d196 100644 --- a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskResult.java +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskResult.java @@ -28,7 +28,7 @@ public class AskResult { */ private ReasonerPlan reasonerPlan; - private KnowledgeGapSet knowledgeGaps; + private Set knowledgeGaps; private final Set exchangeInfos; @@ -41,7 +41,7 @@ public class AskResult { * value for every available variable in the * {@link GraphPattern}. */ - public AskResult(BindingSet someBindings, Set askExchangeInfos, ReasonerPlan aRootNode, KnowledgeGapSet kGaps) { + public AskResult(BindingSet someBindings, Set askExchangeInfos, ReasonerPlan aRootNode, Set kGaps) { this.bindings = someBindings; this.exchangeInfos = askExchangeInfos; this.reasonerPlan = aRootNode; @@ -103,7 +103,7 @@ public ReasonerPlan getReasonerPlan() { return this.reasonerPlan; } - public KnowledgeGapSet getKnowledgeGaps() { + public Set getKnowledgeGaps() { return this.knowledgeGaps; } diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java index 9a91652b..7ad25e2d 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java @@ -77,8 +77,8 @@ public class ReasonerProcessor extends SingleInteractionProcessor { private final Set postExchangeInfos; private Set additionalDomainKnowledge; private ReasonerPlan reasonerPlan; - //private Set> knowledgeGaps; - private KnowledgeGapSet knowledgeGaps; + private Set knowledgeGaps; + //private KnowledgeGapSet knowledgeGaps; /** * These two bindingset handler are a bit dodgy. We need them to make the post @@ -185,8 +185,8 @@ public CompletableFuture executeAskInteraction(BindingSet someBinding continueReasoningBackward(translateBindingSetTo(someBindings)); return this.finalBindingSetFuture.thenApply((bs) -> { - //this.knowledgeGaps = new HashSet<>(); - this.knowledgeGaps = new KnowledgeGapSet(); + this.knowledgeGaps = new HashSet(); + //this.knowledgeGaps = new KnowledgeGapSet(); // if the binding set is empty, check for knowledge gaps if (bs.isEmpty()) { this.knowledgeGaps = getKnowledgeGaps(this.reasonerPlan.getStartNode()); @@ -625,13 +625,13 @@ public ReasonerPlan getReasonerPlan() { * gap. */ //public Set> getKnowledgeGaps(RuleNode plan) { - public KnowledgeGapSet getKnowledgeGaps(RuleNode plan) { + public Set getKnowledgeGaps(RuleNode plan) { assert plan instanceof AntSide; Set> existingOrGaps = new HashSet<>(); // new - KnowledgeGapSet existingOrGaps1 = new KnowledgeGapSet(); + Set existingOrGaps1 = new HashSet(); // TODO do we need to include the parent if we are not backward chaining? Map> nodeCoverage = plan @@ -641,7 +641,7 @@ public KnowledgeGapSet getKnowledgeGaps(RuleNode plan) { Set> collectedOrGaps, someGaps = new HashSet<>(); //new - KnowledgeGapSet collectedOrGaps1, someGaps1 = new KnowledgeGapSet(); + Set collectedOrGaps1, someGaps1 = new HashSet(); for (Entry> entry : nodeCoverage.entrySet()) { @@ -650,7 +650,7 @@ public KnowledgeGapSet getKnowledgeGaps(RuleNode plan) { collectedOrGaps = new HashSet<>(); // new - collectedOrGaps1 = new KnowledgeGapSet(); + collectedOrGaps1 = new HashSet(); boolean foundNeighborWithoutGap = false; for (RuleNode neighbor : entry.getValue()) { LOG.info("Neighbor is {}", neighbor); @@ -715,7 +715,7 @@ public KnowledgeGapSet getKnowledgeGaps(RuleNode plan) { } //new - KnowledgeGapSet newExistingOrGaps1 = new KnowledgeGapSet(); + Set newExistingOrGaps1 = new HashSet(); if (existingOrGaps1.isEmpty()) { existingOrGaps1.addAll(collectedOrGaps1); LOG.info("Added collectedOrGaps1 to existingOrGaps1"); From 48ef2a577ee324b7ffd4b8f7e746ca9d5064bba2 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Tue, 3 Sep 2024 13:52:41 +0200 Subject: [PATCH 14/64] finalized getKnowledgeGaps method so java API returns them --- .../smartconnector/api/KnowledgeGap.java | 12 +-- .../smartconnector/api/KnowledgeGapSet.java | 42 ---------- .../api/impl/ProactiveApiServiceImpl.java | 6 +- .../impl/ReasonerProcessor.java | 84 +++++-------------- 4 files changed, 32 insertions(+), 112 deletions(-) delete mode 100644 smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGapSet.java diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGap.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGap.java index d1995f58..0b498b62 100644 --- a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGap.java +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGap.java @@ -7,11 +7,13 @@ /** * A knowledge gap consists of a set of {@link TriplePattern}s that are missing and need ALL - * to be present in order to satisfy a ask/post interaction. + * to be present in order to satisfy an ask/post interaction. * * Note that there can be multiple knowledge gaps in an ask or post interaction. - * They will be combined in a set of knowledge gaps. - */ + * They will be combined in a Sets that contains one or more + * {@link KnowledgeGap}s in an OR fashion. + * + * */ public class KnowledgeGap extends HashSet { private static final long serialVersionUID = 1L; @@ -23,9 +25,7 @@ public KnowledgeGap() { * Construct a KnowledgeGap from a set of TriplePatterns tps */ public KnowledgeGap(Set tps) { - for (TriplePattern tp: tps) { - this.add(tp); - } + this.addAll(tps); } } diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGapSet.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGapSet.java deleted file mode 100644 index 6b92af73..00000000 --- a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeGapSet.java +++ /dev/null @@ -1,42 +0,0 @@ -package eu.knowledge.engine.smartconnector.api; - -import java.util.HashSet; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A knowledge gap set contains one or more {@link KnowledgeGap}s in an OR - * fashion. This means that these gaps are present when executing an ask or post interaction. - * - * A knowledge gap consists of a set of {@link TriplePattern}s that are missing and need ALL - * to be present in order to satisfy the ask/post interaction. - * - * */ -public class KnowledgeGapSet extends HashSet { - - private static final Logger LOG = LoggerFactory.getLogger(KnowledgeGapSet.class); - - private static final long serialVersionUID = 1L; - - public KnowledgeGapSet() { - } - - /** - * Construct a KnowledgeGapSet from a single {@link KnowledgeGap} kg. - */ - public KnowledgeGapSet(KnowledgeGap kg) { - this.add(kg); - } - - /** - * Write this BindingSet to the standard output. - * This is convenient for debugging. - */ - public void write() { - for (KnowledgeGap b : this) { - System.out.println(b); - } - - } -} diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java index 819a0c4f..0296374a 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java @@ -57,7 +57,7 @@ public void scAskWithGapsPost( @Parameter(description = "The keys bindings are allowed to be incomplete, but they must correspond to the binding keys that were defined in the knowledge interaction.", required = true) @NotNull @Valid JsonNode recipientAndBindingSet, @Suspended final AsyncResponse asyncResponse, @Context SecurityContext securityContext) { - LOG.info("scAskWithGapsPost called for KB {} and KI {} - {}", knowledgeBaseId, knowledgeInteractionId, + LOG.debug("scAskWithGapsPost called for KB {} and KI {} - {}", knowledgeBaseId, knowledgeInteractionId, recipientAndBindingSet); RecipientAndBindingSet recipientAndBindingSetObject; @@ -177,7 +177,7 @@ public void scAskPost( @Parameter(description = "The keys bindings are allowed to be incomplete, but they must correspond to the binding keys that were defined in the knowledge interaction.", required = true) @NotNull @Valid JsonNode recipientAndBindingSet, @Suspended final AsyncResponse asyncResponse, @Context SecurityContext securityContext) { - LOG.info("scAskPost called for KB {} and KI {} - {}", knowledgeBaseId, knowledgeInteractionId, + LOG.debug("scAskPost called for KB {} and KI {} - {}", knowledgeBaseId, knowledgeInteractionId, recipientAndBindingSet); RecipientAndBindingSet recipientAndBindingSetObject; @@ -253,7 +253,7 @@ public void scAskPost( askFuture.thenAccept(askResult -> { - LOG.info("AskResult received, resuming async response: {}", askResult); + LOG.debug("AskResult received, resuming async response: {}", askResult); List infos = askResult.getExchangeInfoPerKnowledgeBase().stream() .map(aei -> new AskExchangeInfo().bindingSet(this.bindingSetToList(aei.getBindings())) .knowledgeBaseId(aei.getKnowledgeBaseId().toString()) diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java index 7ad25e2d..16b021a2 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java @@ -45,7 +45,6 @@ import eu.knowledge.engine.smartconnector.api.ExchangeInfo.Status; import eu.knowledge.engine.smartconnector.api.GraphPattern; import eu.knowledge.engine.smartconnector.api.KnowledgeGap; -import eu.knowledge.engine.smartconnector.api.KnowledgeGapSet; import eu.knowledge.engine.smartconnector.api.KnowledgeInteraction; import eu.knowledge.engine.smartconnector.api.PostExchangeInfo; import eu.knowledge.engine.smartconnector.api.PostKnowledgeInteraction; @@ -186,8 +185,8 @@ public CompletableFuture executeAskInteraction(BindingSet someBinding return this.finalBindingSetFuture.thenApply((bs) -> { this.knowledgeGaps = new HashSet(); - //this.knowledgeGaps = new KnowledgeGapSet(); - // if the binding set is empty, check for knowledge gaps + // TODO: make the calculation of knowledge gaps configurable. + // For now, if the binding set is empty, check for knowledge gaps if (bs.isEmpty()) { this.knowledgeGaps = getKnowledgeGaps(this.reasonerPlan.getStartNode()); } @@ -629,35 +628,28 @@ public Set getKnowledgeGaps(RuleNode plan) { assert plan instanceof AntSide; - Set> existingOrGaps = new HashSet<>(); - // new - Set existingOrGaps1 = new HashSet(); + Set existingOrGaps = new HashSet(); // TODO do we need to include the parent if we are not backward chaining? Map> nodeCoverage = plan .findAntecedentCoverage(((AntSide) plan).getAntecedentNeighbours()); // collect triple patterns that have an empty set - Set> collectedOrGaps, someGaps = new HashSet<>(); - - //new - Set collectedOrGaps1, someGaps1 = new HashSet(); + Set collectedOrGaps, someGaps = new HashSet(); for (Entry> entry : nodeCoverage.entrySet()) { - LOG.info("Entry key is {}", entry.getKey()); - LOG.info("Entry value is {}", entry.getValue()); + LOG.debug("Entry key is {}", entry.getKey()); + LOG.debug("Entry value is {}", entry.getValue()); - collectedOrGaps = new HashSet<>(); - // new - collectedOrGaps1 = new HashSet(); + collectedOrGaps = new HashSet(); boolean foundNeighborWithoutGap = false; for (RuleNode neighbor : entry.getValue()) { - LOG.info("Neighbor is {}", neighbor); + LOG.debug("Neighbor is {}", neighbor); if (!neighbor.getRule().getAntecedent().isEmpty()) { // make sure neighbor has no knowledge gaps - LOG.info("Neighbor has antecedents, so check if the neighbor has gaps"); + LOG.debug("Neighbor has antecedents, so check if the neighbor has gaps"); // knowledge engine specific code. We ignore meta knowledge interactions when // looking for knowledge gaps, because they are very generic and make finding @@ -665,81 +657,51 @@ public Set getKnowledgeGaps(RuleNode plan) { boolean isMeta = isMetaKI(neighbor); //TODO what if the graph contains loops? - //if (!isMeta && (someGaps = getKnowledgeGaps(neighbor)).isEmpty()) { - if (!isMeta && (someGaps1 = getKnowledgeGaps(neighbor)).isEmpty()) { + if (!isMeta && (someGaps = getKnowledgeGaps(neighbor)).isEmpty()) { // found neighbor without knowledge gaps for the current triple, so current triple is covered. - LOG.info("Neighbor has no gaps"); + LOG.debug("Neighbor has no gaps"); foundNeighborWithoutGap = true; break; } - LOG.info("Neighbor has someGaps {}", someGaps); + LOG.debug("Neighbor has someGaps {}", someGaps); collectedOrGaps.addAll(someGaps); - //new - collectedOrGaps1.addAll(someGaps1); } else foundNeighborWithoutGap = true; } - LOG.info("Found a neighbor without gaps is {}", foundNeighborWithoutGap); + LOG.debug("Found a neighbor without gaps is {}", foundNeighborWithoutGap); if (!foundNeighborWithoutGap) { // there is a gap here, either in the current node or in a neighbor. if (collectedOrGaps.isEmpty()) { - collectedOrGaps.add(new HashSet<>(Arrays.asList(entry.getKey()))); - } - LOG.info("CollectedOrGaps is {}", collectedOrGaps); - - if (collectedOrGaps1.isEmpty()) { KnowledgeGap kg = new KnowledgeGap(); kg.add(entry.getKey()); - collectedOrGaps1.add(kg); + collectedOrGaps.add(kg); } - LOG.info("CollectedOrGaps1 is {}", collectedOrGaps1); + LOG.debug("CollectedOrGaps is {}", collectedOrGaps); - Set> newExistingOrGaps = new HashSet<>(); + Set newExistingOrGaps = new HashSet(); if (existingOrGaps.isEmpty()) { existingOrGaps.addAll(collectedOrGaps); - LOG.info("Added collectedOrGaps to existingOrGaps"); + LOG.debug("Added collectedOrGaps to existingOrGaps"); } else { - Set newGap; - for (Set existingOrGap : existingOrGaps) { - for (Set collectedOrGap : collectedOrGaps) { - newGap = new HashSet<>(); + KnowledgeGap newGap; + for (KnowledgeGap existingOrGap : existingOrGaps) { + for (KnowledgeGap collectedOrGap : collectedOrGaps) { + newGap = new KnowledgeGap(); newGap.addAll(existingOrGap); newGap.addAll(collectedOrGap); - LOG.info("Found newGap {}", newGap); + LOG.debug("Found newGap {}", newGap); newExistingOrGaps.add(newGap); } } existingOrGaps = newExistingOrGaps; } - - //new - Set newExistingOrGaps1 = new HashSet(); - if (existingOrGaps1.isEmpty()) { - existingOrGaps1.addAll(collectedOrGaps1); - LOG.info("Added collectedOrGaps1 to existingOrGaps1"); - } else { - KnowledgeGap newGap1; - for (KnowledgeGap existingOrGap1 : existingOrGaps1) { - for (KnowledgeGap collectedOrGap1 : collectedOrGaps1) { - newGap1 = new KnowledgeGap(); - newGap1.addAll(existingOrGap1); - newGap1.addAll(collectedOrGap1); - LOG.info("Found newGap1 {}", newGap1); - newExistingOrGaps1.add(newGap1); - } - } - existingOrGaps1 = newExistingOrGaps1; - } - // end new } } LOG.info("Found existingOrGaps {}", existingOrGaps); - LOG.info("Found existingOrGaps1 {}", existingOrGaps1); - //return existingOrGaps; - return existingOrGaps1; + return existingOrGaps; } private boolean isMetaKI(RuleNode neighbor) { From 98251e5f7aee5000b4a2e6d80dc82d4fde8700c5 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Thu, 5 Sep 2024 14:35:08 +0200 Subject: [PATCH 15/64] deleted a comment --- .../knowledge/engine/smartconnector/impl/ReasonerProcessor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java index 16b021a2..f526d013 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java @@ -77,7 +77,6 @@ public class ReasonerProcessor extends SingleInteractionProcessor { private Set additionalDomainKnowledge; private ReasonerPlan reasonerPlan; private Set knowledgeGaps; - //private KnowledgeGapSet knowledgeGaps; /** * These two bindingset handler are a bit dodgy. We need them to make the post From 0055b1f2f437c40793b020d65a4554463949d4c3 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Thu, 5 Sep 2024 14:38:25 +0200 Subject: [PATCH 16/64] updated the openapi-sc.yaml to include knowledge gaps in ask --- .../src/main/resources/openapi-sc.yaml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml index d93b0153..04fb5b47 100644 --- a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml +++ b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml @@ -678,7 +678,7 @@ components: type: string AskResult: type: object - required: [bindingSet, exchangeInfo] + required: [bindingSet, exchangeInfo, knowledgeGaps] properties: bindingSet: $ref: '#/components/schemas/BindingSet' @@ -686,6 +686,10 @@ components: type: array items: $ref: '#/components/schemas/AskExchangeInfo' + knowledgeGaps: + type: array + items: + $ref: '#/components/schemas/KnowledgeGap' PostResult: type: object required: [resultBindingSet, exchangeInfo] @@ -735,6 +739,11 @@ components: $ref: '#/components/schemas/BindingSet' resultBindingSet: $ref: '#/components/schemas/BindingSet' + KnowledgeGap: + type: array + items: + nullable: false # Enforced manually, but kept here nonetheless. + type: string HandleRequest: type: object properties: From d67bda1870702d4efa39e0c2f755a7cce26aa564 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Thu, 5 Sep 2024 14:40:26 +0200 Subject: [PATCH 17/64] added returning of knowledge gaps in askwithgaps route --- .../api/impl/ProactiveApiServiceImpl.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java index 0296374a..a2c1a0c3 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -24,6 +25,7 @@ import eu.knowledge.engine.rest.model.ResponseMessage; import eu.knowledge.engine.smartconnector.api.BindingSet; import eu.knowledge.engine.smartconnector.api.ExchangeInfo.Initiator; +import eu.knowledge.engine.smartconnector.api.KnowledgeGap; import io.swagger.v3.oas.annotations.Parameter; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; @@ -133,7 +135,7 @@ public void scAskWithGapsPost( askFuture.thenAccept(askResult -> { - LOG.info("AskResult received, resuming async response: {}", askResult); + LOG.debug("AskResult received, resuming async response: {}", askResult); List infos = askResult.getExchangeInfoPerKnowledgeBase().stream() .map(aei -> new AskExchangeInfo().bindingSet(this.bindingSetToList(aei.getBindings())) .knowledgeBaseId(aei.getKnowledgeBaseId().toString()) @@ -145,7 +147,10 @@ public void scAskWithGapsPost( .failedMessage(aei.getFailedMessage())) .collect(Collectors.toList()); + LOG.info("Bindings in result is {}", askResult.getBindings()); + LOG.info("Knowledge gaps in result is {}", askResult.getKnowledgeGaps()); AskResult ar = new AskResult().bindingSet(this.bindingSetToList(askResult.getBindings())) + .knowledgeGaps(this.knowledgeGapsToList(askResult.getKnowledgeGaps())) .exchangeInfo(infos); asyncResponse.resume(Response.status(Status.OK).entity(ar).build()); @@ -166,6 +171,18 @@ public void scAskWithGapsPost( } } + private List> knowledgeGapsToList(Set knowledgeGaps) { + List> listKnowledgeGaps = new ArrayList>(knowledgeGaps.size()); + knowledgeGaps.forEach((kg) -> { + List listKnowledgeGap = new ArrayList(); + kg.forEach((tp) -> { + listKnowledgeGap.add(tp.toString()); + }); + listKnowledgeGaps.add(listKnowledgeGap); + }); + return listKnowledgeGaps; + } + @POST @Path("/ask") @Consumes({ "application/json; charset=UTF-8" }) From 99ede29dabd2740fd49072e2460fdd77a868d2bc Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Thu, 5 Sep 2024 14:45:36 +0200 Subject: [PATCH 18/64] updated /ask route to deal with gaps, made previous /ask /askold --- .../engine/rest/api/impl/ProactiveApiServiceImpl.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java index a2c1a0c3..223ce354 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java @@ -49,17 +49,17 @@ public class ProactiveApiServiceImpl { private RestKnowledgeBaseManager manager = RestKnowledgeBaseManager.newInstance(); @POST - @Path("/askwithgaps") + @Path("/ask") @Consumes({ "application/json; charset=UTF-8" }) @Produces({ "application/json; charset=UTF-8", "text/plain; charset=UTF-8" }) - public void scAskWithGapsPost( + public void scAskPost( @Parameter(description = "The Knowledge Base Id for which to execute the ask.", required = true) @HeaderParam("Knowledge-Base-Id") String knowledgeBaseId, @Parameter(description = "The Ask Knowledge Interaction Id to execute.", required = true) @HeaderParam("Knowledge-Interaction-Id") String knowledgeInteractionId, @Parameter(description = "The keys bindings are allowed to be incomplete, but they must correspond to the binding keys that were defined in the knowledge interaction.", required = true) @NotNull @Valid JsonNode recipientAndBindingSet, @Suspended final AsyncResponse asyncResponse, @Context SecurityContext securityContext) { - LOG.debug("scAskWithGapsPost called for KB {} and KI {} - {}", knowledgeBaseId, knowledgeInteractionId, + LOG.debug("scAskPost called for KB {} and KI {} - {}", knowledgeBaseId, knowledgeInteractionId, recipientAndBindingSet); RecipientAndBindingSet recipientAndBindingSetObject; @@ -184,10 +184,10 @@ private List> knowledgeGapsToList(Set knowledgeGaps) } @POST - @Path("/ask") + @Path("/askold") @Consumes({ "application/json; charset=UTF-8" }) @Produces({ "application/json; charset=UTF-8", "text/plain; charset=UTF-8" }) - public void scAskPost( + public void scAskOldPost( @Parameter(description = "The Knowledge Base Id for which to execute the ask.", required = true) @HeaderParam("Knowledge-Base-Id") String knowledgeBaseId, @Parameter(description = "The Ask Knowledge Interaction Id to execute.", required = true) @HeaderParam("Knowledge-Interaction-Id") String knowledgeInteractionId, From 5fe5d9726e687f83da98be3922782f1befa38668 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sun, 22 Sep 2024 13:08:39 +0200 Subject: [PATCH 19/64] deleted non-used old askPost --- .../api/impl/ProactiveApiServiceImpl.java | 144 ++---------------- 1 file changed, 12 insertions(+), 132 deletions(-) diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java index 223ce354..f3b3d59a 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java @@ -171,138 +171,6 @@ public void scAskPost( } } - private List> knowledgeGapsToList(Set knowledgeGaps) { - List> listKnowledgeGaps = new ArrayList>(knowledgeGaps.size()); - knowledgeGaps.forEach((kg) -> { - List listKnowledgeGap = new ArrayList(); - kg.forEach((tp) -> { - listKnowledgeGap.add(tp.toString()); - }); - listKnowledgeGaps.add(listKnowledgeGap); - }); - return listKnowledgeGaps; - } - - @POST - @Path("/askold") - @Consumes({ "application/json; charset=UTF-8" }) - @Produces({ "application/json; charset=UTF-8", "text/plain; charset=UTF-8" }) - public void scAskOldPost( - @Parameter(description = "The Knowledge Base Id for which to execute the ask.", required = true) @HeaderParam("Knowledge-Base-Id") String knowledgeBaseId, - @Parameter(description = "The Ask Knowledge Interaction Id to execute.", required = true) @HeaderParam("Knowledge-Interaction-Id") String knowledgeInteractionId, - - @Parameter(description = "The keys bindings are allowed to be incomplete, but they must correspond to the binding keys that were defined in the knowledge interaction.", required = true) @NotNull @Valid JsonNode recipientAndBindingSet, - @Suspended final AsyncResponse asyncResponse, @Context SecurityContext securityContext) { - - LOG.debug("scAskPost called for KB {} and KI {} - {}", knowledgeBaseId, knowledgeInteractionId, - recipientAndBindingSet); - - RecipientAndBindingSet recipientAndBindingSetObject; - try { - recipientAndBindingSetObject = new RecipientAndBindingSet(recipientAndBindingSet); - } catch (IllegalArgumentException e) { - LOG.debug("", e); - var response = new ResponseMessage(); - response.setMessageType("error"); - response.setMessage(e.getMessage()); - asyncResponse.resume(Response.status(Status.BAD_REQUEST).entity(response).build()); - return; - } - - if (knowledgeBaseId == null || knowledgeInteractionId == null) { - var response = new ResponseMessage(); - response.setMessageType("error"); - response.setMessage("Both Knowledge-Base-Id and Knowledge-Interaction-Id headers should be non-null."); - asyncResponse.resume(Response.status(Status.BAD_REQUEST).entity(response).build()); - return; - } - - var kb = this.manager.getKB(knowledgeBaseId); - if (kb == null) { - if (this.manager.hasSuspendedKB(knowledgeBaseId)) { - this.manager.removeSuspendedKB(knowledgeBaseId); - var response = new ResponseMessage(); - response.setMessageType("error"); - response.setMessage( - "This knowledge base has been suspended due to inactivity. Please reregister the knowledge base and its knowledge interactions."); - asyncResponse.resume(Response.status(Status.NOT_FOUND).entity(response).build()); - return; - } else { - var response = new ResponseMessage(); - response.setMessageType("error"); - response.setMessage("Smart connector not found, because its ID is unknown."); - asyncResponse.resume(Response.status(Status.NOT_FOUND).entity(response).build()); - return; - } - } - - try { - new URI(knowledgeInteractionId); - } catch (URISyntaxException e) { - var response = new ResponseMessage(); - response.setMessageType("error"); - response.setMessage("Knowledge interaction not found, because its ID must be a valid URI."); - asyncResponse.resume(Response.status(Status.BAD_REQUEST).entity(response).build()); - return; - } - - if (!kb.hasKnowledgeInteraction(knowledgeInteractionId)) { - var response = new ResponseMessage(); - response.setMessageType("error"); - response.setMessage("Knowledge Interaction not found, because its ID is unknown."); - asyncResponse.resume(Response.status(Status.NOT_FOUND).entity(response).build()); - return; - } - - KnowledgeInteractionWithId ki = kb.getKnowledgeInteraction(knowledgeInteractionId); - if (!ki.getKnowledgeInteractionType().equals("AskKnowledgeInteraction")) { - var response = new ResponseMessage(); - response.setMessageType("error"); - response.setMessage("Given Knowledge Interaction ID should have type AskKnowledgeInteraction and not " - + ki.getKnowledgeInteractionType() + "."); - asyncResponse.resume(Response.status(Status.BAD_REQUEST).entity(response).build()); - return; - } - - try { - var askFuture = kb.ask(knowledgeInteractionId, recipientAndBindingSetObject.recipient, - recipientAndBindingSetObject.bindingSet); - - askFuture.thenAccept(askResult -> { - - LOG.debug("AskResult received, resuming async response: {}", askResult); - List infos = askResult.getExchangeInfoPerKnowledgeBase().stream() - .map(aei -> new AskExchangeInfo().bindingSet(this.bindingSetToList(aei.getBindings())) - .knowledgeBaseId(aei.getKnowledgeBaseId().toString()) - .knowledgeInteractionId(aei.getKnowledgeInteractionId().toString()) - .exchangeStart(Date.from(aei.getExchangeStart())) - .exchangeStart(Date.from(aei.getExchangeStart())) - .initiator(toInitiatorEnumAsk(aei.getInitiator())) - .exchangeEnd(Date.from(aei.getExchangeEnd())).status(aei.getStatus().toString()) - .failedMessage(aei.getFailedMessage())) - .collect(Collectors.toList()); - - AskResult ar = new AskResult().bindingSet(this.bindingSetToList(askResult.getBindings())) - .exchangeInfo(infos); - - asyncResponse.resume(Response.status(Status.OK).entity(ar).build()); - }); - - } catch (URISyntaxException | InterruptedException | ExecutionException e) { - LOG.trace("", e); - var response = new ResponseMessage(); - response.setMessageType("error"); - response.setMessage("Something went wrong while sending a POST or while waiting on the REACT."); - asyncResponse.resume(Response.status(Status.INTERNAL_SERVER_ERROR).entity(response).build()); - } catch (IllegalArgumentException e) { - LOG.trace("", e); - var response = new ResponseMessage(); - response.setMessageType("error"); - response.setMessage(e.getMessage()); - asyncResponse.resume(Response.status(Status.BAD_REQUEST).entity(response).build()); - } - } - private List> bindingSetToList(BindingSet bindings) { var listBindings = new ArrayList>(bindings.size()); bindings.forEach((binding) -> { @@ -315,6 +183,18 @@ private List> bindingSetToList(BindingSet bindings) { return listBindings; } + private List> knowledgeGapsToList(Set knowledgeGaps) { + List> listKnowledgeGaps = new ArrayList>(knowledgeGaps.size()); + knowledgeGaps.forEach((kg) -> { + List listKnowledgeGap = new ArrayList(); + kg.forEach((tp) -> { + listKnowledgeGap.add(tp.toString()); + }); + listKnowledgeGaps.add(listKnowledgeGap); + }); + return listKnowledgeGaps; + } + private AskExchangeInfo.InitiatorEnum toInitiatorEnumAsk(Initiator initiator) { switch (initiator) { case KNOWLEDGEBASE: From 94e4cf64af3bd01d2991446faea97ec34878b8d4 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Tue, 24 Sep 2024 11:31:46 +0200 Subject: [PATCH 20/64] fixed knowledge gap to contain correct triples --- .../knowledge/engine/reasoner/api/TriplePattern.java | 5 +++++ .../engine/rest/api/impl/ProactiveApiServiceImpl.java | 5 ++++- .../engine/smartconnector/impl/ReasonerProcessor.java | 11 +---------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TriplePattern.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TriplePattern.java index ca6c2afc..1a55d3ec 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TriplePattern.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TriplePattern.java @@ -176,4 +176,9 @@ public boolean equals(Object obj) { return true; } + public Triple asTriple() { + return Triple.create(this.getSubject(), this.getPredicate(), this.getObject()); + //return null; + } + } diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java index f3b3d59a..d7e6f11f 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java @@ -11,6 +11,8 @@ import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import org.apache.jena.sparql.graph.PrefixMappingZero; +import org.apache.jena.sparql.util.FmtUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -188,7 +190,8 @@ private List> knowledgeGapsToList(Set knowledgeGaps) knowledgeGaps.forEach((kg) -> { List listKnowledgeGap = new ArrayList(); kg.forEach((tp) -> { - listKnowledgeGap.add(tp.toString()); + String tpString = FmtUtils.stringForTriple(tp.asTriple(), new PrefixMappingZero()); + listKnowledgeGap.add(tpString); }); listKnowledgeGaps.add(listKnowledgeGap); }); diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java index f526d013..1ec7cd88 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java @@ -601,18 +601,10 @@ public ReasonerPlan getReasonerPlan() { } /** - * Returns the knowledge gap of this reasoning node. A knowledge gap is a subset + * Returns the knowledge gaps of this reasoning node. A knowledge gap is a subset * of this node's antecedent triple patterns that do not match any neighbor that * has no knowledge gaps. * - * Currently, this method does not show how the knowledge gaps influence each - * other. Some knowledge gaps might have an {@code or}-relation (namely those - * that occur on the same triple) and some might have {@code and}-relations - * (i.e. those that do not occur on the same triple). This information is - * important if you want to know how to solve the gaps because 2 gaps related by - * {@code or} do not both need to be solved, but only one of them. While 2 gaps - * related by {@code and} both need to be solved to solve the gap. - * * @return returns all triples that have no matching nodes (and for which there * are no alternatives). Note that it returns a set of sets. Where every * set in this set represents a single way to resolve the knowledge gaps @@ -622,7 +614,6 @@ public ReasonerPlan getReasonerPlan() { * {@code A} AND {@code B} need to be added to solve the * gap. */ - //public Set> getKnowledgeGaps(RuleNode plan) { public Set getKnowledgeGaps(RuleNode plan) { assert plan instanceof AntSide; From 4afa098e64c35f8caea08fafd47497fda5f044c8 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sun, 29 Sep 2024 09:17:53 +0200 Subject: [PATCH 21/64] added boolean knowledgeGapsEnabled to KnowledgeInteractionBase --- .../src/main/resources/openapi-sc.yaml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml index 04fb5b47..f99dfc18 100644 --- a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml +++ b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml @@ -216,6 +216,18 @@ paths: prefixes: rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#" ex: "http://example.org/" + ASK with knowledge gaps enabled: + value: + knowledgeInteractionType: AskKnowledgeInteraction + graphPattern: "?a ?b ." + knowledgeGapsEnabled: "true" + description: + The knowledge gap enabled option is disabled by default. If enabled, + each activation of the knowledge interaction will provide a set of + knowledge gaps as part of the result. If this set is empty, the + binding set of the result contains the answer. If not empty, + one or more of the knowledge gaps need to be fixed for the + knowledge interaction to produce an answer. responses: '200': description: If the Knowledge Interaction is successfully registered, it returns the KnowledgeInteractionId object. @@ -608,6 +620,8 @@ components: knowledgeInteractionName: type: string pattern: '^[a-zA-Z0-9-]*$' + knowledgeGapsEnabled: + type: boolean communicativeAct: $ref: '#/components/schemas/CommunicativeAct' prefixes: @@ -678,7 +692,7 @@ components: type: string AskResult: type: object - required: [bindingSet, exchangeInfo, knowledgeGaps] + required: [bindingSet, exchangeInfo] properties: bindingSet: $ref: '#/components/schemas/BindingSet' From 56ea06c42792dd25283b3ec23ba4ca8acac14b83 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sun, 29 Sep 2024 09:21:37 +0200 Subject: [PATCH 22/64] extended KnowledgeInteraction constructors with knowledgeGapEnabled --- .../smartconnector/api/KnowledgeInteraction.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeInteraction.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeInteraction.java index a116aa79..6485a7d6 100644 --- a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeInteraction.java +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/KnowledgeInteraction.java @@ -1,7 +1,5 @@ package eu.knowledge.engine.smartconnector.api; -import org.apache.jena.ext.xerces.impl.xpath.regex.RegularExpression; - /** * A {@link KnowledgeInteraction} represents an agreement about the exchange of * knowledge between the {@link SmartConnectorImpl} and the @@ -20,6 +18,8 @@ public abstract class KnowledgeInteraction { private final boolean fullMatchOnly; + private final boolean knowledgeGapsEnabled; + /** * {@code true} if this Knowledge Interaction is used for internal knowledge * engine communication. @@ -36,7 +36,7 @@ public abstract class KnowledgeInteraction { * whether it has side-effects or not. */ public KnowledgeInteraction(CommunicativeAct act) { - this(act, null, false, false); + this(act, null, false, false, false); } /** @@ -50,19 +50,20 @@ public KnowledgeInteraction(CommunicativeAct act) { * about the knowledge base itself. */ public KnowledgeInteraction(CommunicativeAct act, boolean isMeta) { - this(act, null, isMeta, false); + this(act, null, isMeta, false, false); } public KnowledgeInteraction(CommunicativeAct act, boolean isMeta, boolean aFullMatchOnly) { - this(act, null, isMeta, aFullMatchOnly); + this(act, null, isMeta, aFullMatchOnly, false); } - public KnowledgeInteraction(CommunicativeAct act, String name, boolean isMeta, boolean aFullMatchOnly) { + public KnowledgeInteraction(CommunicativeAct act, String name, boolean isMeta, boolean aFullMatchOnly, boolean aKnowledgeGapsEnabled) { this.validateName(name); this.act = act; this.name = name; this.isMeta = isMeta; this.fullMatchOnly = aFullMatchOnly; + this.knowledgeGapsEnabled = aKnowledgeGapsEnabled; } public String getName() { @@ -84,6 +85,9 @@ public boolean fullMatchOnly() { return this.fullMatchOnly; } + public boolean knowledgeGapsEnabled() { + return this.knowledgeGapsEnabled; + } /** * Throws an exception if {@code name} does not conform to the requirements of * knowledge interaction names. From 96e73b8b940bb5343ecf904d9baedfc91c379b27 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sun, 29 Sep 2024 09:24:13 +0200 Subject: [PATCH 23/64] extended AskKnowledgeInteraction constructor with knowledgeGapsEnabled --- .../api/AskKnowledgeInteraction.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskKnowledgeInteraction.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskKnowledgeInteraction.java index 89ab8117..44e3b451 100644 --- a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskKnowledgeInteraction.java +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AskKnowledgeInteraction.java @@ -27,25 +27,28 @@ public final class AskKnowledgeInteraction extends KnowledgeInteraction { * that this {@link KnowledgeInteraction} asks for. */ public AskKnowledgeInteraction(CommunicativeAct act, GraphPattern pattern) { - this(act, pattern, null, false, false); + this(act, pattern, null, false, false, false); } public AskKnowledgeInteraction(CommunicativeAct act, GraphPattern pattern, String name) { - this(act, pattern, name, false, false); + this(act, pattern, name, false, false, false); + } + + public AskKnowledgeInteraction(CommunicativeAct act, GraphPattern pattern, String name, boolean aKnowledgeGapsEnabled) { + this(act, pattern, name, false, false, aKnowledgeGapsEnabled); } public AskKnowledgeInteraction(CommunicativeAct act, GraphPattern pattern, boolean anIsFullMatch) { - this(act, pattern, null, false, anIsFullMatch); + this(act, pattern, null, false, anIsFullMatch, false); } - public AskKnowledgeInteraction(CommunicativeAct act, GraphPattern pattern, boolean anisMeta, - boolean anIsFullMatch) { - this(act, pattern, null, anisMeta, anIsFullMatch); + public AskKnowledgeInteraction(CommunicativeAct act, GraphPattern pattern, boolean anisMeta, boolean anIsFullMatch) { + this(act, pattern, null, anisMeta, anIsFullMatch, false); } public AskKnowledgeInteraction(CommunicativeAct act, GraphPattern pattern, String name, boolean anisMeta, - boolean anIsFullMatch) { - super(act, name, anisMeta, anIsFullMatch); + boolean anIsFullMatch, boolean aKnowledgeGapsEnabled) { + super(act, name, anisMeta, anIsFullMatch, aKnowledgeGapsEnabled); this.pattern = pattern; } From 08495e3b9bffa9d38bbdd4a080386d7eb41cf901 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sun, 29 Sep 2024 09:25:59 +0200 Subject: [PATCH 24/64] No knowledgeGapsEnabled in Answer, Post, React KnowledgeInteraction yet --- .../engine/smartconnector/api/AnswerKnowledgeInteraction.java | 2 +- .../engine/smartconnector/api/PostKnowledgeInteraction.java | 2 +- .../engine/smartconnector/api/ReactKnowledgeInteraction.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AnswerKnowledgeInteraction.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AnswerKnowledgeInteraction.java index 6282a316..18e67398 100644 --- a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AnswerKnowledgeInteraction.java +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/AnswerKnowledgeInteraction.java @@ -45,7 +45,7 @@ public AnswerKnowledgeInteraction(CommunicativeAct anAct, GraphPattern aPattern, public AnswerKnowledgeInteraction(CommunicativeAct anAct, GraphPattern aPattern, String name, boolean anIsMeta, boolean anIsFullMatch) { - super(anAct, name, anIsMeta, anIsFullMatch); + super(anAct, name, anIsMeta, anIsFullMatch, false); this.pattern = aPattern; } diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/PostKnowledgeInteraction.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/PostKnowledgeInteraction.java index 7d4f137f..ae757943 100644 --- a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/PostKnowledgeInteraction.java +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/PostKnowledgeInteraction.java @@ -58,7 +58,7 @@ public PostKnowledgeInteraction(CommunicativeAct act, GraphPattern argument, Gra public PostKnowledgeInteraction(CommunicativeAct act, GraphPattern argument, GraphPattern result, String name, boolean anIsMeta, boolean anIsFullMatch) { - super(act, name, anIsMeta, anIsFullMatch); + super(act, name, anIsMeta, anIsFullMatch, false); this.argument = argument; this.result = result; } diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/ReactKnowledgeInteraction.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/ReactKnowledgeInteraction.java index 06cbd373..5126db2b 100644 --- a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/ReactKnowledgeInteraction.java +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/ReactKnowledgeInteraction.java @@ -54,7 +54,7 @@ public ReactKnowledgeInteraction(CommunicativeAct act, GraphPattern argument, Gr public ReactKnowledgeInteraction(CommunicativeAct act, GraphPattern argument, GraphPattern result, String name, boolean anIsMeta, boolean anIsFullMatch) { - super(act, name, anIsMeta, anIsFullMatch); + super(act, name, anIsMeta, anIsFullMatch, false); this.argument = argument; this.result = result; } From 1f1e5cd0f95aade86ddf9d0d7aeeb176ee2d6e89 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sun, 29 Sep 2024 09:27:04 +0200 Subject: [PATCH 25/64] changed LOG statement from info to debug --- .../knowledge/engine/smartconnector/impl/ReasonerProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java index 1ec7cd88..e75868b5 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java @@ -690,7 +690,7 @@ public Set getKnowledgeGaps(RuleNode plan) { } } - LOG.info("Found existingOrGaps {}", existingOrGaps); + LOG.debug("Found existingOrGaps {}", existingOrGaps); return existingOrGaps; } From ec1a90a40ab475705c60d14c23bd940aad9c6893 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sun, 29 Sep 2024 09:33:02 +0200 Subject: [PATCH 26/64] adjusted Ask to return knowledge gaps only when enabled --- .../rest/api/impl/ProactiveApiServiceImpl.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java index d7e6f11f..15ad3dc0 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java @@ -149,12 +149,16 @@ public void scAskPost( .failedMessage(aei.getFailedMessage())) .collect(Collectors.toList()); - LOG.info("Bindings in result is {}", askResult.getBindings()); - LOG.info("Knowledge gaps in result is {}", askResult.getKnowledgeGaps()); - AskResult ar = new AskResult().bindingSet(this.bindingSetToList(askResult.getBindings())) - .knowledgeGaps(this.knowledgeGapsToList(askResult.getKnowledgeGaps())) - .exchangeInfo(infos); - + LOG.debug("Bindings in result is {}", askResult.getBindings()); + AskResult ar = new AskResult().bindingSet(this.bindingSetToList(askResult.getBindings())).exchangeInfo(infos); + + LOG.debug("KnowledgeGapsEnabled is {}", ki.getKnowledgeGapsEnabled()); + // add knowledge gaps if knowledgeGapsEnabled is true + if (ki.getKnowledgeGapsEnabled()) { + //LOG.info("Knowledge gaps in result is {}", askResult.getKnowledgeGaps()); + ar.knowledgeGaps(this.knowledgeGapsToList(askResult.getKnowledgeGaps())); + } + asyncResponse.resume(Response.status(Status.OK).entity(ar).build()); }); From 5e9837292127f4eaa3d30729e09a31d55c20efbe Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sun, 29 Sep 2024 09:35:54 +0200 Subject: [PATCH 27/64] updated RestKB register KI to enable KGs only for AskKI --- .../knowledge/engine/rest/api/impl/RestKnowledgeBase.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/RestKnowledgeBase.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/RestKnowledgeBase.java index 942e854a..604ec1fb 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/RestKnowledgeBase.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/RestKnowledgeBase.java @@ -388,6 +388,9 @@ public String register(KnowledgeInteractionBase ki) { prefixMapping = new PrefixMappingZero(); } + boolean knowledgeGapsEnabled = ki.getKnowledgeGapsEnabled() == null ? false + : ki.getKnowledgeGapsEnabled(); + String type = ki.getKnowledgeInteractionType(); URI kiId; if (type.equals("AskKnowledgeInteraction")) { @@ -398,7 +401,7 @@ public String register(KnowledgeInteractionBase ki) { throw new IllegalArgumentException("graphPattern must be given for ASK knowledge interactions."); } var askKI = new AskKnowledgeInteraction(ca, new GraphPattern(prefixMapping, aki.getGraphPattern()), - ki.getKnowledgeInteractionName()); + ki.getKnowledgeInteractionName(), knowledgeGapsEnabled); kiId = this.sc.register(askKI); this.knowledgeInteractions.put(kiId, askKI); } else if (type.equals("AnswerKnowledgeInteraction")) { @@ -508,7 +511,7 @@ private KnowledgeInteractionWithId kiToModelKiWithId(URI kiId, KnowledgeInteract var requirements = act.getRequirementPurposes().stream().map(r -> r.toString()).collect(Collectors.toList()); var satisfactions = act.getSatisfactionPurposes().stream().map(r -> r.toString()).collect(Collectors.toList()); var kiwid = new KnowledgeInteractionWithId().knowledgeInteractionId(kiId.toString()) - .knowledgeInteractionName(ki.getName()) + .knowledgeInteractionName(ki.getName()).knowledgeGapsEnabled(ki.knowledgeGapsEnabled()) .communicativeAct(new eu.knowledge.engine.rest.model.CommunicativeAct().requiredPurposes(requirements) .satisfiedPurposes(satisfactions)); From 3ba47380bf6d6a94d6b4e1815568dcbb988eb719 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Tue, 1 Oct 2024 18:27:28 +0200 Subject: [PATCH 28/64] deleted getKnowlegeGaps from Utils as it is now part of Ask --- .../impl/ReasonerProcessor.java | 4 +- .../engine/smartconnector/impl/Util.java | 111 ------------------ 2 files changed, 1 insertion(+), 114 deletions(-) diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java index e75868b5..2e01bc05 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java @@ -184,9 +184,7 @@ public CompletableFuture executeAskInteraction(BindingSet someBinding return this.finalBindingSetFuture.thenApply((bs) -> { this.knowledgeGaps = new HashSet(); - // TODO: make the calculation of knowledge gaps configurable. - // For now, if the binding set is empty, check for knowledge gaps - if (bs.isEmpty()) { + if (bs.isEmpty() && myKnowledgeInteraction.getKnowledgeInteraction().knowledgeGapsEnabled()) { this.knowledgeGaps = getKnowledgeGaps(this.reasonerPlan.getStartNode()); } return new AskResult(translateBindingSetFrom(bs), this.askExchangeInfos, this.reasonerPlan, this.knowledgeGaps); diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/Util.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/Util.java index e37b356b..25820dba 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/Util.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/Util.java @@ -26,16 +26,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import eu.knowledge.engine.reasoner.AntSide; -import eu.knowledge.engine.reasoner.BindingSetHandler; -import eu.knowledge.engine.reasoner.Rule; -import eu.knowledge.engine.reasoner.api.TriplePattern; -import eu.knowledge.engine.reasoner.rulenode.RuleNode; import eu.knowledge.engine.smartconnector.api.Binding; import eu.knowledge.engine.smartconnector.api.BindingSet; import eu.knowledge.engine.smartconnector.api.GraphPattern; -import eu.knowledge.engine.smartconnector.impl.ReasonerProcessor.AnswerBindingSetHandler; -import eu.knowledge.engine.smartconnector.impl.ReasonerProcessor.ReactBindingSetHandler; public class Util { private static final Logger LOG = LoggerFactory.getLogger(Util.class); @@ -106,110 +99,6 @@ public static Model generateModel(GraphPattern graphPattern, BindingSet variable return m; } - /** - * Returns the knowledge gap of this reasoning node. A knowledge gap is a subset - * of this node's antecedent triple patterns that do not match any neighbor that - * has no knowledge gaps. - * - * Currently, this method does not show how the knowledge gaps influence each - * other. Some knowledge gaps might have an {@code or}-relation (namely those - * that occur on the same triple) and some might have {@code and}-relations - * (i.e. those that do not occur on the same triple). This information is - * important if you want to know how to solve the gaps because 2 gaps related by - * {@code or} do not both need to be solved, but only one of them. While 2 gaps - * related by {@code and} both need to be solved to solve the gap. - * - * @return returns all triples that have no matching nodes (and for which there - * are no alternatives). Note that it returns a set of sets. Where every - * set in this set represents a single way to resolve the knowledge gaps - * present in this reasoning graph. So, {@code [[A],[B]]} means either - * triple {@code A} OR triple {@code B} needs be added to - * solve the gap or both, while {@code [[A,B]]} means that both - * {@code A} AND {@code B} need to be added to solve the - * gap. - */ - public static Set> getKnowledgeGaps(RuleNode plan) { - - assert plan instanceof AntSide; - - Set> existingOrGaps = new HashSet<>(); - - // TODO do we need to include the parent if we are not backward chaining? - Map> nodeCoverage = plan - .findAntecedentCoverage(((AntSide) plan).getAntecedentNeighbours()); - - // collect triple patterns that have an empty set - Set> collectedOrGaps, someGaps = new HashSet<>(); - for (Entry> entry : nodeCoverage.entrySet()) { - - collectedOrGaps = new HashSet<>(); - boolean foundNeighborWithoutGap = false; - for (RuleNode neighbor : entry.getValue()) { - if (!neighbor.getRule().getAntecedent().isEmpty()) { - // make sure neighbor has no knowledge gaps - - // knowledge engine specific code. We ignore meta knowledge interactions when - // looking for knowledge gaps, because they are very generic and make finding - // knowledge gaps nearly impossible. - boolean isMeta = isMetaKI(neighbor); - - //TODO what if the graph contains loops? - if (!isMeta && (someGaps = getKnowledgeGaps(neighbor)).isEmpty()) { - // found neighbor without knowledge gaps for the current triple, so current - // triple is covered. - foundNeighborWithoutGap = true; - break; - } - collectedOrGaps.addAll(someGaps); - } else - foundNeighborWithoutGap = true; - } - - if (!foundNeighborWithoutGap) { - // there is a gap here, either in the current node or in a neighbor. - - if (collectedOrGaps.isEmpty()) { - collectedOrGaps.add(new HashSet<>(Arrays.asList(entry.getKey()))); - } - - Set> newExistingOrGaps = new HashSet<>(); - if (existingOrGaps.isEmpty()) { - existingOrGaps.addAll(collectedOrGaps); - } else { - Set newGap; - for (Set existingOrGap : existingOrGaps) { - for (Set collectedOrGap : collectedOrGaps) { - newGap = new HashSet<>(); - newGap.addAll(existingOrGap); - newGap.addAll(collectedOrGap); - newExistingOrGaps.add(newGap); - } - } - existingOrGaps = newExistingOrGaps; - } - } - } - - return existingOrGaps; - } - - private static boolean isMetaKI(RuleNode neighbor) { - - assert neighbor.getRule() instanceof Rule; - - BindingSetHandler bsh = ((Rule) neighbor.getRule()).getBindingSetHandler(); - - if (bsh instanceof ReactBindingSetHandler) { - ReactBindingSetHandler rbsh = (ReactBindingSetHandler) bsh; - return rbsh.getKnowledgeInteractionInfo().isMeta(); - } else if (bsh instanceof AnswerBindingSetHandler) { - AnswerBindingSetHandler absh = (AnswerBindingSetHandler) bsh; - return absh.getKnowledgeInteractionInfo().isMeta(); - } - - return false; - } - public static void removeRedundantBindingsAnswer(BindingSet incoming, BindingSet outgoing) { if (incoming.isEmpty()) { // We should not remove any bindings in this case! From 9fe21dfaf3a5aa17f80e9de2636a2019a89282c6 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Tue, 1 Oct 2024 18:29:30 +0200 Subject: [PATCH 29/64] adjusted test to deal with gaps via Ask --- .../api/TestDynamicSemanticComposition.java | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestDynamicSemanticComposition.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestDynamicSemanticComposition.java index b8944897..b29cbe5f 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestDynamicSemanticComposition.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestDynamicSemanticComposition.java @@ -30,10 +30,8 @@ import eu.knowledge.engine.reasoner.BaseRule.MatchStrategy; import eu.knowledge.engine.reasoner.Match; -import eu.knowledge.engine.reasoner.ReasonerPlan; import eu.knowledge.engine.reasoner.Rule; import eu.knowledge.engine.reasoner.api.TriplePattern; -import eu.knowledge.engine.smartconnector.impl.Util; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; @@ -103,20 +101,20 @@ public void testAskAnswer() throws InterruptedException, URISyntaxException { setupNetwork(); - // start planning ask for targets! + // perform an ASK that should produce gaps and if found update network to fix gaps + try { + AskResult resultWithGaps = kbHVTSearcher.ask(askKI, new BindingSet()).get(); + // check for knowledge gaps + Set gaps = resultWithGaps.getKnowledgeGaps(); + LOG.info("Found gaps: " + gaps); + // add KB that fills the knowledge gap + updateNetwork(gaps); + } catch (InterruptedException | ExecutionException e) { + fail(); + } + + // perform ask for targets and knowledge gaps should have been fixed BindingSet bindings = null; - AskPlan plan = kbHVTSearcher.planAsk(askKI, new RecipientSelector()); - ReasonerPlan rn = plan.getReasonerPlan(); - rn.getStore().printGraphVizCode(rn); - // check for knowledge gaps - Set> gaps = Util.getKnowledgeGaps(rn.getStartNode()); - LOG.info("Found gaps: " + gaps); - - // add KB that fills the knowledge gap - updateNetwork(gaps); - - // start testing ask for targets! - bindings = null; try { AskResult result = kbHVTSearcher.ask(askKI, new BindingSet()).get(); bindings = result.getBindings(); @@ -180,7 +178,7 @@ private void setupNetwork() { LOG.info("Duration: {}", (((double) (end - start)) / 1_000_000)); } - private void updateNetwork(Set> gaps) { + private void updateNetwork(Set gaps) { instantiateTargetCountrySupplierKB(); instantiateTargetLanguageSupplierKB(); @@ -250,7 +248,7 @@ public void instantiateHVTSearcherKB() { // Patterns for the HVTSearcher // a pattern to ask for High Value Target searches GraphPattern gp2 = new GraphPattern(prefixes, "?id rdf:type v1905:HighValueTarget . ?id v1905:hasName ?name ."); - this.askKI = new AskKnowledgeInteraction(new CommunicativeAct(), gp2, "askHVTargets"); + this.askKI = new AskKnowledgeInteraction(new CommunicativeAct(), gp2, "askHVTargets",true); kbHVTSearcher.register(this.askKI); // a pattern to react to incoming new High Value Targets ReactKnowledgeInteraction reactKIsearcher = new ReactKnowledgeInteraction(new CommunicativeAct(), gp2, null); From 2e8117f8d6cdf7039a68270d0fe312237abad8dc Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sun, 6 Oct 2024 21:49:59 +0200 Subject: [PATCH 30/64] added 4 tests for knowledge gaps testing --- .../api/impl/ProactiveApiServiceImpl.java | 2 +- .../impl/ReasonerProcessor.java | 2 +- .../api/TestAskAnswerWithGapsEnabled1.java | 120 +++++++++++ .../api/TestAskAnswerWithGapsEnabled2.java | 155 ++++++++++++++ .../api/TestAskAnswerWithGapsEnabled3.java | 189 ++++++++++++++++++ .../api/TestAskAnswerWithGapsNotEnabled.java | 114 +++++++++++ 6 files changed, 580 insertions(+), 2 deletions(-) create mode 100644 smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled1.java create mode 100644 smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled2.java create mode 100644 smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled3.java create mode 100644 smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsNotEnabled.java diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java index 15ad3dc0..7723849d 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java @@ -155,7 +155,7 @@ public void scAskPost( LOG.debug("KnowledgeGapsEnabled is {}", ki.getKnowledgeGapsEnabled()); // add knowledge gaps if knowledgeGapsEnabled is true if (ki.getKnowledgeGapsEnabled()) { - //LOG.info("Knowledge gaps in result is {}", askResult.getKnowledgeGaps()); + LOG.info("Knowledge gaps in result is {}", askResult.getKnowledgeGaps()); ar.knowledgeGaps(this.knowledgeGapsToList(askResult.getKnowledgeGaps())); } diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java index 2e01bc05..5467cc82 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java @@ -679,7 +679,7 @@ public Set getKnowledgeGaps(RuleNode plan) { newGap = new KnowledgeGap(); newGap.addAll(existingOrGap); newGap.addAll(collectedOrGap); - LOG.debug("Found newGap {}", newGap); + LOG.info("Found newGap {}", newGap); newExistingOrGaps.add(newGap); } } diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled1.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled1.java new file mode 100644 index 00000000..721ab12e --- /dev/null +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled1.java @@ -0,0 +1,120 @@ +package eu.knowledge.engine.smartconnector.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.apache.jena.shared.PrefixMapping; +import org.apache.jena.sparql.core.TriplePath; +import org.apache.jena.sparql.graph.PrefixMappingMem; +import org.apache.jena.sparql.graph.PrefixMappingZero; +import org.apache.jena.sparql.syntax.ElementPathBlock; +import org.apache.jena.sparql.util.FmtUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.knowledge.engine.reasoner.BaseRule.MatchStrategy; +import eu.knowledge.engine.reasoner.Match; +import eu.knowledge.engine.reasoner.Rule; +import eu.knowledge.engine.reasoner.api.TriplePattern; +import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; +import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; + +public class TestAskAnswerWithGapsEnabled1 { + + private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerWithGapsEnabled1.class); + + private static MockedKnowledgeBase kbRelationAsker; + + private static KnowledgeNetwork kn; + + private static PrefixMappingMem prefixes; + + private AskKnowledgeInteraction askKIGaps; + + @BeforeAll + public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { + + prefixes = new PrefixMappingMem(); + prefixes.setNsPrefixes(PrefixMapping.Standard); + prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); + + } + + @Test + public void testAskAnswerWithGapsEnabled() throws InterruptedException, URISyntaxException { + + // In this test there will be only 1 KB with a single AskKI. + // The test will execute the AskKI with knowledge gaps enabled. + // As a result, the set of knowledge gaps should contain a single gap. + + setupNetwork(); + + // Perform the ASK + try { + AskResult result = kbRelationAsker.ask(askKIGaps, new BindingSet()).get(); + // check whether set of knowledge gaps contains a single gap + Set gaps = result.getKnowledgeGaps(); + LOG.info("Found gaps: " + gaps); + assertFalse(gaps.isEmpty(),"The set of knowledge gaps should be empty"); + assertEquals(1, gaps.size(), "Number of gaps should be 1"); + Iterator iter = gaps.iterator(); + while (iter.hasNext()) { + KnowledgeGap gap = iter.next(); + assertEquals("[?a isRelatedTo ?b]", gap.toString(), "Gap should be [?a isRelatedTo ?b]"); + } + BindingSet bindings = result.getBindings(); + LOG.info("Resulting binding set is: " + bindings); + assertTrue(bindings.isEmpty(),"The resulting bindingset should be empty"); + } catch (InterruptedException | ExecutionException e) { + fail(); + } + + } + + private void setupNetwork() { + + instantiateAskRelationsKB(); + kn = new KnowledgeNetwork(); + kn.addKB(kbRelationAsker); + + long start = System.nanoTime(); + kn.sync(); + long end = System.nanoTime(); + LOG.info("Duration: {}", (((double) (end - start)) / 1_000_000)); + } + + public void instantiateAskRelationsKB() { + // start a knowledge base with the behavior "I am interested in related people" + kbRelationAsker = new MockedKnowledgeBase("RelationAsker"); + kbRelationAsker.setReasonerEnabled(true); + + // Register an Ask pattern for relations without knowledge gaps enabled + GraphPattern gp1 = new GraphPattern(prefixes, "?a ex:isRelatedTo ?b ."); + this.askKIGaps = new AskKnowledgeInteraction(new CommunicativeAct(), gp1, "askRelations", true); + kbRelationAsker.register(this.askKIGaps); + + } + + @AfterAll + public static void cleanup() throws InterruptedException, ExecutionException { + LOG.info("Clean up: {}", TestAskAnswerWithGapsEnabled1.class.getSimpleName()); + kn.stop().get(); + } +} diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled2.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled2.java new file mode 100644 index 00000000..96967e30 --- /dev/null +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled2.java @@ -0,0 +1,155 @@ +package eu.knowledge.engine.smartconnector.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.apache.jena.shared.PrefixMapping; +import org.apache.jena.sparql.core.TriplePath; +import org.apache.jena.sparql.graph.PrefixMappingMem; +import org.apache.jena.sparql.graph.PrefixMappingZero; +import org.apache.jena.sparql.syntax.ElementPathBlock; +import org.apache.jena.sparql.util.FmtUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.knowledge.engine.reasoner.BaseRule.MatchStrategy; +import eu.knowledge.engine.reasoner.Match; +import eu.knowledge.engine.reasoner.Rule; +import eu.knowledge.engine.reasoner.api.TriplePattern; +import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; +import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; + +public class TestAskAnswerWithGapsEnabled2 { + + private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerWithGapsEnabled2.class); + + private static MockedKnowledgeBase kbRelationAsker; + private static MockedKnowledgeBase kbRelationProvider; + + private static KnowledgeNetwork kn; + + private static PrefixMappingMem prefixes; + + private AskKnowledgeInteraction askKIGaps; + + @BeforeAll + public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { + + prefixes = new PrefixMappingMem(); + prefixes.setNsPrefixes(PrefixMapping.Standard); + prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); + + } + + @Test + public void testAskAnswerWithGapsEnabled() throws InterruptedException, URISyntaxException { + + // In this test there will be an Ask KB with a single AskKI and + // an AnswerKB with a single AnswerKI that answers only part of the Ask pattern. + // The test will execute the AskKI with knowledge gaps enabled. + // As a result, the set of knowledge gaps should contain a single gap. + + setupNetwork(); + + // Perform the ASK + try { + AskResult result = kbRelationAsker.ask(askKIGaps, new BindingSet()).get(); + // check whether set of knowledge gaps contains a single gap + Set gaps = result.getKnowledgeGaps(); + LOG.info("Found gaps: " + gaps); + assertFalse(gaps.isEmpty(),"The set of knowledge gaps should be empty"); + assertEquals(1, gaps.size(), "Number of gaps should be 1"); + Iterator iter = gaps.iterator(); + while (iter.hasNext()) { + KnowledgeGap gap = iter.next(); + Iterator gapiter = gap.iterator(); + while (gapiter.hasNext()) { + TriplePattern triple = gapiter.next(); + String tpString = FmtUtils.stringForTriple(triple.asTriple(), new PrefixMappingZero()); + LOG.info("Gap is " + tpString); + assertEquals("?a ?c", tpString, "Gap should be ?a ?c"); + } + } + BindingSet bindings = result.getBindings(); + LOG.info("Resulting binding set is: " + bindings); + assertTrue(bindings.isEmpty(),"The resulting bindingset should be empty"); + } catch (InterruptedException | ExecutionException e) { + fail(); + } + + } + + private void setupNetwork() { + + instantiateAskRelationsKB(); + instantiateAnswerRelationsKB(); + kn = new KnowledgeNetwork(); + kn.addKB(kbRelationAsker); + kn.addKB(kbRelationProvider); + + long start = System.nanoTime(); + kn.sync(); + long end = System.nanoTime(); + LOG.info("Duration: {}", (((double) (end - start)) / 1_000_000)); + } + + public void instantiateAskRelationsKB() { + // start a knowledge base with the behavior "I am interested in related people" + kbRelationAsker = new MockedKnowledgeBase("RelationAsker"); + kbRelationAsker.setReasonerEnabled(true); + + // Register an Ask pattern for relations without knowledge gaps enabled + GraphPattern gp1 = new GraphPattern(prefixes, "?a ex:isRelatedTo ?b . ?a ex:isFatherOf ?c ."); + this.askKIGaps = new AskKnowledgeInteraction(new CommunicativeAct(), gp1, "askRelations", true); + kbRelationAsker.register(this.askKIGaps); + + } + + public void instantiateAnswerRelationsKB() { + // start a knowledge base with the behavior "I can supply related people" + kbRelationProvider = new MockedKnowledgeBase("RelationProvider"); + kbRelationProvider.setReasonerEnabled(true); + + // Patterns for the RelationProvider: an Answer pattern for relations + GraphPattern gp1 = new GraphPattern(prefixes, "?a ex:isRelatedTo ?b ."); + AnswerKnowledgeInteraction aKI = new AnswerKnowledgeInteraction(new CommunicativeAct(), gp1, "answerRelations"); + kbRelationProvider.register(aKI, (AnswerHandler) (anAKI, anAnswerExchangeInfo) -> { + assertTrue( + anAnswerExchangeInfo.getIncomingBindings().isEmpty() + || anAnswerExchangeInfo.getIncomingBindings().iterator().next().getVariables().isEmpty(), + "Should not have bindings in this binding set."); + + // add 1 dummy binding to the answer + BindingSet bindingSet = new BindingSet(); + Binding binding1 = new Binding(); + binding1.put("a", ""); + binding1.put("b", ""); + bindingSet.add(binding1); + + return bindingSet; + }); + } + + @AfterAll + public static void cleanup() throws InterruptedException, ExecutionException { + LOG.info("Clean up: {}", TestAskAnswerWithGapsEnabled2.class.getSimpleName()); + kn.stop().get(); + } +} diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled3.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled3.java new file mode 100644 index 00000000..77713d53 --- /dev/null +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled3.java @@ -0,0 +1,189 @@ +package eu.knowledge.engine.smartconnector.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.apache.jena.shared.PrefixMapping; +import org.apache.jena.sparql.core.TriplePath; +import org.apache.jena.sparql.graph.PrefixMappingMem; +import org.apache.jena.sparql.graph.PrefixMappingZero; +import org.apache.jena.sparql.syntax.ElementPathBlock; +import org.apache.jena.sparql.util.FmtUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.knowledge.engine.reasoner.BaseRule.MatchStrategy; +import eu.knowledge.engine.reasoner.Match; +import eu.knowledge.engine.reasoner.Rule; +import eu.knowledge.engine.reasoner.api.TriplePattern; +import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; +import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; + +public class TestAskAnswerWithGapsEnabled3 { + + private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerWithGapsEnabled3.class); + + private static MockedKnowledgeBase kbRelationAsker; + private static MockedKnowledgeBase kbRelationProvider; + private static MockedKnowledgeBase kbRelationReactor; + + private static KnowledgeNetwork kn; + + private static PrefixMappingMem prefixes; + + private AskKnowledgeInteraction askKIGaps; + + @BeforeAll + public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { + + prefixes = new PrefixMappingMem(); + prefixes.setNsPrefixes(PrefixMapping.Standard); + prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); + + } + + @Test + public void testAskAnswerReactWithGapsEnabled() throws InterruptedException, URISyntaxException { + + // In this test there will be an Ask KB with an AskKI with 2 triplepatterns, + // an AnswerKB with a single AnswerKI that answers only the first triplepattern of the Ask pattern, and + // a ReactKB that can answer the , but needs another pattern to be satisfied. + // The test will execute the AskKI with knowledge gaps enabled. + // As a result, the set of knowledge gaps should contain a single gap. + + setupNetwork(); + + // Perform the ASK + try { + AskResult result = kbRelationAsker.ask(askKIGaps, new BindingSet()).get(); + // check whether set of knowledge gaps contains a single gap + Set gaps = result.getKnowledgeGaps(); + LOG.info("Found gaps: " + gaps); + assertFalse(gaps.isEmpty(),"The set of knowledge gaps should not be empty"); + assertEquals(1, gaps.size(), "Number of gaps should be 1"); + Iterator iter = gaps.iterator(); + while (iter.hasNext()) { + KnowledgeGap gap = iter.next(); + Iterator gapiter = gap.iterator(); + while (gapiter.hasNext()) { + TriplePattern triple = gapiter.next(); + String tpString = FmtUtils.stringForTriple(triple.asTriple(), new PrefixMappingZero()); + LOG.info("Gap is " + tpString); + } + } + BindingSet bindings = result.getBindings(); + LOG.info("Resulting binding set is: " + bindings); + assertTrue(bindings.isEmpty(),"The resulting bindingset should be empty"); + } catch (InterruptedException | ExecutionException e) { + fail(); + } + + } + + private void setupNetwork() { + + instantiateAskRelationsKB(); + instantiateAnswerRelationsKB(); + instantiateReactRelationsKB(); + kn = new KnowledgeNetwork(); + kn.addKB(kbRelationAsker); + kn.addKB(kbRelationProvider); + kn.addKB(kbRelationReactor); + + long start = System.nanoTime(); + kn.sync(); + long end = System.nanoTime(); + LOG.info("Duration: {}", (((double) (end - start)) / 1_000_000)); + } + + public void instantiateAskRelationsKB() { + // start a knowledge base with the behavior "I am interested in related people" + kbRelationAsker = new MockedKnowledgeBase("RelationAsker"); + kbRelationAsker.setReasonerEnabled(true); + + // Register an Ask pattern for relations without knowledge gaps enabled + GraphPattern gp1 = new GraphPattern(prefixes, "?a ex:isRelatedTo ?b . ?a ex:isFatherOf ?c ."); + this.askKIGaps = new AskKnowledgeInteraction(new CommunicativeAct(), gp1, "askRelations", true); + kbRelationAsker.register(this.askKIGaps); + + } + + public void instantiateAnswerRelationsKB() { + // start a knowledge base with the behavior "I can supply related people" + kbRelationProvider = new MockedKnowledgeBase("RelationProvider"); + kbRelationProvider.setReasonerEnabled(true); + + // Patterns for the RelationProvider: an Answer pattern for relations + GraphPattern gp1 = new GraphPattern(prefixes, "?a ex:isRelatedTo1 ?b ."); + AnswerKnowledgeInteraction aKI = new AnswerKnowledgeInteraction(new CommunicativeAct(), gp1, "answerRelations"); + kbRelationProvider.register(aKI, (AnswerHandler) (anAKI, anAnswerExchangeInfo) -> { + assertTrue( + anAnswerExchangeInfo.getIncomingBindings().isEmpty() + || anAnswerExchangeInfo.getIncomingBindings().iterator().next().getVariables().isEmpty(), + "Should not have bindings in this binding set."); + + // add 1 dummy binding to the answer + BindingSet bindingSet = new BindingSet(); + Binding binding1 = new Binding(); + binding1.put("a", ""); + binding1.put("b", ""); + bindingSet.add(binding1); + + return bindingSet; + }); + } + + public void instantiateReactRelationsKB() { + + // start a knowledge base with the behavior "I can react to supply related people" + // when I get couples of "people that live in the same house". + kbRelationReactor = new MockedKnowledgeBase("relationReactor"); + kbRelationReactor.setReasonerEnabled(true); + + // Patterns for the relationReactor: an React pattern to supply relations + GraphPattern gp1 = new GraphPattern(prefixes, "?a ex:liveInTheSameHouse ?b ."); + GraphPattern gp2 = new GraphPattern(prefixes, "?a ex:isRelatedTo ?b ."); + ReactKnowledgeInteraction reactKI = new ReactKnowledgeInteraction(new CommunicativeAct(), gp1, gp2); + kbRelationReactor.register(reactKI, (anRKI, aReactExchangeInfo) -> { + + LOG.info("RelationReactor reacting to incoming bindings..."); + BindingSet bindingSet = new BindingSet(); + var argument = aReactExchangeInfo.getArgumentBindings(); + Iterator iter = argument.iterator(); + while (iter.hasNext()) { + Binding b = iter.next(); + LOG.info("Incoming tuple of people living in the same house is {}", b); + Binding binding1 = new Binding(); + binding1.put("a", b.get("a")); + binding1.put("b", b.get("b")); + bindingSet.add(binding1); + } + + return bindingSet; + }); + + } + + @AfterAll + public static void cleanup() throws InterruptedException, ExecutionException { + LOG.info("Clean up: {}", TestAskAnswerWithGapsEnabled3.class.getSimpleName()); + kn.stop().get(); + } +} diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsNotEnabled.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsNotEnabled.java new file mode 100644 index 00000000..61e9bb94 --- /dev/null +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsNotEnabled.java @@ -0,0 +1,114 @@ +package eu.knowledge.engine.smartconnector.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.apache.jena.shared.PrefixMapping; +import org.apache.jena.sparql.core.TriplePath; +import org.apache.jena.sparql.graph.PrefixMappingMem; +import org.apache.jena.sparql.graph.PrefixMappingZero; +import org.apache.jena.sparql.syntax.ElementPathBlock; +import org.apache.jena.sparql.util.FmtUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.knowledge.engine.reasoner.BaseRule.MatchStrategy; +import eu.knowledge.engine.reasoner.Match; +import eu.knowledge.engine.reasoner.Rule; +import eu.knowledge.engine.reasoner.api.TriplePattern; +import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; +import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; + +public class TestAskAnswerWithGapsNotEnabled { + + private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerWithGapsNotEnabled.class); + + private static MockedKnowledgeBase kbRelationAsker; + + private static KnowledgeNetwork kn; + + private static PrefixMappingMem prefixes; + + private AskKnowledgeInteraction askKI; + + @BeforeAll + public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { + + prefixes = new PrefixMappingMem(); + prefixes.setNsPrefixes(PrefixMapping.Standard); + prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); + + } + + @Test + public void testAskAnswerWithoutGapsEnabled() throws InterruptedException, URISyntaxException { + + // In this test there will be only 1 KB with a single AskKI. + // The test will execute the AskKI without knowledge gaps enabled. + // As a result, the set of knowledge gaps should be empty. + + setupNetwork(); + + // Perform the ASK + try { + AskResult result = kbRelationAsker.ask(askKI, new BindingSet()).get(); + // check whether set of knowledge gaps is empty + Set gaps = result.getKnowledgeGaps(); + LOG.info("Found gaps: " + gaps); + assertTrue(gaps.isEmpty(),"The set of knowledge gaps should be empty"); + BindingSet bindings = result.getBindings(); + LOG.info("Resulting binding set is: " + bindings); + assertTrue(bindings.isEmpty(),"The resulting bindingset should be empty"); + } catch (InterruptedException | ExecutionException e) { + fail(); + } + + } + + private void setupNetwork() { + + instantiateAskRelationsKB(); + kn = new KnowledgeNetwork(); + kn.addKB(kbRelationAsker); + + long start = System.nanoTime(); + kn.sync(); + long end = System.nanoTime(); + LOG.info("Duration: {}", (((double) (end - start)) / 1_000_000)); + } + + public void instantiateAskRelationsKB() { + // start a knowledge base with the behavior "I am interested in related people" + kbRelationAsker = new MockedKnowledgeBase("RelationAsker"); + kbRelationAsker.setReasonerEnabled(true); + + // Register an Ask pattern for relations without knowledge gaps enabled + GraphPattern gp1 = new GraphPattern(prefixes, "?a ex:isRelatedTo ?b ."); + this.askKI = new AskKnowledgeInteraction(new CommunicativeAct(), gp1, "askRelations"); + kbRelationAsker.register(this.askKI); + + } + + @AfterAll + public static void cleanup() throws InterruptedException, ExecutionException { + LOG.info("Clean up: {}", TestAskAnswerWithGapsNotEnabled.class.getSimpleName()); + kn.stop().get(); + } +} From 68da55bbde2ae6543639b5971a97c03ce8b45561 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Wed, 9 Oct 2024 08:43:25 +0200 Subject: [PATCH 31/64] changed knowledgeGaps set to null instead of empty when not used --- .../engine/smartconnector/impl/ReasonerProcessor.java | 1 - .../smartconnector/api/TestAskAnswerWithGapsEnabled1.java | 2 +- .../smartconnector/api/TestAskAnswerWithGapsEnabled2.java | 2 +- .../smartconnector/api/TestAskAnswerWithGapsEnabled3.java | 2 +- .../smartconnector/api/TestAskAnswerWithGapsNotEnabled.java | 4 ++-- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java index 5467cc82..8bb75232 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java @@ -183,7 +183,6 @@ public CompletableFuture executeAskInteraction(BindingSet someBinding continueReasoningBackward(translateBindingSetTo(someBindings)); return this.finalBindingSetFuture.thenApply((bs) -> { - this.knowledgeGaps = new HashSet(); if (bs.isEmpty() && myKnowledgeInteraction.getKnowledgeInteraction().knowledgeGapsEnabled()) { this.knowledgeGaps = getKnowledgeGaps(this.reasonerPlan.getStartNode()); } diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled1.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled1.java index 721ab12e..41397144 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled1.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled1.java @@ -105,7 +105,7 @@ public void instantiateAskRelationsKB() { kbRelationAsker = new MockedKnowledgeBase("RelationAsker"); kbRelationAsker.setReasonerEnabled(true); - // Register an Ask pattern for relations without knowledge gaps enabled + // Register an Ask pattern for relations with knowledge gaps enabled GraphPattern gp1 = new GraphPattern(prefixes, "?a ex:isRelatedTo ?b ."); this.askKIGaps = new AskKnowledgeInteraction(new CommunicativeAct(), gp1, "askRelations", true); kbRelationAsker.register(this.askKIGaps); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled2.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled2.java index 96967e30..7c396c97 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled2.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled2.java @@ -115,7 +115,7 @@ public void instantiateAskRelationsKB() { kbRelationAsker = new MockedKnowledgeBase("RelationAsker"); kbRelationAsker.setReasonerEnabled(true); - // Register an Ask pattern for relations without knowledge gaps enabled + // Register an Ask pattern for relations with knowledge gaps enabled GraphPattern gp1 = new GraphPattern(prefixes, "?a ex:isRelatedTo ?b . ?a ex:isFatherOf ?c ."); this.askKIGaps = new AskKnowledgeInteraction(new CommunicativeAct(), gp1, "askRelations", true); kbRelationAsker.register(this.askKIGaps); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled3.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled3.java index 77713d53..651f7e72 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled3.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsEnabled3.java @@ -118,7 +118,7 @@ public void instantiateAskRelationsKB() { kbRelationAsker = new MockedKnowledgeBase("RelationAsker"); kbRelationAsker.setReasonerEnabled(true); - // Register an Ask pattern for relations without knowledge gaps enabled + // Register an Ask pattern for relations with knowledge gaps enabled GraphPattern gp1 = new GraphPattern(prefixes, "?a ex:isRelatedTo ?b . ?a ex:isFatherOf ?c ."); this.askKIGaps = new AskKnowledgeInteraction(new CommunicativeAct(), gp1, "askRelations", true); kbRelationAsker.register(this.askKIGaps); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsNotEnabled.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsNotEnabled.java index 61e9bb94..bfe4d9ae 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsNotEnabled.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithGapsNotEnabled.java @@ -62,7 +62,7 @@ public void testAskAnswerWithoutGapsEnabled() throws InterruptedException, URISy // In this test there will be only 1 KB with a single AskKI. // The test will execute the AskKI without knowledge gaps enabled. - // As a result, the set of knowledge gaps should be empty. + // As a result, the set of knowledge gaps should be null. setupNetwork(); @@ -72,7 +72,7 @@ public void testAskAnswerWithoutGapsEnabled() throws InterruptedException, URISy // check whether set of knowledge gaps is empty Set gaps = result.getKnowledgeGaps(); LOG.info("Found gaps: " + gaps); - assertTrue(gaps.isEmpty(),"The set of knowledge gaps should be empty"); + assertEquals(null, gaps,"The set of knowledge gaps should be null"); BindingSet bindings = result.getBindings(); LOG.info("Resulting binding set is: " + bindings); assertTrue(bindings.isEmpty(),"The resulting bindingset should be empty"); From b021597bbf817f2a8dc4022afaf2bf825e86c160 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Wed, 9 Oct 2024 13:34:10 +0200 Subject: [PATCH 32/64] added a new RestAPI test to check knowledge gaps enabled flag --- ...tRegisterKnowledgeInteractionWithGaps.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteractionWithGaps.java diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteractionWithGaps.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteractionWithGaps.java new file mode 100644 index 00000000..f34fe20d --- /dev/null +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteractionWithGaps.java @@ -0,0 +1,55 @@ +package eu.knowledge.engine.rest.api; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; + +import org.apache.jena.atlas.logging.Log; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import eu.knowledge.engine.rest.RestServerHelper; +import eu.knowledge.engine.test_utils.HttpTester; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestRegisterKnowledgeInteractionWithGaps { + private final RestServerHelper rsh = new RestServerHelper(); + private static int PORT = 8280; + + @BeforeAll + public void setUpServer() { + rsh.start(PORT); + } + + @Test + public void testRegisterKi() throws IOException { + URL url = new URL("http://localhost:" + PORT + "/rest"); + + HttpTester registerKb = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"http://example.com/kb\", \"knowledgeBaseName\": \"KB\", \"knowledgeBaseDescription\": \"KB\"}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerKb.expectStatus(200); + + HttpTester registerKiWithGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "POST", + "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", \"knowledgeInteractionName\": \"some-name\", \"graphPattern\": \"?a ?b ?c.\", \"knowledgeGapsEnabled\": true}", + Map.of("Knowledge-Base-Id", "http://example.com/kb", "Content-Type", "application/json", "Accept", + "*/*")); + registerKiWithGapsEnabled.expectStatus(200); + + HttpTester getKiWithGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "GET", null, Map.of("Knowledge-Base-Id", + "http://example.com/kb", "Content-Type", "application/json", "Accept", "*/*")); + var body = getKiWithGapsEnabled.getBody(); + assertTrue(body.contains("\"http://example.com/kb/interaction/some-name\"")); + assertTrue(body.contains("\"knowledgeGapsEnabled\":true")); + } + + @AfterAll + public void cleanUp() { + rsh.cleanUp(); + } +} From f092211793cf94142e39dec15038792bc37b69e1 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Tue, 15 Oct 2024 19:32:57 +0200 Subject: [PATCH 33/64] added handling exception for knowledgeGapsEnabled setting --- .../eu/knowledge/engine/rest/api/impl/RestKnowledgeBase.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/RestKnowledgeBase.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/RestKnowledgeBase.java index 604ec1fb..d0ce9418 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/RestKnowledgeBase.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/RestKnowledgeBase.java @@ -390,6 +390,9 @@ public String register(KnowledgeInteractionBase ki) { boolean knowledgeGapsEnabled = ki.getKnowledgeGapsEnabled() == null ? false : ki.getKnowledgeGapsEnabled(); + if (knowledgeGapsEnabled && !this.sc.isReasonerEnabled()) { + throw new IllegalArgumentException("You can only set knowledgeGapsEnabled when the Knowledge Base is reasonerEnabled."); + } String type = ki.getKnowledgeInteractionType(); URI kiId; From d482621eb38f7c0f243110c2ab55ca671444f02a Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Wed, 16 Oct 2024 14:28:31 +0200 Subject: [PATCH 34/64] Finished first RestAPI unit test ASK without knowledge gaps --- .../rest/api/TestAskWithGapsNotEnabled.java | 68 +++++++++++++++++++ ...tRegisterKnowledgeInteractionWithGaps.java | 55 --------------- 2 files changed, 68 insertions(+), 55 deletions(-) create mode 100644 smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java delete mode 100644 smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteractionWithGaps.java diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java new file mode 100644 index 00000000..61aee40e --- /dev/null +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java @@ -0,0 +1,68 @@ +package eu.knowledge.engine.rest.api; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; + +import org.apache.jena.atlas.logging.Log; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import eu.knowledge.engine.rest.RestServerHelper; +import eu.knowledge.engine.test_utils.HttpTester; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestAskWithGapsNotEnabled { + private final RestServerHelper rsh = new RestServerHelper(); + private static int PORT = 8280; + + @BeforeAll + public void setUpServer() { + rsh.start(PORT); + } + + @Test + public void testAskWithoutGaps() throws IOException { + + // In this test there will be only 1 KB with a single AskKI. + // The test will execute the AskKI without knowledge gaps enabled. + // As a result, the set of knowledge gaps should be null. + + URL url = new URL("http://localhost:" + PORT + "/rest"); + + HttpTester registerKb = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"https://www.tno.nl/example/relationAsker\", \"knowledgeBaseName\": \"RelationAsker\", \"knowledgeBaseDescription\": \"A KB that asks for relations between people\", \"reasonerEnabled\" : true}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerKb.expectStatus(200); + + HttpTester registerKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "POST", + "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", \"knowledgeInteractionName\": \"askRelations\", \"graphPattern\": \"?a ?b .\"}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", "application/json", "Accept", + "*/*")); + registerKiWithoutGapsEnabled.expectStatus(200); + + HttpTester getKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "GET", null, Map.of("Knowledge-Base-Id", + "https://www.tno.nl/example/relationAsker", "Content-Type", "application/json", "Accept", "*/*")); + var body = getKiWithoutGapsEnabled.getBody(); + assertTrue(body.contains("\"https://www.tno.nl/example/relationAsker/interaction/askRelations\"")); + + HttpTester askKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ask"), "POST", + "{\"recipientSelector\": {\"knowledgeBases\": []}, \"bindingSet\": []}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Knowledge-Interaction-Id", "https://www.tno.nl/example/relationAsker/interaction/askRelations", + "Content-Type", "application/json", "Accept", + "*/*")); + var result = askKiWithoutGapsEnabled.getBody(); + assertFalse(result.contains("\"knowledgeGaps\":")); + } + + @AfterAll + public void cleanUp() { + rsh.cleanUp(); + } +} diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteractionWithGaps.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteractionWithGaps.java deleted file mode 100644 index f34fe20d..00000000 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteractionWithGaps.java +++ /dev/null @@ -1,55 +0,0 @@ -package eu.knowledge.engine.rest.api; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.net.URL; -import java.util.Map; - -import org.apache.jena.atlas.logging.Log; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import eu.knowledge.engine.rest.RestServerHelper; -import eu.knowledge.engine.test_utils.HttpTester; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class TestRegisterKnowledgeInteractionWithGaps { - private final RestServerHelper rsh = new RestServerHelper(); - private static int PORT = 8280; - - @BeforeAll - public void setUpServer() { - rsh.start(PORT); - } - - @Test - public void testRegisterKi() throws IOException { - URL url = new URL("http://localhost:" + PORT + "/rest"); - - HttpTester registerKb = new HttpTester(new URL(url + "/sc"), "POST", - "{\"knowledgeBaseId\": \"http://example.com/kb\", \"knowledgeBaseName\": \"KB\", \"knowledgeBaseDescription\": \"KB\"}", - Map.of("Content-Type", "application/json", "Accept", "*/*")); - registerKb.expectStatus(200); - - HttpTester registerKiWithGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "POST", - "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", \"knowledgeInteractionName\": \"some-name\", \"graphPattern\": \"?a ?b ?c.\", \"knowledgeGapsEnabled\": true}", - Map.of("Knowledge-Base-Id", "http://example.com/kb", "Content-Type", "application/json", "Accept", - "*/*")); - registerKiWithGapsEnabled.expectStatus(200); - - HttpTester getKiWithGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "GET", null, Map.of("Knowledge-Base-Id", - "http://example.com/kb", "Content-Type", "application/json", "Accept", "*/*")); - var body = getKiWithGapsEnabled.getBody(); - assertTrue(body.contains("\"http://example.com/kb/interaction/some-name\"")); - assertTrue(body.contains("\"knowledgeGapsEnabled\":true")); - } - - @AfterAll - public void cleanUp() { - rsh.cleanUp(); - } -} From 3c4ba86fbf8140bd5dfd494e5a71bff7b5545265 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Mon, 21 Oct 2024 15:34:53 +0200 Subject: [PATCH 35/64] Added 3 new unit test on the RestAPI for knowledge gap testing --- .../TestAskAnswerReactWithGapsEnabled.java | 193 ++++++++++++++++++ .../api/TestAskAnswerWithGapsEnabled.java | 131 ++++++++++++ .../rest/api/TestAskWithGapsEnabled.java | 69 +++++++ 3 files changed, 393 insertions(+) create mode 100644 smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java create mode 100644 smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabled.java create mode 100644 smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java new file mode 100644 index 00000000..50ed929a --- /dev/null +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java @@ -0,0 +1,193 @@ +package eu.knowledge.engine.rest.api; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import eu.knowledge.engine.rest.RestServerHelper; +import eu.knowledge.engine.test_utils.AsyncTester; +import eu.knowledge.engine.test_utils.HttpTester; +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonReader; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestAskAnswerReactWithGapsEnabled { + private final RestServerHelper rsh = new RestServerHelper(); + private static int PORT = 8280; + + @BeforeAll + public void setUpServer() { + rsh.start(PORT); + } + + @Test + public void testAskAnswerReactWithGaps() throws IOException { + + // In this test there will be an Ask KB with an AskKI with 2 triplepatterns, + // an AnswerKB with a single AnswerKI that answers only the first triplepattern of the Ask pattern, and + // a ReactKB that can answer the other triplepattern of the Ask, but needs another pattern to be satisfied. + // The test will execute the AskKI with knowledge gaps enabled. + // As a result, the set of knowledge gaps should contain a single gap. + + URL url = new URL("http://localhost:" + PORT + "/rest"); + + // activate the answer SC, KB, KI in a separate thread + var answeringSc = new AsyncTester(new Runnable() { + @Override + public void run() { + String answerKBId = "https://www.tno.nl/example/relationProvider"; + String answerKIId = answerKBId + "/interaction/answerRelations"; + try { + // register the AnswerKB + HttpTester registerAnswerKb = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"https://www.tno.nl/example/relationProvider\", \"knowledgeBaseName\": \"RelationProvider\", \"knowledgeBaseDescription\": \"A KB that provides relations between people\", \"reasonerEnabled\" : true}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerAnswerKb.expectStatus(200); + + // register the AnswerKI + HttpTester registerAnswerKi = new HttpTester(new URL(url + "/sc/ki"), "POST", """ + { + "knowledgeInteractionType": "AnswerKnowledgeInteraction", + "knowledgeInteractionName": "answerRelations", + "graphPattern": "?a ?b ." + } + """, Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationProvider", "Content-Type", "application/json", "Accept", "*/*")); + registerAnswerKi.expectStatus(200); + + // get the handle for the answerKB to see if there are requests to be handled + var test = new HttpTester(new URL(url.toString() + "/sc/handle"), "GET", null, Map + .of("Knowledge-Base-Id", answerKBId, "Content-Type", "application/json", "Accept", "*/*")); + test.expectStatus(200); + + // build the body to answer the request: add handle request ID and dummy data bindingset + JsonObjectBuilder builder = Json.createObjectBuilder(); + JsonReader jp = Json.createReader(new StringReader(test.getBody())); + JsonObject jo = jp.readObject(); + int handleRequestId = jo.getInt("handleRequestId"); + builder.add("handleRequestId", handleRequestId); + JsonReader jr = Json.createReader(new StringReader("[{\"a\": \"\",\"b\": \"\"}]")); + JsonArray bs = jr.readArray(); + builder.add("bindingSet", bs); + JsonObject jo2 = builder.build(); + String body = jo2.toString(); + System.out.println("Handle an answer to a request with body: " + body); + + // fire the POST handle to execute the answer + var test2 = new HttpTester(new URL(url.toString() + "/sc/handle"), "POST", body, + Map.of("Knowledge-Base-Id", answerKBId, "Knowledge-Interaction-Id", answerKIId, "Content-Type", + "application/json", "Accept", "*/*")); + test2.expectStatus(200); + + } catch (MalformedURLException e) { + fail(); + } + } + }); + answeringSc.start(); + + // activate the answer SC, KB, KI in a separate thread + var reactingSc = new AsyncTester(new Runnable() { + @Override + public void run() { + String reactKBId = "https://www.tno.nl/example/relationReactor"; + String reactKIId = reactKBId + "/interaction/reactRelations"; + try { + // register the ReactKB + HttpTester registerReactKb = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"https://www.tno.nl/example/relationReactor\", \"knowledgeBaseName\": \"RelationReactor\", \"knowledgeBaseDescription\": \"A KB that reacts to supply related people\", \"reasonerEnabled\" : true}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerReactKb.expectStatus(200); + + // register the ReactKI + HttpTester registerReactKi = new HttpTester(new URL(url + "/sc/ki"), "POST", """ + { + "knowledgeInteractionType": "ReactKnowledgeInteraction", + "knowledgeInteractionName": "reactRelations", + "argumentGraphPattern": "?a ?b .", + "resultGraphPattern": "?a ?b ." + } + """, Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationReactor", "Content-Type", "application/json", "Accept", "*/*")); + registerReactKi.expectStatus(200); + + System.out.println("Getting the handle for the reactKBId"); + // get the handle for the reactKB to see if there are requests to be handled => + // NOTE: it should never exit/return this handle for this test + var test = new HttpTester(new URL(url.toString() + "/sc/handle"), "GET", null, + Map.of("Knowledge-Base-Id", reactKBId, "Content-Type", "application/json", "Accept", "*/*")); + test.expectStatus(200); + + // build the body to react to the request: add handle request ID and dummy data bindingset + JsonObjectBuilder builder = Json.createObjectBuilder(); + JsonReader jp = Json.createReader(new StringReader(test.getBody())); + JsonObject jo = jp.readObject(); + int handleRequestId = jo.getInt("handleRequestId"); + builder.add("handleRequestId", handleRequestId); + // for now simply add an empty bindingset as a result + builder.add("bindingSet", JsonObject.EMPTY_JSON_ARRAY); + JsonObject jo2 = builder.build(); + String body = jo2.toString(); + System.out.println("Handle a react to a request with body: " + body); + + // fire the POST handle to execute the react + var test2 = new HttpTester(new URL(url.toString() + "/sc/handle"), "POST", body, + Map.of("Knowledge-Base-Id", reactKBId, "Knowledge-Interaction-Id", reactKIId, "Content-Type", + "application/json", "Accept", "*/*")); + test2.expectStatus(200); + + } catch (MalformedURLException e) { + fail(); + } + } + }); + reactingSc.start(); + + // register the AskKB + HttpTester registerKb = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"https://www.tno.nl/example/relationAsker\", \"knowledgeBaseName\": \"RelationAsker\", \"knowledgeBaseDescription\": \"A KB that asks for relations between people\", \"reasonerEnabled\" : true}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerKb.expectStatus(200); + + // register the AskKI + HttpTester registerKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "POST", """ + { + "knowledgeInteractionType": "AskKnowledgeInteraction", + "knowledgeInteractionName": "askRelations", + "graphPattern": "?a ?b . ?a ?c .", + "knowledgeGapsEnabled": true + } + """, Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", "application/json", "Accept", + "*/*")); + registerKiWithoutGapsEnabled.expectStatus(200); + + // fire the ask KI + HttpTester askKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ask"), "POST", + "{\"recipientSelector\": {\"knowledgeBases\": []}, \"bindingSet\": []}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Knowledge-Interaction-Id", "https://www.tno.nl/example/relationAsker/interaction/askRelations", + "Content-Type", "application/json", "Accept", + "*/*")); + var result = askKiWithoutGapsEnabled.getBody(); + System.out.println("Result is:" +result); + assertTrue(result.contains("\"knowledgeGaps\":[[\"?a ?c\",\"?a ?b\"]]")); + assertTrue(result.contains("\"bindingSet\":[]")); + + } + + @AfterAll + public void cleanUp() { + rsh.cleanUp(); + } +} diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabled.java new file mode 100644 index 00000000..bb424da7 --- /dev/null +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabled.java @@ -0,0 +1,131 @@ +package eu.knowledge.engine.rest.api; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import org.apache.jena.atlas.logging.Log; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import eu.knowledge.engine.rest.RestServerHelper; +import eu.knowledge.engine.test_utils.AsyncTester; +import eu.knowledge.engine.test_utils.HttpTester; +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonReader; +import jakarta.json.JsonValue; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestAskAnswerWithGapsEnabled { + private final RestServerHelper rsh = new RestServerHelper(); + private static int PORT = 8280; + + @BeforeAll + public void setUpServer() { + rsh.start(PORT); + } + + @Test + public void testAskAnswerWithGaps() throws IOException { + + // In this test there will be an Ask KB with a single AskKI and + // an AnswerKB with a single AnswerKI that answers only part of the Ask pattern. + // The test will execute the AskKI with knowledge gaps enabled. + // As a result, the set of knowledge gaps should contain a single gap. + + URL url = new URL("http://localhost:" + PORT + "/rest"); + + // activate the answer SC, KB, KI in a separate thread + var answeringSc = new AsyncTester(new Runnable() { + @Override + public void run() { + String answerKBId = "https://www.tno.nl/example/relationProvider"; + String answerKIId = answerKBId + "/interaction/answerRelations"; + try { + // register the AnswerKB + HttpTester registerAnswerKb = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"https://www.tno.nl/example/relationProvider\", \"knowledgeBaseName\": \"RelationProvider\", \"knowledgeBaseDescription\": \"A KB that provides relations between people\", \"reasonerEnabled\" : true}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerAnswerKb.expectStatus(200); + + // register the AnswerKI + HttpTester registerAnswerKi = new HttpTester(new URL(url + "/sc/ki"), "POST", + "{\"knowledgeInteractionType\": \"AnswerKnowledgeInteraction\", \"knowledgeInteractionName\": \"answerRelations\", \"graphPattern\": \"?a ?b .\"}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationProvider", "Content-Type", "application/json", "Accept", + "*/*")); + registerAnswerKi.expectStatus(200); + + // get the handle for the answerKB to see if there are requests to be handled + var test = new HttpTester(new URL(url.toString() + "/sc/handle"), "GET", null, Map + .of("Knowledge-Base-Id", answerKBId, "Content-Type", "application/json", "Accept", "*/*")); + test.expectStatus(200); + + // build the body to answer the request: add handle request ID and dummy data bindingset + JsonObjectBuilder builder = Json.createObjectBuilder(); + JsonReader jp = Json.createReader(new StringReader(test.getBody())); + JsonObject jo = jp.readObject(); + int handleRequestId = jo.getInt("handleRequestId"); + builder.add("handleRequestId", handleRequestId); + JsonReader jr = Json.createReader(new StringReader("[{\"a\": \"\",\"b\": \"\"}]")); + JsonArray bs = jr.readArray(); + builder.add("bindingSet", bs); + JsonObject jo2 = builder.build(); + String body = jo2.toString(); + System.out.println("Handle an answer to a request with body: " + body); + + // fire the POST handle to execute the answer + var test2 = new HttpTester(new URL(url.toString() + "/sc/handle"), "POST", body, + Map.of("Knowledge-Base-Id", answerKBId, "Knowledge-Interaction-Id", answerKIId, "Content-Type", + "application/json", "Accept", "*/*")); + test2.expectStatus(200); + + } catch (MalformedURLException e) { + fail(); + } + } + }); + answeringSc.start(); + + // register the AskKB + HttpTester registerKb = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"https://www.tno.nl/example/relationAsker\", \"knowledgeBaseName\": \"RelationAsker\", \"knowledgeBaseDescription\": \"A KB that asks for relations between people\", \"reasonerEnabled\" : true}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerKb.expectStatus(200); + + // register the AskKI + HttpTester registerKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "POST", + "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", \"knowledgeInteractionName\": \"askRelations\", \"graphPattern\": \"?a ?b . ?a ?c .\", \"knowledgeGapsEnabled\": true}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", "application/json", "Accept", + "*/*")); + registerKiWithoutGapsEnabled.expectStatus(200); + + // fire the ask KI + HttpTester askKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ask"), "POST", + "{\"recipientSelector\": {\"knowledgeBases\": []}, \"bindingSet\": []}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Knowledge-Interaction-Id", "https://www.tno.nl/example/relationAsker/interaction/askRelations", + "Content-Type", "application/json", "Accept", + "*/*")); + var result = askKiWithoutGapsEnabled.getBody(); + System.out.println("Result is:" +result); + assertTrue(result.contains("\"knowledgeGaps\":[[\"?a ?c\"]]")); + + } + + @AfterAll + public void cleanUp() { + rsh.cleanUp(); + } +} diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java new file mode 100644 index 00000000..41fbe393 --- /dev/null +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java @@ -0,0 +1,69 @@ +package eu.knowledge.engine.rest.api; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; + +import org.apache.jena.atlas.logging.Log; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import eu.knowledge.engine.rest.RestServerHelper; +import eu.knowledge.engine.test_utils.HttpTester; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestAskWithGapsEnabled { + private final RestServerHelper rsh = new RestServerHelper(); + private static int PORT = 8280; + + @BeforeAll + public void setUpServer() { + rsh.start(PORT); + } + + @Test + public void testAskWithGaps() throws IOException { + + // In this test there will be only 1 KB with a single AskKI. + // The test will execute the AskKI with knowledge gaps enabled. + // As a result, the set of knowledge gaps should contain a single gap. + + URL url = new URL("http://localhost:" + PORT + "/rest"); + + HttpTester registerKb = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"https://www.tno.nl/example/relationAsker\", \"knowledgeBaseName\": \"RelationAsker\", \"knowledgeBaseDescription\": \"A KB that asks for relations between people\", \"reasonerEnabled\" : true}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerKb.expectStatus(200); + + HttpTester registerKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "POST", + "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", \"knowledgeInteractionName\": \"askRelations\", \"graphPattern\": \"?a ?b .\", \"knowledgeGapsEnabled\": true}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", "application/json", "Accept", + "*/*")); + registerKiWithoutGapsEnabled.expectStatus(200); + + HttpTester getKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "GET", null, Map.of("Knowledge-Base-Id", + "https://www.tno.nl/example/relationAsker", "Content-Type", "application/json", "Accept", "*/*")); + var body = getKiWithoutGapsEnabled.getBody(); + assertTrue(body.contains("\"https://www.tno.nl/example/relationAsker/interaction/askRelations\"")); + + HttpTester askKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ask"), "POST", + "{\"recipientSelector\": {\"knowledgeBases\": []}, \"bindingSet\": []}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Knowledge-Interaction-Id", "https://www.tno.nl/example/relationAsker/interaction/askRelations", + "Content-Type", "application/json", "Accept", + "*/*")); + var result = askKiWithoutGapsEnabled.getBody(); + System.out.println(result); + assertTrue(result.contains("\"knowledgeGaps\":[[\"?a ?b\"]]")); + } + + @AfterAll + public void cleanUp() { + rsh.cleanUp(); + } +} From f375823f8f83422a0cc86fa9e1598a6d5eb5fd51 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Mon, 21 Oct 2024 15:51:49 +0200 Subject: [PATCH 36/64] replaced the knowledgeGapsEnabled option description --- .../src/main/resources/openapi-sc.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml index f99dfc18..7853dc19 100644 --- a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml +++ b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml @@ -180,6 +180,13 @@ paths: type: string requestBody: required: true + description: + The body of the ASK can contain the option "knowledgeGapEnabled" + (See the example "ASK with knowledge gaps enabled"). + This option is disabled by default. If enabled, each activation of the knowledge interaction will + provide a set of knowledge gaps as part of the result. If this set is empty, the binding set of the + result contains the answer. If the set of knowledge gaps is not empty, this contains one or more + knowledge gaps that need to be fixed for the knowledge interaction to produce an answer. content: application/json; charset=UTF-8: schema: From 5bf4250ce16dee90ea4c0bd06441fd208b3418bb Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Tue, 5 Nov 2024 14:39:49 +0100 Subject: [PATCH 37/64] Make sure all REST API unit tests remove their KBs at the end. This prevents subsequent tests to fail because the KBs they try to register already exist. This problem occurs when all the Unit Tests are run within the same JVM and KE Runtime. With this fix, this no longer causes problems. --- .../TestAskAnswerReactWithGapsEnabled.java | 67 +++-- .../api/TestAskAnswerWithGapsEnabled.java | 42 +-- .../rest/api/TestAskWithGapsEnabled.java | 21 +- .../rest/api/TestAskWithGapsNotEnabled.java | 21 +- .../engine/rest/api/TestPostMemoryLeak.java | 1 + .../engine/rest/api/TestQuickScPosts.java | 43 +-- .../api/TestRegisterKnowledgeInteraction.java | 1 + .../engine/rest/api/TestScLifeCycle.java | 14 +- .../rest/api/TestSuspendedKnowledgeBase.java | 283 ++++++++---------- .../knowledge/engine/rest/api/TestUtil.java | 53 ++++ 10 files changed, 281 insertions(+), 265 deletions(-) create mode 100644 smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestUtil.java diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java index 50ed929a..48be7d47 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java @@ -1,6 +1,5 @@ package eu.knowledge.engine.rest.api; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -38,8 +37,10 @@ public void setUpServer() { public void testAskAnswerReactWithGaps() throws IOException { // In this test there will be an Ask KB with an AskKI with 2 triplepatterns, - // an AnswerKB with a single AnswerKI that answers only the first triplepattern of the Ask pattern, and - // a ReactKB that can answer the other triplepattern of the Ask, but needs another pattern to be satisfied. + // an AnswerKB with a single AnswerKI that answers only the first triplepattern + // of the Ask pattern, and + // a ReactKB that can answer the other triplepattern of the Ask, but needs + // another pattern to be satisfied. // The test will execute the AskKI with knowledge gaps enabled. // As a result, the set of knowledge gaps should contain a single gap. @@ -65,7 +66,8 @@ public void run() { "knowledgeInteractionName": "answerRelations", "graphPattern": "?a ?b ." } - """, Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationProvider", "Content-Type", "application/json", "Accept", "*/*")); + """, Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationProvider", + "Content-Type", "application/json", "Accept", "*/*")); registerAnswerKi.expectStatus(200); // get the handle for the answerKB to see if there are requests to be handled @@ -73,13 +75,15 @@ public void run() { .of("Knowledge-Base-Id", answerKBId, "Content-Type", "application/json", "Accept", "*/*")); test.expectStatus(200); - // build the body to answer the request: add handle request ID and dummy data bindingset + // build the body to answer the request: add handle request ID and dummy data + // bindingset JsonObjectBuilder builder = Json.createObjectBuilder(); JsonReader jp = Json.createReader(new StringReader(test.getBody())); JsonObject jo = jp.readObject(); int handleRequestId = jo.getInt("handleRequestId"); builder.add("handleRequestId", handleRequestId); - JsonReader jr = Json.createReader(new StringReader("[{\"a\": \"\",\"b\": \"\"}]")); + JsonReader jr = Json.createReader(new StringReader( + "[{\"a\": \"\",\"b\": \"\"}]")); JsonArray bs = jr.readArray(); builder.add("bindingSet", bs); JsonObject jo2 = builder.build(); @@ -88,8 +92,8 @@ public void run() { // fire the POST handle to execute the answer var test2 = new HttpTester(new URL(url.toString() + "/sc/handle"), "POST", body, - Map.of("Knowledge-Base-Id", answerKBId, "Knowledge-Interaction-Id", answerKIId, "Content-Type", - "application/json", "Accept", "*/*")); + Map.of("Knowledge-Base-Id", answerKBId, "Knowledge-Interaction-Id", answerKIId, + "Content-Type", "application/json", "Accept", "*/*")); test2.expectStatus(200); } catch (MalformedURLException e) { @@ -98,7 +102,7 @@ public void run() { } }); answeringSc.start(); - + // activate the answer SC, KB, KI in a separate thread var reactingSc = new AsyncTester(new Runnable() { @Override @@ -120,17 +124,19 @@ public void run() { "argumentGraphPattern": "?a ?b .", "resultGraphPattern": "?a ?b ." } - """, Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationReactor", "Content-Type", "application/json", "Accept", "*/*")); + """, Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationReactor", + "Content-Type", "application/json", "Accept", "*/*")); registerReactKi.expectStatus(200); System.out.println("Getting the handle for the reactKBId"); - // get the handle for the reactKB to see if there are requests to be handled => + // get the handle for the reactKB to see if there are requests to be handled => // NOTE: it should never exit/return this handle for this test - var test = new HttpTester(new URL(url.toString() + "/sc/handle"), "GET", null, - Map.of("Knowledge-Base-Id", reactKBId, "Content-Type", "application/json", "Accept", "*/*")); + var test = new HttpTester(new URL(url.toString() + "/sc/handle"), "GET", null, Map + .of("Knowledge-Base-Id", reactKBId, "Content-Type", "application/json", "Accept", "*/*")); test.expectStatus(200); - - // build the body to react to the request: add handle request ID and dummy data bindingset + + // build the body to react to the request: add handle request ID and dummy data + // bindingset JsonObjectBuilder builder = Json.createObjectBuilder(); JsonReader jp = Json.createReader(new StringReader(test.getBody())); JsonObject jo = jp.readObject(); @@ -144,8 +150,8 @@ public void run() { // fire the POST handle to execute the react var test2 = new HttpTester(new URL(url.toString() + "/sc/handle"), "POST", body, - Map.of("Knowledge-Base-Id", reactKBId, "Knowledge-Interaction-Id", reactKIId, "Content-Type", - "application/json", "Accept", "*/*")); + Map.of("Knowledge-Base-Id", reactKBId, "Knowledge-Interaction-Id", reactKIId, + "Content-Type", "application/json", "Accept", "*/*")); test2.expectStatus(200); } catch (MalformedURLException e) { @@ -154,7 +160,7 @@ public void run() { } }); reactingSc.start(); - + // register the AskKB HttpTester registerKb = new HttpTester(new URL(url + "/sc"), "POST", "{\"knowledgeBaseId\": \"https://www.tno.nl/example/relationAsker\", \"knowledgeBaseName\": \"RelationAsker\", \"knowledgeBaseDescription\": \"A KB that asks for relations between people\", \"reasonerEnabled\" : true}", @@ -169,25 +175,30 @@ public void run() { "graphPattern": "?a ?b . ?a ?c .", "knowledgeGapsEnabled": true } - """, Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", "application/json", "Accept", - "*/*")); + """, Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", + "application/json", "Accept", "*/*")); registerKiWithoutGapsEnabled.expectStatus(200); // fire the ask KI HttpTester askKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ask"), "POST", "{\"recipientSelector\": {\"knowledgeBases\": []}, \"bindingSet\": []}", - Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Knowledge-Interaction-Id", "https://www.tno.nl/example/relationAsker/interaction/askRelations", - "Content-Type", "application/json", "Accept", - "*/*")); + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Knowledge-Interaction-Id", + "https://www.tno.nl/example/relationAsker/interaction/askRelations", "Content-Type", + "application/json", "Accept", "*/*")); var result = askKiWithoutGapsEnabled.getBody(); - System.out.println("Result is:" +result); - assertTrue(result.contains("\"knowledgeGaps\":[[\"?a ?c\",\"?a ?b\"]]")); - assertTrue(result.contains("\"bindingSet\":[]")); - + System.out.println("Result is:" + result); + assertTrue(result.contains( + "\"knowledgeGaps\":[[\"?a ?c\",\"?a ?b\"]]")); + assertTrue(result.contains("\"bindingSet\":[]")); + } @AfterAll - public void cleanUp() { + public void cleanUp() throws IOException { + + TestUtil.unregisterAllKBs("http://localhost:" + PORT + "/rest"); + rsh.cleanUp(); } + } diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabled.java index bb424da7..8a3c123e 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabled.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabled.java @@ -1,6 +1,5 @@ package eu.knowledge.engine.rest.api; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -9,12 +8,9 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Map; -import java.util.concurrent.CountDownLatch; -import org.apache.jena.atlas.logging.Log; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -26,7 +22,6 @@ import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; import jakarta.json.JsonReader; -import jakarta.json.JsonValue; @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class TestAskAnswerWithGapsEnabled { @@ -64,8 +59,8 @@ public void run() { // register the AnswerKI HttpTester registerAnswerKi = new HttpTester(new URL(url + "/sc/ki"), "POST", "{\"knowledgeInteractionType\": \"AnswerKnowledgeInteraction\", \"knowledgeInteractionName\": \"answerRelations\", \"graphPattern\": \"?a ?b .\"}", - Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationProvider", "Content-Type", "application/json", "Accept", - "*/*")); + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationProvider", "Content-Type", + "application/json", "Accept", "*/*")); registerAnswerKi.expectStatus(200); // get the handle for the answerKB to see if there are requests to be handled @@ -73,13 +68,15 @@ public void run() { .of("Knowledge-Base-Id", answerKBId, "Content-Type", "application/json", "Accept", "*/*")); test.expectStatus(200); - // build the body to answer the request: add handle request ID and dummy data bindingset + // build the body to answer the request: add handle request ID and dummy data + // bindingset JsonObjectBuilder builder = Json.createObjectBuilder(); JsonReader jp = Json.createReader(new StringReader(test.getBody())); JsonObject jo = jp.readObject(); int handleRequestId = jo.getInt("handleRequestId"); builder.add("handleRequestId", handleRequestId); - JsonReader jr = Json.createReader(new StringReader("[{\"a\": \"\",\"b\": \"\"}]")); + JsonReader jr = Json.createReader(new StringReader( + "[{\"a\": \"\",\"b\": \"\"}]")); JsonArray bs = jr.readArray(); builder.add("bindingSet", bs); JsonObject jo2 = builder.build(); @@ -88,8 +85,8 @@ public void run() { // fire the POST handle to execute the answer var test2 = new HttpTester(new URL(url.toString() + "/sc/handle"), "POST", body, - Map.of("Knowledge-Base-Id", answerKBId, "Knowledge-Interaction-Id", answerKIId, "Content-Type", - "application/json", "Accept", "*/*")); + Map.of("Knowledge-Base-Id", answerKBId, "Knowledge-Interaction-Id", answerKIId, + "Content-Type", "application/json", "Accept", "*/*")); test2.expectStatus(200); } catch (MalformedURLException e) { @@ -98,7 +95,7 @@ public void run() { } }); answeringSc.start(); - + // register the AskKB HttpTester registerKb = new HttpTester(new URL(url + "/sc"), "POST", "{\"knowledgeBaseId\": \"https://www.tno.nl/example/relationAsker\", \"knowledgeBaseName\": \"RelationAsker\", \"knowledgeBaseDescription\": \"A KB that asks for relations between people\", \"reasonerEnabled\" : true}", @@ -108,24 +105,27 @@ public void run() { // register the AskKI HttpTester registerKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "POST", "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", \"knowledgeInteractionName\": \"askRelations\", \"graphPattern\": \"?a ?b . ?a ?c .\", \"knowledgeGapsEnabled\": true}", - Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", "application/json", "Accept", - "*/*")); + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", + "application/json", "Accept", "*/*")); registerKiWithoutGapsEnabled.expectStatus(200); // fire the ask KI HttpTester askKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ask"), "POST", "{\"recipientSelector\": {\"knowledgeBases\": []}, \"bindingSet\": []}", - Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Knowledge-Interaction-Id", "https://www.tno.nl/example/relationAsker/interaction/askRelations", - "Content-Type", "application/json", "Accept", - "*/*")); + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Knowledge-Interaction-Id", + "https://www.tno.nl/example/relationAsker/interaction/askRelations", "Content-Type", + "application/json", "Accept", "*/*")); var result = askKiWithoutGapsEnabled.getBody(); - System.out.println("Result is:" +result); - assertTrue(result.contains("\"knowledgeGaps\":[[\"?a ?c\"]]")); - + System.out.println("Result is:" + result); + assertTrue(result.contains("\"knowledgeGaps\":[[\"?a ?c\"]]")); + } @AfterAll - public void cleanUp() { + public void cleanUp() throws MalformedURLException { + + TestUtil.unregisterAllKBs("http://localhost:" + PORT + "/rest"); rsh.cleanUp(); } + } diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java index 41fbe393..87b5348f 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java @@ -2,8 +2,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URL; import java.util.Map; @@ -43,20 +45,21 @@ public void testAskWithGaps() throws IOException { HttpTester registerKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "POST", "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", \"knowledgeInteractionName\": \"askRelations\", \"graphPattern\": \"?a ?b .\", \"knowledgeGapsEnabled\": true}", - Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", "application/json", "Accept", - "*/*")); + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", + "application/json", "Accept", "*/*")); registerKiWithoutGapsEnabled.expectStatus(200); - HttpTester getKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "GET", null, Map.of("Knowledge-Base-Id", - "https://www.tno.nl/example/relationAsker", "Content-Type", "application/json", "Accept", "*/*")); + HttpTester getKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "GET", null, + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", + "application/json", "Accept", "*/*")); var body = getKiWithoutGapsEnabled.getBody(); assertTrue(body.contains("\"https://www.tno.nl/example/relationAsker/interaction/askRelations\"")); - + HttpTester askKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ask"), "POST", "{\"recipientSelector\": {\"knowledgeBases\": []}, \"bindingSet\": []}", - Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Knowledge-Interaction-Id", "https://www.tno.nl/example/relationAsker/interaction/askRelations", - "Content-Type", "application/json", "Accept", - "*/*")); + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Knowledge-Interaction-Id", + "https://www.tno.nl/example/relationAsker/interaction/askRelations", "Content-Type", + "application/json", "Accept", "*/*")); var result = askKiWithoutGapsEnabled.getBody(); System.out.println(result); assertTrue(result.contains("\"knowledgeGaps\":[[\"?a ?b\"]]")); @@ -64,6 +67,8 @@ public void testAskWithGaps() throws IOException { @AfterAll public void cleanUp() { + TestUtil.unregisterAllKBs("http://localhost:" + PORT + "/rest"); rsh.cleanUp(); } + } diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java index 61aee40e..9a13d629 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java @@ -2,8 +2,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URL; import java.util.Map; @@ -43,26 +45,29 @@ public void testAskWithoutGaps() throws IOException { HttpTester registerKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "POST", "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", \"knowledgeInteractionName\": \"askRelations\", \"graphPattern\": \"?a ?b .\"}", - Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", "application/json", "Accept", - "*/*")); + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", + "application/json", "Accept", "*/*")); registerKiWithoutGapsEnabled.expectStatus(200); - HttpTester getKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "GET", null, Map.of("Knowledge-Base-Id", - "https://www.tno.nl/example/relationAsker", "Content-Type", "application/json", "Accept", "*/*")); + HttpTester getKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "GET", null, + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", + "application/json", "Accept", "*/*")); var body = getKiWithoutGapsEnabled.getBody(); assertTrue(body.contains("\"https://www.tno.nl/example/relationAsker/interaction/askRelations\"")); - + HttpTester askKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ask"), "POST", "{\"recipientSelector\": {\"knowledgeBases\": []}, \"bindingSet\": []}", - Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Knowledge-Interaction-Id", "https://www.tno.nl/example/relationAsker/interaction/askRelations", - "Content-Type", "application/json", "Accept", - "*/*")); + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Knowledge-Interaction-Id", + "https://www.tno.nl/example/relationAsker/interaction/askRelations", "Content-Type", + "application/json", "Accept", "*/*")); var result = askKiWithoutGapsEnabled.getBody(); assertFalse(result.contains("\"knowledgeGaps\":")); } @AfterAll public void cleanUp() { + TestUtil.unregisterAllKBs("http://localhost:" + PORT + "/rest"); + rsh.cleanUp(); } } diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestPostMemoryLeak.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestPostMemoryLeak.java index b3e88b25..65e25d2a 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestPostMemoryLeak.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestPostMemoryLeak.java @@ -177,6 +177,7 @@ public void run() { @AfterAll public void cleanUp() { + TestUtil.unregisterAllKBs("http://localhost:" + PORT + "/rest"); rsh.cleanUp(); } } diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestQuickScPosts.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestQuickScPosts.java index d3add0cb..797c0544 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestQuickScPosts.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestQuickScPosts.java @@ -32,7 +32,7 @@ public void testPostScsSimultaneously() throws IOException { // You can make the test pass consistently by uncommenting the following: // new HttpTester(url, "GET", null, Map.of( - // "Accept", "*/*" + // "Accept", "*/*" // )).expectStatus(200); // This latch is used to make the threads synchronize before they will @@ -58,12 +58,9 @@ public void run() { } new HttpTester(url, "POST", - "{\"knowledgeBaseId\": \""+ kb1Id + "\", \"knowledgeBaseName\": \"KB1\", \"knowledgeBaseDescription\": \"KB1\"}", - Map.of( - "Content-Type", "application/json", - "Accept", "*/*" - ) - ).expectStatus(200); + "{\"knowledgeBaseId\": \"" + kb1Id + + "\", \"knowledgeBaseName\": \"KB1\", \"knowledgeBaseDescription\": \"KB1\"}", + Map.of("Content-Type", "application/json", "Accept", "*/*")).expectStatus(200); afterRegister.countDown(); try { @@ -72,15 +69,9 @@ public void run() { fail(); } - new HttpTester(url, "GET", null, Map.of( - "Accept", "*/*", - "Knowledge-Base-Id", kb1Id - )).expectStatus(200); + new HttpTester(url, "GET", null, Map.of("Accept", "*/*", "Knowledge-Base-Id", kb1Id)).expectStatus(200); - new HttpTester(url, "GET", null, Map.of( - "Accept", "*/*", - "Knowledge-Base-Id", kb2Id - )).expectStatus(200); + new HttpTester(url, "GET", null, Map.of("Accept", "*/*", "Knowledge-Base-Id", kb2Id)).expectStatus(200); } }); @@ -95,13 +86,10 @@ public void run() { } new HttpTester(url, "POST", - "{\"knowledgeBaseId\": \""+ kb2Id + "\", \"knowledgeBaseName\": \"KB1\", \"knowledgeBaseDescription\": \"KB1\"}", - Map.of( - "Content-Type", "application/json", - "Accept", "*/*" - ) - ).expectStatus(200); - + "{\"knowledgeBaseId\": \"" + kb2Id + + "\", \"knowledgeBaseName\": \"KB1\", \"knowledgeBaseDescription\": \"KB1\"}", + Map.of("Content-Type", "application/json", "Accept", "*/*")).expectStatus(200); + afterRegister.countDown(); try { afterRegister.await(); @@ -109,15 +97,9 @@ public void run() { fail(); } - new HttpTester(url, "GET", null, Map.of( - "Accept", "*/*", - "Knowledge-Base-Id", kb1Id - )).expectStatus(200); + new HttpTester(url, "GET", null, Map.of("Accept", "*/*", "Knowledge-Base-Id", kb1Id)).expectStatus(200); - new HttpTester(url, "GET", null, Map.of( - "Accept", "*/*", - "Knowledge-Base-Id", kb2Id - )).expectStatus(200); + new HttpTester(url, "GET", null, Map.of("Accept", "*/*", "Knowledge-Base-Id", kb2Id)).expectStatus(200); } }); @@ -131,6 +113,7 @@ public void run() { @AfterAll public void cleanUp() { + TestUtil.unregisterAllKBs("http://localhost:" + PORT + "/rest"); rsh.cleanUp(); } } diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteraction.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteraction.java index 2d2b5c22..ea135efc 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteraction.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteraction.java @@ -50,6 +50,7 @@ public void testRegisterKi() throws IOException { @AfterAll public void cleanUp() { + TestUtil.unregisterAllKBs("http://localhost:" + PORT + "/rest"); rsh.cleanUp(); } } diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestScLifeCycle.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestScLifeCycle.java index 1bfc69d7..99db4193 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestScLifeCycle.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestScLifeCycle.java @@ -26,10 +26,8 @@ public void setUpServer() { public void testInvalidJson() throws IOException { URL url = new URL("http://localhost:" + PORT + "/rest/sc"); - HttpTester httpTest = new HttpTester(url, "POST", "{\"bla\"{}", Map.of( - "Content-Type", "application/json", - "Accept", "*/*" - )); + HttpTester httpTest = new HttpTester(url, "POST", "{\"bla\"{}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); httpTest.expectStatus(400); } @@ -37,15 +35,15 @@ public void testInvalidJson() throws IOException { public void testValidJson() throws IOException { URL url = new URL("http://localhost:" + PORT + "/rest/sc"); - HttpTester httpTest = new HttpTester(url, "POST", "{\"knowledgeBaseId\": \"http://example.com/kb\", \"knowledgeBaseName\": \"KB\", \"knowledgeBaseDescription\": \"KB\"}", Map.of( - "Content-Type", "application/json", - "Accept", "*/*" - )); + HttpTester httpTest = new HttpTester(url, "POST", + "{\"knowledgeBaseId\": \"http://example.com/kb\", \"knowledgeBaseName\": \"KB\", \"knowledgeBaseDescription\": \"KB\"}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); httpTest.expectStatus(200); } @AfterAll public void cleanUp() { + TestUtil.unregisterAllKBs("http://localhost:" + PORT + "/rest"); rsh.cleanUp(); } } diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestSuspendedKnowledgeBase.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestSuspendedKnowledgeBase.java index 7b14f0ff..d739de31 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestSuspendedKnowledgeBase.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestSuspendedKnowledgeBase.java @@ -13,166 +13,125 @@ @Disabled @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class TestSuspendedKnowledgeBase { - int NUM_UNHANDLED_REQUESTS = 5; - CountDownLatch latch = new CountDownLatch(NUM_UNHANDLED_REQUESTS); - private final RestServerHelper rsh = new RestServerHelper(); - private static final int PORT = 8280; - - @BeforeAll - public void setUpServer() { - rsh.start(PORT); - } - - @Test - public void testSuspendingKnowledgeBase() throws MalformedURLException { - URL url = new URL("http://localhost:" + PORT + "/rest"); - HttpTester registerKb = new HttpTester(new URL(url + "/sc"), - "POST", - "{\"knowledgeBaseId\": \"http://example.com/kb1\", " + - "\"knowledgeBaseName\": \"KB1\", " + - "\"knowledgeBaseDescription\": \"KB1\"}", Map.of( - "Content-Type", "application/json", - "Accept", "*/*" - )); - registerKb.expectStatus(200); - HttpTester registerKb2 = new HttpTester(new URL(url + "/sc"), - "POST", - "{\"knowledgeBaseId\": \"http://example.com/kb2\", " + - "\"knowledgeBaseName\": \"KB2\", " + - "\"knowledgeBaseDescription\": \"KB2\"}", Map.of( - "Content-Type", "application/json", - "Accept", "*/*" - )); - registerKb2.expectStatus(200); - - - var sc1 = new AsyncTester(() -> { - try { - HttpTester registerAsk = new HttpTester(new URL(url + "/sc/ki"), - "POST", - "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", " + - "\"knowledgeInteractionName\": \"ask\"," + - "\"graphPattern\": \"?a ?b ?c.\"}", - Map.of( - "Knowledge-Base-Id", "http://example.com/kb1", - "Content-Type", "application/json", - "Accept", "*/*" - ) - ); - registerAsk.expectStatus(200); - System.out.println("Registered Ask"); - HttpTester executeAsk = new HttpTester(new URL(url + "/sc/ask"), - "POST", "[{}]", - Map.of( - "Knowledge-Base-Id", "http://example.com/kb1", - "Knowledge-Interaction-Id", "http://example.com/kb1/interaction/ask", - "Content-Type", "application/json", - "Accept", "*/*" - ) - ); - System.out.println("Executing Ask"); - var body = executeAsk.getBody(); - System.out.println(body); - - for (int i = 0; i < NUM_UNHANDLED_REQUESTS; i++) { - System.out.println("Making another ask"); - new AsyncTester(() -> { - HttpTester executeAsk1; - try { - executeAsk1 = new HttpTester(new URL(url + "/sc/ask"), - "POST", "[{}]", - Map.of( - "Knowledge-Base-Id", "http://example.com/kb1", - "Knowledge-Interaction-Id", "http://example.com/kb1/interaction/ask", - "Content-Type", "application/json", - "Accept", "*/*" - ) - ); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - System.out.println("Executing Ask"); - latch.countDown(); - var body1 = executeAsk1.getBody(); - System.out.println(body1); - }).start(); - } - } catch (MalformedURLException e) { - System.err.println(e.getMessage()); - } - }); - - var sc2 = new AsyncTester(() -> { - try { - HttpTester registerAnswer = new HttpTester(new URL(url + "/sc/ki"), - "POST", - "{\"knowledgeInteractionType\": \"AnswerKnowledgeInteraction\", " + - "\"knowledgeInteractionName\": \"answer\"," + - "\"graphPattern\": \"?a ?b ?c.\"}", - Map.of( - "Knowledge-Base-Id", "http://example.com/kb2", - "Content-Type", "application/json", - "Accept", "*/*" - ) - ); - registerAnswer.expectStatus(200); - System.out.println("Registered Answer"); - - HttpTester waitForRequest = new HttpTester(new URL(url + "/sc/handle"), - "GET", null, - Map.of( - "Knowledge-Base-Id", "http://example.com/kb2", - "Content-Type", "application/json", - "Accept", "*/*" - ) - ); - var body = waitForRequest.getBody(); - System.out.println(body); - - System.out.println("Waiting for requests"); - var postAnswer = new HttpTester(new URL(url + "/sc/handle"), "POST", """ - { - "handleRequestId": 1, - "bindingSet": [{ - "a": "", - "b": "", - "c": "" - }] - } - """, - Map.of("Content-Type", "application/json", - "Accept", "*/*", - "Knowledge-Base-Id", "http://example.com/kb2", - "Knowledge-Interaction-Id", "http://example.com/kb2/interaction/answer")); - postAnswer.getBody(); - System.out.println("Posted answer"); - - latch.await(); - - HttpTester deleteSC = new HttpTester(new URL(url + "/sc/"), - "DELETE", "", - Map.of( - "Knowledge-Base-Id", "http://example.com/kb2", - "Content-Type", "application/json", - "Accept", "*/*" - )); - deleteSC.expectStatus(200); - } catch (MalformedURLException e) { - System.err.println(e.getMessage()); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - }); - - sc1.start(); - sc2.start(); - sc1.joinAndRethrow(); - sc2.joinAndRethrow(); - - } - - @AfterAll - public void cleanUp() { - rsh.cleanUp(); - } + int NUM_UNHANDLED_REQUESTS = 5; + CountDownLatch latch = new CountDownLatch(NUM_UNHANDLED_REQUESTS); + private final RestServerHelper rsh = new RestServerHelper(); + private static final int PORT = 8280; + + @BeforeAll + public void setUpServer() { + rsh.start(PORT); + } + + @Test + public void testSuspendingKnowledgeBase() throws MalformedURLException { + URL url = new URL("http://localhost:" + PORT + "/rest"); + HttpTester registerKb = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"http://example.com/kb1\", " + "\"knowledgeBaseName\": \"KB1\", " + + "\"knowledgeBaseDescription\": \"KB1\"}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerKb.expectStatus(200); + HttpTester registerKb2 = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"http://example.com/kb2\", " + "\"knowledgeBaseName\": \"KB2\", " + + "\"knowledgeBaseDescription\": \"KB2\"}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerKb2.expectStatus(200); + + var sc1 = new AsyncTester(() -> { + try { + HttpTester registerAsk = new HttpTester(new URL(url + "/sc/ki"), "POST", + "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", " + + "\"knowledgeInteractionName\": \"ask\"," + "\"graphPattern\": \"?a ?b ?c.\"}", + Map.of("Knowledge-Base-Id", "http://example.com/kb1", "Content-Type", "application/json", + "Accept", "*/*")); + registerAsk.expectStatus(200); + System.out.println("Registered Ask"); + HttpTester executeAsk = new HttpTester(new URL(url + "/sc/ask"), "POST", "[{}]", + Map.of("Knowledge-Base-Id", "http://example.com/kb1", "Knowledge-Interaction-Id", + "http://example.com/kb1/interaction/ask", "Content-Type", "application/json", "Accept", + "*/*")); + System.out.println("Executing Ask"); + var body = executeAsk.getBody(); + System.out.println(body); + + for (int i = 0; i < NUM_UNHANDLED_REQUESTS; i++) { + System.out.println("Making another ask"); + new AsyncTester(() -> { + HttpTester executeAsk1; + try { + executeAsk1 = new HttpTester(new URL(url + "/sc/ask"), "POST", "[{}]", + Map.of("Knowledge-Base-Id", "http://example.com/kb1", "Knowledge-Interaction-Id", + "http://example.com/kb1/interaction/ask", "Content-Type", + "application/json", "Accept", "*/*")); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + System.out.println("Executing Ask"); + latch.countDown(); + var body1 = executeAsk1.getBody(); + System.out.println(body1); + }).start(); + } + } catch (MalformedURLException e) { + System.err.println(e.getMessage()); + } + }); + + var sc2 = new AsyncTester(() -> { + try { + HttpTester registerAnswer = new HttpTester(new URL(url + "/sc/ki"), "POST", + "{\"knowledgeInteractionType\": \"AnswerKnowledgeInteraction\", " + + "\"knowledgeInteractionName\": \"answer\"," + "\"graphPattern\": \"?a ?b ?c.\"}", + Map.of("Knowledge-Base-Id", "http://example.com/kb2", "Content-Type", "application/json", + "Accept", "*/*")); + registerAnswer.expectStatus(200); + System.out.println("Registered Answer"); + + HttpTester waitForRequest = new HttpTester(new URL(url + "/sc/handle"), "GET", null, + Map.of("Knowledge-Base-Id", "http://example.com/kb2", "Content-Type", "application/json", + "Accept", "*/*")); + var body = waitForRequest.getBody(); + System.out.println(body); + + System.out.println("Waiting for requests"); + var postAnswer = new HttpTester(new URL(url + "/sc/handle"), "POST", """ + { + "handleRequestId": 1, + "bindingSet": [{ + "a": "", + "b": "", + "c": "" + }] + } + """, + Map.of("Content-Type", "application/json", "Accept", "*/*", "Knowledge-Base-Id", + "http://example.com/kb2", "Knowledge-Interaction-Id", + "http://example.com/kb2/interaction/answer")); + postAnswer.getBody(); + System.out.println("Posted answer"); + + latch.await(); + + HttpTester deleteSC = new HttpTester(new URL(url + "/sc/"), "DELETE", "", Map.of("Knowledge-Base-Id", + "http://example.com/kb2", "Content-Type", "application/json", "Accept", "*/*")); + deleteSC.expectStatus(200); + } catch (MalformedURLException e) { + System.err.println(e.getMessage()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + + sc1.start(); + sc2.start(); + sc1.joinAndRethrow(); + sc2.joinAndRethrow(); + + } + + @AfterAll + public void cleanUp() { + TestUtil.unregisterAllKBs("http://localhost:" + PORT + "/rest"); + rsh.cleanUp(); + } } diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestUtil.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestUtil.java new file mode 100644 index 00000000..623d1b6a --- /dev/null +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestUtil.java @@ -0,0 +1,53 @@ +package eu.knowledge.engine.rest.api; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; + +import eu.knowledge.engine.test_utils.HttpTester; +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonReader; +import jakarta.json.JsonValue; + +public class TestUtil { + + public static void unregisterAllKBs(String url) { + + HttpTester listKbs; + try { + listKbs = new HttpTester(new URL(url + "/sc"), "GET", null, null); + listKbs.expectStatus(200); + String body = listKbs.getBody(); + System.out.println("List: " + body); + + JsonReader jsonReader = Json.createReader(new StringReader(body)); + JsonArray KBs = jsonReader.readArray(); + + for (JsonValue jo : KBs) { + unregisterKb(url, jo.asJsonObject().getString("knowledgeBaseId")); + } + + } catch (MalformedURLException e) { + fail(); + e.printStackTrace(); + } + + } + + public static void unregisterKb(String url, String id) { + + try { + HttpTester deleteKb = new HttpTester(new URL(url + "/sc"), "DELETE", null, Map.of("Knowledge-Base-Id", id)); + deleteKb.expectStatus(200); + } catch (MalformedURLException e) { + e.printStackTrace(); + fail(); + + } + } + +} From 7dff8c8f0a114c573695be53f61d2757cb3b1b00 Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Tue, 5 Nov 2024 16:12:03 +0100 Subject: [PATCH 38/64] Added countdownlatch to prevent timing issues. The provider was not yet ready when the asker already asked its question, so the question returned a knowledge gap that was too big. --- .../engine/rest/api/TestAskAnswerWithGapsEnabled.java | 8 +++++++- .../test/java/eu/knowledge/engine/rest/api/TestUtil.java | 1 - 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabled.java index 8a3c123e..16541abc 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabled.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabled.java @@ -8,6 +8,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Map; +import java.util.concurrent.CountDownLatch; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -34,13 +35,15 @@ public void setUpServer() { } @Test - public void testAskAnswerWithGaps() throws IOException { + public void testAskAnswerWithGaps() throws IOException, InterruptedException { // In this test there will be an Ask KB with a single AskKI and // an AnswerKB with a single AnswerKI that answers only part of the Ask pattern. // The test will execute the AskKI with knowledge gaps enabled. // As a result, the set of knowledge gaps should contain a single gap. + CountDownLatch KBReady = new CountDownLatch(1); + URL url = new URL("http://localhost:" + PORT + "/rest"); // activate the answer SC, KB, KI in a separate thread @@ -63,6 +66,7 @@ public void run() { "application/json", "Accept", "*/*")); registerAnswerKi.expectStatus(200); + KBReady.countDown(); // get the handle for the answerKB to see if there are requests to be handled var test = new HttpTester(new URL(url.toString() + "/sc/handle"), "GET", null, Map .of("Knowledge-Base-Id", answerKBId, "Content-Type", "application/json", "Accept", "*/*")); @@ -96,12 +100,14 @@ public void run() { }); answeringSc.start(); + KBReady.await(); // register the AskKB HttpTester registerKb = new HttpTester(new URL(url + "/sc"), "POST", "{\"knowledgeBaseId\": \"https://www.tno.nl/example/relationAsker\", \"knowledgeBaseName\": \"RelationAsker\", \"knowledgeBaseDescription\": \"A KB that asks for relations between people\", \"reasonerEnabled\" : true}", Map.of("Content-Type", "application/json", "Accept", "*/*")); registerKb.expectStatus(200); + // register the AskKI HttpTester registerKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "POST", "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", \"knowledgeInteractionName\": \"askRelations\", \"graphPattern\": \"?a ?b . ?a ?c .\", \"knowledgeGapsEnabled\": true}", diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestUtil.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestUtil.java index 623d1b6a..bcbcb6f8 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestUtil.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestUtil.java @@ -22,7 +22,6 @@ public static void unregisterAllKBs(String url) { listKbs = new HttpTester(new URL(url + "/sc"), "GET", null, null); listKbs.expectStatus(200); String body = listKbs.getBody(); - System.out.println("List: " + body); JsonReader jsonReader = Json.createReader(new StringReader(body)); JsonArray KBs = jsonReader.readArray(); From cc8545d792d2828d6e27193d2f968f5ef010e1b1 Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Thu, 7 Nov 2024 11:39:34 +0100 Subject: [PATCH 39/64] Some minor fixes. Done: - fix variable name - add thread syncing --- .../api/ReactKnowledgeInteraction.java | 12 ++++++------ .../rest/api/TestAskAnswerReactWithGapsEnabled.java | 13 ++++++++++++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/ReactKnowledgeInteraction.java b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/ReactKnowledgeInteraction.java index e062217c..312bb412 100644 --- a/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/ReactKnowledgeInteraction.java +++ b/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/ReactKnowledgeInteraction.java @@ -41,18 +41,18 @@ public ReactKnowledgeInteraction(CommunicativeAct act, GraphPattern argument, Gr } public ReactKnowledgeInteraction(CommunicativeAct act, GraphPattern argument, GraphPattern result, - boolean anIsFullMatch) { - this(act, argument, result, null, false, anIsFullMatch, null); + boolean anIncludeMetaKIs) { + this(act, argument, result, null, false, anIncludeMetaKIs, null); } public ReactKnowledgeInteraction(CommunicativeAct act, GraphPattern argument, GraphPattern result, boolean anIsMeta, - boolean anIsFullMatch) { - this(act, argument, result, null, anIsMeta, anIsFullMatch, null); + boolean anIncludeMetaKIs) { + this(act, argument, result, null, anIsMeta, anIncludeMetaKIs, null); } public ReactKnowledgeInteraction(CommunicativeAct act, GraphPattern argument, GraphPattern result, String name, - boolean anIsMeta, boolean anIsFullMatch, MatchStrategy aMatchStrategy) { - super(act, name, anIsMeta, anIsFullMatch, false, aMatchStrategy); + boolean anIsMeta, boolean anIncludeMetaKIs, MatchStrategy aMatchStrategy) { + super(act, name, anIsMeta, anIncludeMetaKIs, false, aMatchStrategy); this.argument = argument; this.result = result; } diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java index 48be7d47..7a57de4a 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java @@ -8,6 +8,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Map; +import java.util.concurrent.CountDownLatch; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -34,7 +35,7 @@ public void setUpServer() { } @Test - public void testAskAnswerReactWithGaps() throws IOException { + public void testAskAnswerReactWithGaps() throws IOException, InterruptedException { // In this test there will be an Ask KB with an AskKI with 2 triplepatterns, // an AnswerKB with a single AnswerKI that answers only the first triplepattern @@ -46,6 +47,10 @@ public void testAskAnswerReactWithGaps() throws IOException { URL url = new URL("http://localhost:" + PORT + "/rest"); + // sync between threads to make sure the ask is not activated before the others + // are ready. + CountDownLatch KBReady = new CountDownLatch(2); + // activate the answer SC, KB, KI in a separate thread var answeringSc = new AsyncTester(new Runnable() { @Override @@ -70,6 +75,8 @@ public void run() { "Content-Type", "application/json", "Accept", "*/*")); registerAnswerKi.expectStatus(200); + KBReady.countDown(); + // get the handle for the answerKB to see if there are requests to be handled var test = new HttpTester(new URL(url.toString() + "/sc/handle"), "GET", null, Map .of("Knowledge-Base-Id", answerKBId, "Content-Type", "application/json", "Accept", "*/*")); @@ -128,6 +135,8 @@ public void run() { "Content-Type", "application/json", "Accept", "*/*")); registerReactKi.expectStatus(200); + KBReady.countDown(); + System.out.println("Getting the handle for the reactKBId"); // get the handle for the reactKB to see if there are requests to be handled => // NOTE: it should never exit/return this handle for this test @@ -161,6 +170,8 @@ public void run() { }); reactingSc.start(); + KBReady.await(); + // register the AskKB HttpTester registerKb = new HttpTester(new URL(url + "/sc"), "POST", "{\"knowledgeBaseId\": \"https://www.tno.nl/example/relationAsker\", \"knowledgeBaseName\": \"RelationAsker\", \"knowledgeBaseDescription\": \"A KB that asks for relations between people\", \"reasonerEnabled\" : true}", From 19f233994e10fc9dc9943af843668fe6b1c5a78d Mon Sep 17 00:00:00 2001 From: lathouwerssam Date: Fri, 8 Nov 2024 11:32:05 +0100 Subject: [PATCH 40/64] Add page for common errors and add info to KI page --- docs/docs/faq.md | 27 ------- .../get-started/knowledge-interactions.md | 77 ++++++++++++++++++- docs/docs/tke-errors.md | 13 ++++ 3 files changed, 86 insertions(+), 31 deletions(-) create mode 100644 docs/docs/tke-errors.md diff --git a/docs/docs/faq.md b/docs/docs/faq.md index 909367ec..cd97a71b 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -57,21 +57,6 @@ SELECT ?sensor WHERE { This tells the Knowledge Engine that you cannot send it an email address and receive the username. -*Question*: Can you explain how to register the argument pattern and the result graph pattern? In the KE API I saw only one graph pattern in the register of a Knowledge interaction, and no parameter to indicate if it is an argument pattern or a result graph pattern. -- *Answer*: In the Java Developer API the constructors of the [PostKnowledgeInteraction](https://github.com/TNO/knowledge-engine/blob/master/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/PostKnowledgeInteraction.java) and [ReactKnowledgeInteraction](https://github.com/TNO/knowledge-engine/blob/master/smart-connector-api/src/main/java/eu/knowledge/engine/smartconnector/api/ReactKnowledgeInteraction.java) objects require both an argument and a result graph pattern. - - In the JSON body of the [REST Developer API ](https://github.com/TNO/knowledge-engine/blob/master/smart-connector-rest-server/src/main/resources/openapi-sc.yaml) `POST /sc/ki` operation, you specific the type of the Knowledge Interaction. If you choose the PostKnowledgeInteraction or ReactKnowledgeInteraction `knowledgeInteractionType`, the argument and result graph patterns are also expected (see also the schema of the request body): - - ```json - { - "knowledgeInteractionType": "PostKnowledgeInteraction", - "argumentGraphPattern": "?s ?p ?o", - "resultGraphPattern": "?x ?y ?z" - } - ``` - - Note that the result graph pattern is optional. - *Question*: In the context of graph pattern matching, can you explain the subset/superset condition: "when the Graph Pattern" of the sender is a superset of the Graph Pattern of the receiver' ? Is it at definition level or at data level? I found (at ontology level) the following explanation: "Ontology O1 is a subset of ontology O2 if all definitions in O1 are contained in O2 (O2 is the superset of O1)". 1) More concrete: is 'a b c .' graph pattern a subset or a superset of 'a b c . d e f.' ? 2) In case of Post/React knowledge interactions, both argument graph patterns must match and also both result graph patterns must match? @@ -84,18 +69,6 @@ SELECT ?sensor WHERE { 3) Yes, the PostKnowledgeInteraction sends the argument and the ReactKnowledgeInteraction sends the (optional) result. 4) Currently, this will not work, because we are using a graph pattern *matcher* instead of a *reasoner*. I expect the reasoner to indeed allow them to interact if the POST side result pattern is a subset of the REACT side result pattern. In that case the result binding set at the POST side should also be a subset (in fields) of the binding set given from the REACT side. So, the results are always given to a Knowledge Base in its own terminology, this already happens by translating the variable names, but should also happen in the way you describe once the reasoner is active. -*Question*: I successfully created smart connector (https://cybergrid.com/kb1) and the knowledge Interaction. When I wanted to execute the ask command with the following body: -```json -[ - { - "deviceName": "device1" - } -] -``` -I received the following expectation from the knowladge-engine: ```400 Bad Request: ['device1' is not an unprefixed URI or literal.]``` -- *Answer*: The reason your request fails is because variable bindings need to be either RDF Literals or IRIs. See also our [documentation](java_developer_api.md#bindings). - If you change your example value from `device1` to something like ``, this particular error should be resolved. - *Question*: Do we need a long polling connection for every Knowledge Interaction? Doesn't that get very complicated? - *Answer*: No, per Smart Connector (or Knowledge Base) you need a single long polling connection to receive all interactions from the Knowledge Engine. Do remember that this long polling connection is returned with status code 202 every 29 seconds and also needs to be reestablished after you receive data via it. diff --git a/docs/docs/get-started/knowledge-interactions.md b/docs/docs/get-started/knowledge-interactions.md index 3f373365..e1442719 100644 --- a/docs/docs/get-started/knowledge-interactions.md +++ b/docs/docs/get-started/knowledge-interactions.md @@ -11,22 +11,67 @@ import TabItem from '@theme/TabItem'; ```java // ASK: -AskKnowledgeInteraction askInteraction = new AskKnowledgeInteraction(graphPattern); +AskKnowledgeInteraction askInteraction = new AskKnowledgeInteraction(communicativeAct, graphPattern); smartConnector.register(askInteraction); // ANSWER: -AnswerKnowledgeInteraction answerInteraction = new AnswerKnowledgeInteraction(graphPattern); +AnswerKnowledgeInteraction answerInteraction = new AnswerKnowledgeInteraction(communicativeAct, graphPattern); smartConnector.register(answerInteraction); // POST: -PostKnowledgeInteraction postInteraction = new PostKnowledgeInteraction(graphPattern); +PostKnowledgeInteraction postInteraction = new PostKnowledgeInteraction(communicativeAct, argumentGraphPattern, resultGraphPattern); smartConnector.register(postInteraction); // REACT: -ReactKnowledgeInteraction reactInteraction = new ReactKnowledgeInteraction(graphPattern); +ReactKnowledgeInteraction reactInteraction = new ReactKnowledgeInteraction(communicativeAct, argumentGraphPattern, resultGraphPattern); smartConnector.register(reactInteraction); ``` +You can also provide a name for your interaction, for example: +```java +AskKnowledgeInteraction askInteraction = new AskKnowledgeInteraction(communicativeAct, graphPattern, name); +``` + +If you want to use prefixes in your graph pattern, these can be defined in the `graphPattern`: +```java +GraphPattern graphPattern = new GraphPattern(prefixes, pattern); +``` + + + +To instantiate a Knowledge Interaction, you need to send a POST to `/sc/ki` with a body that contains the type of knowledge interaction that you want to make, and any required graph patterns. +ASK and ANSWER require one graph pattern, like so: + +```json +{ + "knowledgeInteractionType": "AskKnowledgeInteraction", + "graphPattern": "?s ?p ?o" +} +``` + +POST and REACT require an `argumentGraphPattern`, and optionally use a `resultGraphPattern`. For example: + +```json +{ + "knowledgeInteractionType": "PostKnowledgeInteraction", + "argumentGraphPattern": "?s ?p ?o", + "resultGraphPattern": "?x ?y ?z" +} +``` + +You can also provide a name for your interaction and define prefixes for your graph patterns: + +```json +{ + "knowledgeInteractionType": "AskKnowledgeInteraction", + "knowledgeInteractionName": "my-ask", + "graphPattern": "?a rdf:type ex:Book", + "prefixes": { + "rdf": "https://www.w3.org/1999/02/22-rdf-syntax-ns/" + } +} +``` + @@ -50,6 +95,30 @@ AskKnowledgeInteraction askInteraction = new AskKnowledgeInteraction(graphPatter AskResult interactionResult = sc.ask(askInteraction, queryBindings).get(); ``` + + +Send a POST to `/sc/ask` to execute an Ask Knowledge Interaction. +To execute a Post Knowledge Interaction, send a POST to `/sc/post`. + +Triggering an interaction requires you to provide two parameters: +* `Knowledge-Base-Id`: specifies the Knowledge Base Id for which to execute the ask +* `Knowledge-Interaction-Id`: specifies the Ask Knowledge Interaction that should be executed + +In the body you can also specify a binding set, or a recipient selector *and* binding set. +The recipient selector can be used to select a single Knowledge Base Id which should be contacted. +The binding set specifies values that you are interested in. These must correspond to the variables in the graph pattern of the knowledge interaction. +```json +{ + "recipientSelector": { + "knowledgeBases": [] + }, + "bindingSet": [ + {} + ] +} +``` +If you leave the array for `knowledgeBases` empty, then it will simply ask all relevant KBs. + diff --git a/docs/docs/tke-errors.md b/docs/docs/tke-errors.md new file mode 100644 index 00000000..f8737afa --- /dev/null +++ b/docs/docs/tke-errors.md @@ -0,0 +1,13 @@ +--- + sidebar_position: 9 +--- +# Common Error Messages + +On this page you can find a list of common error messages and what they mean. + +### `x` is not an unprefixed URI or literal +Whenever you are specifying variable bindings, for example in a binding set when executing an ask, you may encounter this error. +It occurs because all variable bindings need to be either RDF Literals or IRIs. +If you want to identify a specific object or device, you can use something like ``. +Note that if the variable binding is for a subject in a triple (?subject ?predicate ?object), then an IRI is always rquired. +For more information on bindings and binding sets, see: [Bindings](https://docs.knowledge-engine.eu/java_developer_api#bindings). \ No newline at end of file From 1bb85078b14f8f1fc264204532cc6d9b2ce066bf Mon Sep 17 00:00:00 2001 From: lathouwerssam Date: Fri, 8 Nov 2024 16:03:34 +0100 Subject: [PATCH 41/64] Move question on binding set ordering to API page --- docs/docs/faq.md | 3 --- docs/docs/java_developer_api.md | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/faq.md b/docs/docs/faq.md index cd97a71b..a4321955 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -152,9 +152,6 @@ We have an issue #95 which would allow you to instruct the Knowledge Engine to n } ``` -*Question*: In a Ask/Answer interaction the bindingSet is a list of items. The items should stay in the order there are inserted on the Answer side. Currently it is not the case. -- *Answer*: The Knowledge Engine cannot guarantee the ordering of the Bindings in a BindingSet due to several reasons. From a Semantic Technology perspective, the bindings are stored in a binding set in which the ordering is not fixed. Also, due to the matchers (and future reasoners) that mediate between different smart connectors and the fact that the response can be a union from BindingSets received from different knowledge bases, it is difficult to guarantee any ordering. Ideally, the ordering of the bindings can be derived from data, for example an ordering value or timestamps. This would allow the data to be sorted after receiving it from the interoperability layer. - *Question*: Some partners use a query like API request whereby they can specify via an input field what kind of results they expect back, together with the device id (temperature sensor, smart plug, meter,...) For instance one can select the list of parameters (status, temperature, power, voltage, door_state, humidity, luminosity etc.) that should be returned. In the result below is only the power_total returned, but additional fields could be selected/set in the input field (depending on the device type it can return more than one value) diff --git a/docs/docs/java_developer_api.md b/docs/docs/java_developer_api.md index a8928b5e..683a84db 100644 --- a/docs/docs/java_developer_api.md +++ b/docs/docs/java_developer_api.md @@ -109,6 +109,9 @@ Two important things should be noted: A result of a knowledge interaction can have more than 1 match. These matches are collected in a `BindingSet`, which is simply a set of bindings. +The Knowledge Engine cannot guarantee the ordering of bindings within a binding set. +This mean that bindings may change order, e.g. the order of bindings for an ask and corresponding answer may be different. + ## Expressibility The Graph Pattern syntax has a limited expressibility. This means there are certain things that you might want to express with them, but are unable to. Sometimes this means it limits the actual data exchange, but sometimes there are work-arounds. One of the limitations is related to one-to-many relations. Take the following RDF about parents and children in which a parent has a one-to-many relation with its children: From 2b8a4efa4603a4b604cccb7f08c5d9b1e63ffae2 Mon Sep 17 00:00:00 2001 From: lathouwerssam Date: Tue, 12 Nov 2024 10:12:02 +0100 Subject: [PATCH 42/64] Move questions in FAQ with common error messages to other page --- docs/docs/faq.md | 15 +++------------ docs/docs/java_developer_api.md | 6 ++++-- docs/docs/tke-errors.md | 18 +++++++++++++++--- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/docs/docs/faq.md b/docs/docs/faq.md index a4321955..771c545a 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -235,7 +235,7 @@ Result: For more information about the Knowledge Engine and Knowledge Interactions, see the recorded workshop on our shared drive: https://drive.inesctec.pt/f/16182787 -*Quesiton*: We noticed that we cannot get consistent results when testing a POST/REACT exchange, both locally and with a partner. So to summarize: +*Question*: We noticed that we cannot get consistent results when testing a POST/REACT exchange, both locally and with a partner. So to summarize: * we can send a POST request * the REACT sides don't receive 100% of the time (more like 25%), whether it is locally running or from our partner's platform * When receiving the POST, the REACT side sends the confirmation properly @@ -350,24 +350,15 @@ I guess my question is more specifically: “Will the compliance checker look at | | *both argument and result GP* | no | yes | n/a | | ANSWER | | n/a | n/a | yes | -*Question*: When making the ASK request, the binding set is a list of datapoints where the timestamps are increasing from the first to the last element. When the Answer service receives a response, the order of the datapoints inside the binding set is completely changed. -- *Answer*: You are correct to observe that the ordering of bindings in a binding set is not guaranteed by the Knowledge Engine. The reason why we call it a binding set is, because the elements in a set are unordered. Since JSON does not support sets and we are using JSON for our REST API, we put the bindings in a JSON list ([ … ]). I can imagine this is a bit misleading and you would expect the result to keep the ordering. However, due to the nature of RDF, the ordering of the bindings in a binding set cannot be used to encode any information when exchanging data. The ordering should, thus, be encoded explicitly using numbers or (as in your case) timestamps and the receiving end (ANSWER) should use this information to put the information in the correct order. - -*Question*: My KB has an ASK knowledge interaction with the same graph pattern as the ANSWER knowledge interaction of the Whirlpool KB with whom I want to exchange data, but the data exchange is not happing. What is going wrong? +*Question*: My KB has an ASK knowledge interaction with the same graph pattern as the ANSWER knowledge interaction of the Whirlpool KB with whom I want to exchange data, but the data exchange is not happening. What is going wrong? - *Answer*: Double check the communicative acts that both knowledge interactions use. The CommunicativeAct is meant to indicate the 'reason' for the interaction and in most cases the “InformPurpose” is sufficient and therefore it is the default communicative act of every registered Knowledge Interaction. Whenever the KE wants to exchange data it compares the sender KI’s communicative act with the recipient KI’s communicative act and if they ‘match’ the data will be exchanged. If both KB use the REST API to register KIs and do not specify the communicative act, they will be able to exchange data. However, when they _do_ specify the communicative act when registering a KI, they should be compatible. In the case of Whirlpool the problem might be that Whirlpool explicitly configures the old default https://www.tno.nl/energy/ontology/interconnect#InformPurpose URI. This default was changed to https://w3id.org/knowledge-engine/InformPurpose in KE version `1.1.2` and sometimes prevents two knowledge bases from exchanging data. *Question*: What should our KB do When we receive a request for data (either via an ANSWER or REACT Knowledge Interaction), but we do not have a response? -- *Answer*: You should send an empty binding set when you do not have a response. Also when your REACT Knowledge Interaction has no result graph pattern, you should always return an empty binding set to the Knowledge Engine. If an error occurs while responding, you can either return an empty BindingSet (although this does not give any information about the error occurring) or call the (in case you are using the asynchronous handler methods of the Java Developer API) `future.completeExceptionally(...)` method. - -*Question*: There are lots of 'HTTP/1.1 header parser received no bytes' errors in the logs. How do I prevent these? -- *Answer*: This can be prevented by using a shorter timeout in the Java HTTP Client. Try using the following Java option: `-Djdk.httpclient.keepalive.timeout=3`. This can also be set in the docker configuration of a KER docker container by setting the JAVA_TOOL_OPTIONS as follows: `JAVA_TOOL_OPTIONS: "-Djdk.httpclient.keepalive.timeout=3"`. +- *Answer*: You should send an empty binding set when you do not have a response. Also, when your REACT Knowledge Interaction has no result graph pattern, you should always return an empty binding set to the Knowledge Engine. If an error occurs while responding, you can either return an empty BindingSet (although this does not give any information about the error occurring) or call the (in case you are using the asynchronous handler methods of the Java Developer API) `future.completeExceptionally(...)` method. *Question*: Whenever I do a post or ask, the memory usage of the Knowledge Engine Runtime skyrockets and it fails with a error (`HTTP 500`) after a minute or two. - *Answer*: Double check whether you are enabling the reasoner when you create a Smart Connector for your Knowledge Base. When using the REST Developer API, you can disable the reasoner by setting the JSON property `reasonerEnabled` to `false` or leave the property out altogether because by default the reasoner is disabled. Currently, the reasoner is not usable for scenario's where graph patterns are more than about 5 or 6 triple patterns, because the algorithm for graph pattern matching uses too much memory. We are working on improving this algorithm and hopefully allow more use cases to enable the reasoner and benefit the increased interoperability. -*Question*: The result of my POST contains the following failed message in its ExchangeInfo: "java.lang.IllegalArgumentException: KB gave outgoing binding Binding [...], but this doesn't have a matching incoming binding!". What does this mean? -- *Answer*: The reason this error is given, is the following: REACT KIs that contain both an argument and result graph pattern and these two graph patterns share a variable name, the knowledge engine expects all values for this variable in the result binding set to also occur as a value of the variable in the argument binding set. This has to do with how these graph patterns are internally used to form if … then … rules. If this is not the case, it gives the above failedMessage. If this shared variable in the argument and result graph patterns is intended to be the same, make sure you align the values of this variable in the result binding set with the values of this variable in the argument binding set. If the variable is not intended to be the same thing, you can rename one of them to prevent the knowledge engine to expect them to share values. - *Question*: There is an existing Knowledge Network with a Knowledge Directory (KD) and multiple Knowledge Engine Runtimes (KERs). How do I setup my own KER and configure it such that it participates in the existing Knowledge Network? - *Answer*: Every Knowledge Engine Runtime (KER) in distributed mode consists of two APIs: [Knowledge Engine Developer REST API](https://github.com/TNO/knowledge-engine/blob/1.2.5/smart-connector-rest-server/src/main/resources/openapi-sc.yaml) and the [Inter-Knowledge Engine Runtime API](https://github.com/TNO/knowledge-engine/blob/1.2.5/smart-connector/src/main/resources/openapi-inter-ker.yaml). The former is started on port `8280` by default and you use this API to register your Knowledge Base and Knowledge Interactions. The latter API is meant for internal communication between KERs and you do not need to use it yourself. However, you do need to make sure this API is reachable for other KERs in the Knowledge Network. By default this API is available on port 8081, but sometimes you need to change this port using the `KE_RUNTIME_PORT` environment variable. Make sure the latter API of your KER is accessible from the internet and configure its URL when starting the KER with the `KE_RUNTIME_EXPOSED_URL` environment variable. To set this up correctly, you typically install a reverse proxy like NGNIX and open the correct ports in the firewall of the server. For this you need to contact the administrator of the server you are using. A KER starts in distributed mode when it detects the `KD_URL` environment variable. This variable points to the Knowledge Directory of the Knowledge Network. You can configure it using environment variables `KD_URL=`. If the Knowledge Directory is protected using Basic Authentication, you can add the credentials to the KD_URL as described [here](https://stackoverflow.com/a/50528734). diff --git a/docs/docs/java_developer_api.md b/docs/docs/java_developer_api.md index 683a84db..121f64e3 100644 --- a/docs/docs/java_developer_api.md +++ b/docs/docs/java_developer_api.md @@ -109,8 +109,10 @@ Two important things should be noted: A result of a knowledge interaction can have more than 1 match. These matches are collected in a `BindingSet`, which is simply a set of bindings. -The Knowledge Engine cannot guarantee the ordering of bindings within a binding set. -This mean that bindings may change order, e.g. the order of bindings for an ask and corresponding answer may be different. +The Knowledge Engine does not guarantee the ordering of bindings in a binding set. +The reason why we call it a binding set is because the elements in a set are unordered. +Due to the nature of RDF, the ordering of the bindings in a binding set cannot be used to encode any information when exchanging data. +Thus, if you need ordering, this should be encoded explicitly, e.g. by using numbers or timestamps, and the receiving end should use this information to put the information in the correct order. ## Expressibility diff --git a/docs/docs/tke-errors.md b/docs/docs/tke-errors.md index f8737afa..7efa8c82 100644 --- a/docs/docs/tke-errors.md +++ b/docs/docs/tke-errors.md @@ -8,6 +8,18 @@ On this page you can find a list of common error messages and what they mean. ### `x` is not an unprefixed URI or literal Whenever you are specifying variable bindings, for example in a binding set when executing an ask, you may encounter this error. It occurs because all variable bindings need to be either RDF Literals or IRIs. -If you want to identify a specific object or device, you can use something like ``. -Note that if the variable binding is for a subject in a triple (?subject ?predicate ?object), then an IRI is always rquired. -For more information on bindings and binding sets, see: [Bindings](https://docs.knowledge-engine.eu/java_developer_api#bindings). \ No newline at end of file +If you want to identify a specific object or device `x`, you can use something like ``. +Note that if the variable binding is for a subject in a triple (?subject ?predicate ?object), then an IRI is always required. +For more information on bindings and binding sets, see: [Bindings](https://docs.knowledge-engine.eu/java_developer_api#bindings). + +### There are lots of 'HTTP/1.1 header parser received no bytes' errors in the logs. How do I prevent these? +This can be prevented by using a shorter timeout in the Java HTTP Client. +Try using the following Java option: `-Djdk.httpclient.keepalive.timeout=3`. +This can also be set in the docker configuration of a Knowledge Engine Runtime docker container by setting the JAVA_TOOL_OPTIONS as follows: `JAVA_TOOL_OPTIONS: "-Djdk.httpclient.keepalive.timeout=3"`. + +### "java.lang.IllegalArgumentException: KB gave outgoing binding Binding [...], but this doesn't have a matching incoming binding!" +When using a REACT Knowledge Interaction that contains both an argument and result graph pattern which share a variable name, the Knowledge Engine expects all values for this variable in the result binding set to also occur as a value of the variable in the argument binding set. +This has to do with how these graph patterns are internally used to form if … then … rules. +If this is not the case, it gives the above error message. +If this shared variable in the argument and result graph patterns is intended to be the same, make sure you align the values of this variable in the result binding set with the values of this variable in the argument binding set. +If the variable is not intended to be the same thing, you can rename one of them to prevent the knowledge engine to expect them to share values. \ No newline at end of file From 0c27e8823d6d37d1e9606876aec49cf201c57a7f Mon Sep 17 00:00:00 2001 From: lathouwerssam Date: Tue, 12 Nov 2024 11:15:49 +0100 Subject: [PATCH 43/64] Add common error about recipientSelector and bindingSet --- docs/docs/tke-errors.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/docs/tke-errors.md b/docs/docs/tke-errors.md index 7efa8c82..31d90a73 100644 --- a/docs/docs/tke-errors.md +++ b/docs/docs/tke-errors.md @@ -17,9 +17,25 @@ This can be prevented by using a shorter timeout in the Java HTTP Client. Try using the following Java option: `-Djdk.httpclient.keepalive.timeout=3`. This can also be set in the docker configuration of a Knowledge Engine Runtime docker container by setting the JAVA_TOOL_OPTIONS as follows: `JAVA_TOOL_OPTIONS: "-Djdk.httpclient.keepalive.timeout=3"`. -### "java.lang.IllegalArgumentException: KB gave outgoing binding Binding [...], but this doesn't have a matching incoming binding!" +### java.lang.IllegalArgumentException: KB gave outgoing binding Binding [...], but this doesn't have a matching incoming binding! When using a REACT Knowledge Interaction that contains both an argument and result graph pattern which share a variable name, the Knowledge Engine expects all values for this variable in the result binding set to also occur as a value of the variable in the argument binding set. This has to do with how these graph patterns are internally used to form if … then … rules. If this is not the case, it gives the above error message. If this shared variable in the argument and result graph patterns is intended to be the same, make sure you align the values of this variable in the result binding set with the values of this variable in the argument binding set. -If the variable is not intended to be the same thing, you can rename one of them to prevent the knowledge engine to expect them to share values. \ No newline at end of file +If the variable is not intended to be the same thing, you can rename one of them to prevent the knowledge engine to expect them to share values. + +### The JSON Object should contain both a recipientSelector and a bindingSet key +When executing an ASK or POST you have to provide either (a) a binding set, or (b) a recipient selector *and* binding set. +The binding set specifies values that you are interested in. +The recipient selector can be used to select a single Knowledge Base which should be contacted. +In case you do not want to select a single Knowledge Base, you can pass an empty list to contact *all* relevant Knowledge Bases: +```json +{ + "recipientSelector": { + "knowledgeBases": [] + }, + "bindingSet": [ + {} + ] +} +``` \ No newline at end of file From 9522040226b0e0f157e942e0687cf9abd7e91584 Mon Sep 17 00:00:00 2001 From: lathouwerssam Date: Tue, 12 Nov 2024 15:48:09 +0100 Subject: [PATCH 44/64] More FAQs rewritten and/or integrated into the documentation --- docs/docs/faq.md | 160 +++++++++++------- docs/docs/get-started/_category_.json | 4 +- docs/docs/get-started/knowledge-directory.md | 13 ++ .../get-started/knowledge-interactions.md | 3 +- docs/docs/get-started/smart-connector.md | 40 ++++- docs/docs/quickstart.md | 33 ---- 6 files changed, 148 insertions(+), 105 deletions(-) create mode 100644 docs/docs/get-started/knowledge-directory.md delete mode 100644 docs/docs/quickstart.md diff --git a/docs/docs/faq.md b/docs/docs/faq.md index 771c545a..afcba188 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -6,24 +6,52 @@ FAQ === -*Question*: may be a question of dummies, what is the time of validity of the information? is it specified in the graph pattern? -- *Answer*: The validity of the information is not available by default, but you can include it into the graph pattern if you need it for your use case. - -*Question*: There is a limited and defined number of KI. How do we proceed to "generate" all possible graph patterns and so associated KI ? -- *Answer*: The KIs used should match and the KIs and their Graph Patterns are the end result of the SAREFization process for services. Also, if you need pilot specific KIs, you can agree on them within your pilot using SAREF or a pilot specific ontology. - -*Question*: what about ACKs of the requests? -- *Answer*: There are no explicit ACKs. The Ask receives an answer of one or more KBs and the Post receives the BindingSet for the optional Result Graph Pattern. If you need an explicit ACK (for example with the turn on the light example in the presentation) you can use the result Graph Pattern to contain the ACK. - -*Question*: Is there any specific requirements such as RAM, CPU and disk space for deploying the KE ? Is there aditional components to take into account such as an external DB or it is all inclusive ? Is there OS specific configuration such as network or ports ? -- *Answer*: We do not have minimal requirements for the KE yet. Locally I am running multiple Smart Connectors just fine on my Intel Core i7-8650 CPU @ 1.9GHz with 16 Gb RAM, but it also depends on the amount of traffic of course. -By the way, the current version (0.1.6) of the KE is a centralized one where all the Smart Connectors (with their reasoner) run on a server hosted by INESC TEC and partners create and access their Smart Connector via the REST Developer API of the generic adapter. A future version of the generic adapter will contain an instance of the Smart Connector (plus reasoner). -Currently, an instance of the Smart Connector is self contained, so no external database is required. The future version will of course need to use the network (ports are not yet decided) to communicate with other Smart Connectors (and the Knowledge Directory). - -*Question*: A Knowledge Interaction (KI) is a basic graph pattern. I suppose that this means just a sequence of triple patterns. Does this mean that for example the ‘FILTER’ keyword can’t be used in the KI? -- *Answer*: Indeed, the FILTER keyword (of the SPARQL language) is not available. Although this is a very useful keyword and we would love to support something like that, there needs to be an equivalent of filtering in the reasoner and most of the time this is not there. We do keep this in mind when looking/making for a new reasoner, but I do not expect this to be available anytime soon (there is still research required, I think). Note that a Knowledge Interaction is more than a single Basic Graph Pattern (although it is the most important part of it). It also has a type (Ask/Answer or Post/React) and a Communicative Act (to convey the 'reason' for the interaction). Also, the Post/React KI have two graph patterns attached to them; the argument and the result graph pattern. - -*Question*: It means also that a SparQL query like you see below is a SparQL query and is not something that can be used at the KE REST API interface. Only the part within the brackets is a basic graph pattern. Is that right? +### What is the time of validity of the exchanged information? +The validity of the exchanged information is not available by default, but you can include it into the graph pattern if you need it for your use case. + +### Are there any technical requirements, e.g. RAM, CPU and disk space, for deploying the Knowledge Engine? +We do not have minimal requirements for the Knowledge Engine yet. +Locally we can run multiple Smart Connectors on an Intel Core i7-8650 CPU @ 1.9GHz with 16 Gb RAM, but the requirements also depend on the amount of data that is exchanged. +An instance of the Smart Connector is self-contained and no external database or storage is required. + +For setting up your own Knowledge Network, including a Knowledge Directory, typically the following steps are required: +1. Have a machine or virtual machine ready: + * Ideally configure the (virtual) machine to be in a DMZ network, separate from other critical/sensitive resources (good security practice) +2. Deploy the Knowledge Engine and Knowledge Directory on the machine, i.e. deploy two Java servers. +3. Configure the firewall to allow external communication to that (virtual) machine. + * Depending on the local infrastructure, configure a proxy (if it exists) to forward the requests to that (virtual) machine. + +A medium range (virtual) machine with the following requirements should be sufficient to set up your own Knowledge Network: +* (Ideally) Linux-based OS +* Latest Java SE installed +* Outside world (inbound) internet access +* Low/medium CPU (2 cores at least) +* 16 GB RAM (nowadays a good minimum for a server, more is better) + +It is recommended that someone can access the (virtual) machine to collect any logs and troubleshoot when necessary. + +### How does the Knowledge Engine deal with privacy-sensitive information? +The Knowledge Engine (and the Smart Connector) functions as a serving hatch and does not store any data that is being exchanged. +All this data is stored in the Knowledge Bases which are responsible for protecting privacy-sensitive data. +Within the Knowledge Engine we distinguish between *graph patterns* and *binding sets*. +The graph patterns should not contain any privacy-sensitive data since they are part of the meta-data that is being stored as capability descriptions. +This information is needed to orchestrate the data exchange. +These graph patterns might also show up in the logs of the smart connector (at all log levels). +The binding sets, on the other hand, _can_ contain privacy-sensitive data which will not be stored. +Binding Sets are also not stored in the log files of Smart Connectors if these have a log level of INFO or higher. +Keep in mind, though, that the Knowledge Engine functions as an intelligent broker between consumers and producers of knowledge. +This might cause the Knowledge Engine to exchange your data with unexpected parties within the Knowledge Network. +So make sure your knowledge network only contains trusted KBs. + +### Can we use SPARQL keywords such as FILTER in Knowledge Interactions? +No, SPARQL keywords are not available. +We do not use SPARQL, because SPARQL is only usable for a question/answer interactions, while we also support publish/subscribe and function call interactions. +Although keywords such as FILTER are very useful keywords, and we would love to support something like that, there need to be equivalent options in the reasoner and most of the time this is not there. +We do keep this in mind when looking for/making a new reasoner, but do not expect this to be available anytime soon (there is still research required). +Note that a Knowledge Interaction is more than a single Basic Graph Pattern (although it is the most important part of it). +It also has a type (Ask/Answer or Post/React) and a Communicative Act (to convey the 'reason' for the interaction). + +Take for example the following SPARQL query: ```sparql SELECT ?sensor WHERE { ?building a saref4bldg:Building. @@ -33,7 +61,54 @@ SELECT ?sensor WHERE { ?vibrationSensor saref:hasState ?state . } ``` -- *Answer*: Exactly, the WHERE part contains the Basic Graph Patterns and those are used to create the Knowledge Interactions. We do not use SPARQL, because SPARQL is only usable for a question/answer interactions, while the Interoperability layer should also support publish/subscribe and function call interactions. +We can use the Basic Graph Pattern from the WHERE-clause to create a Knowledge Interaction. +We will then also need to specify the type of interaction, e.g. ASK, and the Communicative Act. + + +### Why do our two Knowledge Bases not exchange data even though they have matching graph patterns? +In this case, typically the error is in the Knowledge Interactions that you expect to match. +Two Knowledge Interactions match when: +* The types match +* The graph patterns match +* The communicative acts match + +In the table below 'yes' means those two types of Knowledge Interactions match, while 'no' means those two types of Knowledge Interactions do not match. + +| | | POST | | ASK | +|---------------|-----------------------------------------|-------------------------|-----------------------------------------|------------| +| | | *only argument GP* | *both argument and result GP* | | +| REACT | *only argument* GP | yes | no | n/a | +| | *both argument and result GP* | no | yes | n/a | +| ANSWER | | n/a | n/a | yes | + +When your two Knowledge Interaction types have a 'yes', then you can take a look at whether the graph patterns match. + +Two graph patterns match when every triple of the first graph pattern is also in the second graph pattern and vice versa. +The ordering and names of variables like `?s` are ignored. +Note that in case of POST and REACT Knowledge Interactions, both the argument graph pattern and the result graph pattern must match. + +If you are sure that the graph patterns match (be careful of typos!), check which communicative acts they use. +The CommunicativeAct is meant to indicate the 'reason' for the interaction and in most cases the “InformPurpose” is sufficient, and therefore it is the default communicative act of every registered Knowledge Interaction. +Whenever the Knowledge Engine wants to exchange data it compares the sender Knowledge Interaction’s communicative act with the recipient Knowledge Interaction’s communicative act and if they ‘match’ the data will be exchanged. +If both Knowledge Bases use the REST API to register Knowledge Interactions and do not specify the communicative act, they will be able to exchange data. +However, when they _do_ specify the communicative act when registering a Knowledge Interaction, they should be compatible. + + + + + + + +*Question*: There is a limited and defined number of KI. How do we proceed to "generate" all possible graph patterns and so associated KI ? +- *Answer*: The KIs used should match and the KIs and their Graph Patterns are the end result of the SAREFization process for services. Also, if you need pilot specific KIs, you can agree on them within your pilot using SAREF or a pilot specific ontology. + +*Question*: what about ACKs of the requests? +- *Answer*: There are no explicit ACKs. The Ask receives an answer of one or more KBs and the Post receives the BindingSet for the optional Result Graph Pattern. If you need an explicit ACK (for example with the turn on the light example in the presentation) you can use the result Graph Pattern to contain the ACK. + +*Question*: It means also that a SparQL query like you see below is a SparQL query and is not something that can be used at the KE REST API interface. Only the part within the brackets is a basic graph pattern. Is that right? + + + *Question*: In POST /sc/ki one registers a Knowledge Interaction along with the knowledge interaction type. In POST /sc/ask one queries for some results by referring to a KI and providing an incomplete binding set. The result will be a complete binding set. In your presentation KE for dummies slide 12, you mentioned that one could restrict the question (at the react side). I didn’t find in the rest of the slides on how one can do that, except by having a literal in the registered KI. In your example a person has a name and a email address, but the logic only allows to ask for the email address associated with a person with a certain name, but it does not allow to get the name associated with a specific email address. How do we impose such a restriction, or we can’t do this at this stage? @@ -90,7 +165,7 @@ Let's say we have some graph pattern like: And the timeseries returns an array of temperature values and timestamp for each value. -In the ANWSER Knowledge interaction will the binding set be something like: +In the ANSWER Knowledge interaction will the binding set be something like: ```json [ { @@ -316,51 +391,8 @@ I guess my question is more specifically: “Will the compliance checker look at And last but not least, a short reaction to Georg’s remark: “I think it makes sense to think about the GP as the body of a query”. It certainly does, although within the context of the Knowledge Engine graph patterns are also used for non-query like interactions. -*Question*: Which are the technical requirements needed to host the KE at pilot level for the next 22 months? Do you have an estimation about hosting/processing needs? And from the technical support, any idea about the effort that might require? -- *Answer*: From a technical perspective what needs to be done is: - 1. Have a machine or virtual machine ready (spec discussion separately); - 1.1. Ideally configure the machine/virtual machine to be in a DMZ network, separate from other critical/sensitive resources (for the sake of prevention) - 2. Deploy the KE and Knowledge Directory in the machine i.e., deploy two java servers. - 3. Configure firewall to allow external communication to that machine/virtual machine - 3.1 Depending on the local infrastructure, configure a proxy (if it exists) to forward the requests to that machine/virtual machine. - 4. Communicate me the public IP address where the KE is now available (so that we can use the project domain to identify that IP). - 5. If the proxy used uses a wildcard TLS certificate it can protect the KE instance (that is what we are doing in the cloud instance). If the wildcard certificate is not available, we need to acquire one and deploy it together with the KE. - - These are essentially the steps. These steps are standard, so any technical colleague should be able to do it. - - As for the technical requirements of the machine, we will collect that info and provide it to you. But a medium range machine/virtual machine should do it. - Requirements: - - (ideally) Linux based OS , e.g., latest ubuntu - - Latest Java SE installed - - Outside world (inbound) internet access - - Low/medium CPU (2 core at least) - - 16 GB RAM (nowadays a good minimum for a server. if if has more, better). - - Finally someone should be able to access that machine to collect any logs and trouble shoot whenever necessary. -*Question*: How does the Knowledge Engine deal with privacy sensitive information? -- *Answer*: The Knowledge Engine (and the Smart Connector) functions as a serving hatch and does not store any data that is being exchanged. All this data is stored in the Knowledge Bases and the KB are responsible for protecting privacy sensitive data. Within the Knowledge Engine we distinguish between *graph patterns* and *binding sets*. The graph patterns should not contain any privacy sensitive data since they are part of the meta data that is being stored as capability descriptions. This information is needed to orchestrate the data exchange. These graph patterns might also show up in the logs of the smart connector (at all log levels). The binding sets, on the other hand, _can_ contain privacy sensitive data which will not be stored. Binding Sets are also not stored in the log files of Smart Connectors if these have a log level of INFO or higher. Keep in mind, though, that the Knowledge Engine functions as an intelligent broker between consumers and producers of knowledge. This might cause the Knowledge Engine to exchange your data with unexpected parties within the Knowledge Network. So make sure your knowledge network only contains trusted KBs. - -*Question*: Why do our two KBs not exchange data, while they should? -- *Answer*: Assuming we are talking about the Matcher (default) instead of the Reasoner. The first thing to check are the Knowledge Interactions (KIs) of the two KBs that you expect to match. For two KIs to match, both their type needs to match and their graph patterns. In the table below 'yes' means those two types of KIs match, while 'no' means those two types of KIs do not match. When your two KI types have a 'yes', you can take a look at whether the graph patterns match. Two graph patterns match when every triple of the first graph pattern is also in the second graph pattern and vice versa. The ordering and names of variables like `?s` are ignored. Note that in case of POST and REACT KIs, both the argument graph pattern and the result graph pattern must match. - -| | | POST | | ASK | -|---------------|-----------------------------------------|-------------------------|-----------------------------------------|------------| -| | | *only argument GP* | *both argument and result GP* | | -| REACT | *only argument* GP | yes | no | n/a | -| | *both argument and result GP* | no | yes | n/a | -| ANSWER | | n/a | n/a | yes | - -*Question*: My KB has an ASK knowledge interaction with the same graph pattern as the ANSWER knowledge interaction of the Whirlpool KB with whom I want to exchange data, but the data exchange is not happening. What is going wrong? -- *Answer*: Double check the communicative acts that both knowledge interactions use. The CommunicativeAct is meant to indicate the 'reason' for the interaction and in most cases the “InformPurpose” is sufficient and therefore it is the default communicative act of every registered Knowledge Interaction. Whenever the KE wants to exchange data it compares the sender KI’s communicative act with the recipient KI’s communicative act and if they ‘match’ the data will be exchanged. If both KB use the REST API to register KIs and do not specify the communicative act, they will be able to exchange data. However, when they _do_ specify the communicative act when registering a KI, they should be compatible. In the case of Whirlpool the problem might be that Whirlpool explicitly configures the old default https://www.tno.nl/energy/ontology/interconnect#InformPurpose URI. This default was changed to https://w3id.org/knowledge-engine/InformPurpose in KE version `1.1.2` and sometimes prevents two knowledge bases from exchanging data. - *Question*: What should our KB do When we receive a request for data (either via an ANSWER or REACT Knowledge Interaction), but we do not have a response? - *Answer*: You should send an empty binding set when you do not have a response. Also, when your REACT Knowledge Interaction has no result graph pattern, you should always return an empty binding set to the Knowledge Engine. If an error occurs while responding, you can either return an empty BindingSet (although this does not give any information about the error occurring) or call the (in case you are using the asynchronous handler methods of the Java Developer API) `future.completeExceptionally(...)` method. *Question*: Whenever I do a post or ask, the memory usage of the Knowledge Engine Runtime skyrockets and it fails with a error (`HTTP 500`) after a minute or two. -- *Answer*: Double check whether you are enabling the reasoner when you create a Smart Connector for your Knowledge Base. When using the REST Developer API, you can disable the reasoner by setting the JSON property `reasonerEnabled` to `false` or leave the property out altogether because by default the reasoner is disabled. Currently, the reasoner is not usable for scenario's where graph patterns are more than about 5 or 6 triple patterns, because the algorithm for graph pattern matching uses too much memory. We are working on improving this algorithm and hopefully allow more use cases to enable the reasoner and benefit the increased interoperability. - -*Question*: There is an existing Knowledge Network with a Knowledge Directory (KD) and multiple Knowledge Engine Runtimes (KERs). How do I setup my own KER and configure it such that it participates in the existing Knowledge Network? -- *Answer*: Every Knowledge Engine Runtime (KER) in distributed mode consists of two APIs: [Knowledge Engine Developer REST API](https://github.com/TNO/knowledge-engine/blob/1.2.5/smart-connector-rest-server/src/main/resources/openapi-sc.yaml) and the [Inter-Knowledge Engine Runtime API](https://github.com/TNO/knowledge-engine/blob/1.2.5/smart-connector/src/main/resources/openapi-inter-ker.yaml). The former is started on port `8280` by default and you use this API to register your Knowledge Base and Knowledge Interactions. The latter API is meant for internal communication between KERs and you do not need to use it yourself. However, you do need to make sure this API is reachable for other KERs in the Knowledge Network. By default this API is available on port 8081, but sometimes you need to change this port using the `KE_RUNTIME_PORT` environment variable. Make sure the latter API of your KER is accessible from the internet and configure its URL when starting the KER with the `KE_RUNTIME_EXPOSED_URL` environment variable. To set this up correctly, you typically install a reverse proxy like NGNIX and open the correct ports in the firewall of the server. For this you need to contact the administrator of the server you are using. A KER starts in distributed mode when it detects the `KD_URL` environment variable. This variable points to the Knowledge Directory of the Knowledge Network. You can configure it using environment variables `KD_URL=`. If the Knowledge Directory is protected using Basic Authentication, you can add the credentials to the KD_URL as described [here](https://stackoverflow.com/a/50528734). - -*Question*: There is an existing Knowledge Engine Runtime (KER) that I want to use to develop a knowledge base. How do I connect to this KER? -- *Answer*: To connect to the existing KER you need to know and have access to the KER's [Knowledge Engine REST Developer API](https://github.com/TNO/knowledge-engine/blob/1.2.5/smart-connector-rest-server/src/main/resources/openapi-sc.yaml). If you have the URL of the KER you want to use, you can test it by activating its `GET /sc` operation via the browser with an URL that looks like this: `/sc` (if the KER is protected with Basic Authentication your browser might ask you for credentials, you can also put the credentials directly into the URL as user info). This operation returns JSON with all the Knowledge Bases (KBs) that are registered with that KER (if there are none, it returns an empty JSON array `[]`). If you run a KER on your own computer (for example using the provided docker image), the `` would typically be `http://localhost:8280`. Now that you tested the KER, you can use the `` to register your KB and Knowledge Interactions (KIs) by activating the different operations that are described in the Open API specification above. +- *Answer*: Double check whether you are enabling the reasoner when you create a Smart Connector for your Knowledge Base. When using the REST Developer API, you can disable the reasoner by setting the JSON property `reasonerEnabled` to `false` or leave the property out altogether because by default the reasoner is disabled. Currently, the reasoner is not usable for scenario's where graph patterns are more than about 5 or 6 triple patterns, because the algorithm for graph pattern matching uses too much memory. We are working on improving this algorithm and hopefully allow more use cases to enable the reasoner and benefit the increased interoperability. \ No newline at end of file diff --git a/docs/docs/get-started/_category_.json b/docs/docs/get-started/_category_.json index 7e664cc1..a49f28a7 100644 --- a/docs/docs/get-started/_category_.json +++ b/docs/docs/get-started/_category_.json @@ -1,8 +1,8 @@ { - "label": "Tutorial", + "label": "Guides", "position": 6, "link": { "type": "generated-index", - "description": "In-depth details about how to use the various elements of the Knowledge Engine" + "description": "In-depth details about how to use the various elements of the Knowledge Engine." } } diff --git a/docs/docs/get-started/knowledge-directory.md b/docs/docs/get-started/knowledge-directory.md new file mode 100644 index 00000000..cd969d5f --- /dev/null +++ b/docs/docs/get-started/knowledge-directory.md @@ -0,0 +1,13 @@ +--- +sidebar_position: 4 +--- +# Starting a Knowledge Directory +This page describes how to setup a Knowledge Directory. + +You can start the Knowledge Directory on ports 8080 with the available JAR: +```bash +cd knowledge-directory/target/ + +java -Dorg.slf4j.simpleLogger.logFile=kd.log -cp "knowledge-directory-1.2.5.jar:dependency/*" eu.knowledge.engine.knowledgedirectory.Main 8080 +``` +You can of course run the Knowledge Directory on another port by replacing 8080 by your preferred port number. diff --git a/docs/docs/get-started/knowledge-interactions.md b/docs/docs/get-started/knowledge-interactions.md index e1442719..3d73ae98 100644 --- a/docs/docs/get-started/knowledge-interactions.md +++ b/docs/docs/get-started/knowledge-interactions.md @@ -3,7 +3,8 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Knowledge Interactions +# Using Knowledge Interactions +This page describes how to register and execute Knowledge Interactions. ## How to instantiate a Knowledge Interaction? diff --git a/docs/docs/get-started/smart-connector.md b/docs/docs/get-started/smart-connector.md index 04ffb75a..e8b868a0 100644 --- a/docs/docs/get-started/smart-connector.md +++ b/docs/docs/get-started/smart-connector.md @@ -5,9 +5,42 @@ sidebar_position: 6 import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Smart Connectors +# Connecting to a Knowledge Network +This page describes how to connect to an (existing) Knowledge Network using a Smart Connector. + +To connect to a Knowledge Network, you need a Knowledge Engine Runtime (KER). +Every KER in distributed mode consists of two APIs: [Knowledge Engine Developer REST API](https://github.com/TNO/knowledge-engine/blob/1.2.5/smart-connector-rest-server/src/main/resources/openapi-sc.yaml) and the [Inter-Knowledge Engine Runtime API](https://github.com/TNO/knowledge-engine/blob/1.2.5/smart-connector/src/main/resources/openapi-inter-ker.yaml). +The former is started on port `8280` by default, and you use this API to register your Knowledge Base and Knowledge Interactions. +The latter API is meant for internal communication between KERs and you do not need to use it yourself. +However, you do need to make sure this API is reachable for other KERs in the Knowledge Network. +By default, this API is available on port 8081, but sometimes you need to change this port using the `KE_RUNTIME_PORT` environment variable. +Make sure the latter API of your KER is accessible from the internet and configure its URL when starting the KER with the `KE_RUNTIME_EXPOSED_URL` environment variable. +To set this up correctly, you typically install a reverse proxy like NGINX and open the correct ports in the firewall of the server. +For this you need to contact the administrator of the server you are using. +A KER starts in distributed mode when it detects the `KD_URL` environment variable. +This variable points to the Knowledge Directory of the Knowledge Network. +You can configure it using environment variables `KD_URL=`. +If the Knowledge Directory is protected using Basic Authentication, you can add the credentials to the KD_URL as described [here](https://stackoverflow.com/a/50528734). + + + +To connect to a network, the following steps are required: +* Get access to the Knowledge Engine Runtime (KER) you want to connect to +* Start a Knowledge Engine Runtime (KER) on your computer +* Register your Knowledge Base via the REST Developer API +* [Register your Knowledge Interactions via the REST Developer API](./knowledge-interactions.md) + +## Getting access to the Knowledge Engine Runtime +To get access to the Knowledge Engine Runtime you want to connect to, you will need its URL. +You can test whether you have access to its REST Developer API by activating its `GET /sc` operation via the browser with a URL like: `/sc` +If the KER is protected with Basic Authentication, your browser might ask you for credentials. +This operation returns JSON with all the Knowledge Bases that are registered with that Knowledge Engine Runtime. +An empty list indicates that no Knowledge Bases are registered with this Knowledge Engine Runtime. +If you run a Knowledge Engine Runtime on your own computer, then the URL is typically `http://localhost:8280`. + +## How to start a Smart Connector? +> *Before starting a Smart Connector, please ensure that there is a Knowledge Directory available to connect to.* -## How to instantiate a Smart Connector? @@ -31,9 +64,6 @@ java -Dorg.slf4j.simpleLogger.logFile=ke.log -cp "smart-connector-rest-dist-1.2. - -## How to add a Smart Connector? - ## How to remove a Smart Connector? ## How to renew the lease of a Smart Connector? \ No newline at end of file diff --git a/docs/docs/quickstart.md b/docs/docs/quickstart.md deleted file mode 100644 index 3c4248b7..00000000 --- a/docs/docs/quickstart.md +++ /dev/null @@ -1,33 +0,0 @@ ---- - sidebar_position: 3 ---- - -# Quickstart - -Setting up a Knowledge Network requires 3 steps: -1. Start a Knowledge Directory -2. Start one (or more) Smart Connectors. -3. Register Knowledge Interactions - -## Starting the Knowledge Directory -Start the Knowledge Directory on ports 8080: -```bash -cd knowledge-directory/target/ - -java -Dorg.slf4j.simpleLogger.logFile=kd.log -cp "knowledge-directory-1.2.5.jar:dependency/*" eu.knowledge.engine.knowledgedirectory.Main 8080 -``` -You can of course run the Knowledge Directory on another port by replacing 8080 by your preferred port number. - -## Starting a Smart Connector -After starting a Knowledge Directory, one can start smart connectors to join the network via: -```bash -cd smart-connector-rest-dist/target - -export KD_URL=http://localhost:8080 -export KE_RUNTIME_EXPOSED_URL=http://localhost:8081 -export KE_RUNTIME_PORT=8081 - -java -Dorg.slf4j.simpleLogger.logFile=ke.log -cp "smart-connector-rest-dist-1.2.5.jar:dependency/*" eu.knowledge.engine.rest.Main 8280 -``` - -## Registering Knowledge Interactions \ No newline at end of file From 2a67979844e8ac7c2d6b12d1b07bb1c7bbdfc562 Mon Sep 17 00:00:00 2001 From: lathouwerssam Date: Wed, 13 Nov 2024 14:08:20 +0100 Subject: [PATCH 45/64] More FAQs rewritten and/or integrated into the documentation --- docs/docs/faq.md | 335 +++++++++--------- docs/docs/get-started/knowledge-base.md | 22 ++ .../get-started/knowledge-interactions.md | 4 + docs/docs/get-started/smart-connector.md | 12 +- docs/docs/java_developer_api.md | 70 ++-- 5 files changed, 240 insertions(+), 203 deletions(-) create mode 100644 docs/docs/get-started/knowledge-base.md diff --git a/docs/docs/faq.md b/docs/docs/faq.md index afcba188..90e35fc4 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -94,146 +94,94 @@ If both Knowledge Bases use the REST API to register Knowledge Interactions and However, when they _do_ specify the communicative act when registering a Knowledge Interaction, they should be compatible. +### How do we get all possible graph patterns and their associated Knowledge Interactions that we need to register? +Within a specific use case or setting, the Knowledge Interactions and their graph patterns are the end result of the ontology engineering process. +In this process you need to decide what data you want to exchange, and which ontologies you want to use for this. +You can also build your own ontology, though we recommend reusing ontologies where possible as it's beneficial for the interoperability with other systems. +### Do you send ACKs for all requests? +There are no explicit ACKs. +The Ask receives an answer of one or more KBs and the Post receives the BindingSet for the optional Result Graph Pattern. +If you need an explicit ACK, you can put it in the result graph pattern of a REACT Knowledge Interaction. +### Can we restrict the results of a Knowledge Interaction? +The first way to restrict a result is to limit which Knowledge Bases are contacted. +When executing an interaction, you can specify a single knowledge base. +In this case, it will only contact that specific knowledge base to try and answer your query. -*Question*: There is a limited and defined number of KI. How do we proceed to "generate" all possible graph patterns and so associated KI ? -- *Answer*: The KIs used should match and the KIs and their Graph Patterns are the end result of the SAREFization process for services. Also, if you need pilot specific KIs, you can agree on them within your pilot using SAREF or a pilot specific ontology. +The second way to restrict a Knowledge Interaction is to use literals. +This can be done either in the graph pattern of the registered Knowledge Interaction _or_ in the binding set when executing an interaction. +This way you will only receive data related to that literal. -*Question*: what about ACKs of the requests? -- *Answer*: There are no explicit ACKs. The Ask receives an answer of one or more KBs and the Post receives the BindingSet for the optional Result Graph Pattern. If you need an explicit ACK (for example with the turn on the light example in the presentation) you can use the result Graph Pattern to contain the ACK. +But what if you want to make this more general? +Take for example a person with a name and an email address. +You may want to be able to request the email address for a person with a certain name, but do not want to provide the name for a specific email address. -*Question*: It means also that a SparQL query like you see below is a SparQL query and is not something that can be used at the KE REST API interface. Only the part within the brackets is a basic graph pattern. Is that right? - - - - -*Question*: In POST /sc/ki one registers a Knowledge Interaction along with the knowledge interaction type. In POST /sc/ask one queries for some results by referring to a KI and providing an incomplete binding set. The result will be a complete binding set. In your presentation KE for dummies slide 12, you mentioned that one could restrict the question (at the react side). I didn’t find in the rest of the slides on how one can do that, except by having a literal in the registered KI. In your example a person has a name and a email address, but the logic only allows to ask for the email address associated with a person with a certain name, but it does not allow to get the name associated with a specific email address. How do we impose such a restriction, or we can’t do this at this stage? - -- *Answer*: If the logic does not allow the inverse, then you should not use an Ask/Answer Knowledge Interactions with a graph pattern like: - ```sparql - ?person :hasUsername ?userName . - ?person :hasEmailaddress ?emailAddress . - ``` - - In that case you want to use the Post/React Knowledge Interactions. These have two Graph Patterns and the *argument* graph pattern would look something like: - - ```sparql - ?person :hasUsername ?userName . - ``` - - and the *result* graph pattern would look something like: - - ```sparql - ?person :hasEmailaddress ?emailAddress . - ``` - - This tells the Knowledge Engine that you cannot send it an email address and receive the username. - -*Question*: In the context of graph pattern matching, can you explain the subset/superset condition: "when the Graph Pattern" of the sender is a superset of the Graph Pattern of the receiver' ? Is it at definition level or at data level? I found (at ontology level) the following explanation: "Ontology O1 is a subset of ontology O2 if all definitions in O1 are contained in O2 (O2 is the superset of O1)". -1) More concrete: is 'a b c .' graph pattern a subset or a superset of 'a b c . d e f.' ? -2) In case of Post/React knowledge interactions, both argument graph patterns must match and also both result graph patterns must match? -3) In case of Post/React knowledge interactions graph pattern matching, is the POST side regarded as the sender for the argument graph pattern and the REACT side as the sender for the result graph pattern? -4) Let's assume the REACT side result pattern is like 'a b c . d e f.' and the POST side result pattern is 'a b c .'. Is this allowed? So it is similar to a 'SELECT' in SQL? The result binding set at the POST side is then also reduced, I assume (not in number of _records_, but _fields_). -- *Answer*: We do not have defined the subset/superset terms within the context of the Knowledge Engine and Graph Patterns, but it would indeed be helpful to do so. Since the idea of the Knowledge Engine is that the *data* is always kept at the source and is only retrieved when necessary for an interaction, it is more suitable to talk about subset/superset at the definition level and not at the data level. This is because all data is simply not available in a single location. The definition level is also the level where currently the *matching* between graph patterns happens. The *matcher* (as opposed to the *reasoner*) does not support subset/superset matching. - - 1) I would say graph pattern `a b c` is a subset of the graph pattern `a b c . d e f`. Note that graph pattern typically contain variables like `?a`. Note that graph pattern matching ignores variable names and triple order. - 2) Yes, the argument graph pattern and result graph pattern should both match if two Post/React Knowledge Interactions want to exchange data. Note that this probably changes when the Knowledge Engine uses a reasoner instead of a matcher. - 3) Yes, the PostKnowledgeInteraction sends the argument and the ReactKnowledgeInteraction sends the (optional) result. - 4) Currently, this will not work, because we are using a graph pattern *matcher* instead of a *reasoner*. I expect the reasoner to indeed allow them to interact if the POST side result pattern is a subset of the REACT side result pattern. In that case the result binding set at the POST side should also be a subset (in fields) of the binding set given from the REACT side. So, the results are always given to a Knowledge Base in its own terminology, this already happens by translating the variable names, but should also happen in the way you describe once the reasoner is active. - -*Question*: Do we need a long polling connection for every Knowledge Interaction? Doesn't that get very complicated? -- *Answer*: No, per Smart Connector (or Knowledge Base) you need a single long polling connection to receive all interactions from the Knowledge Engine. Do remember that this long polling connection is returned with status code 202 every 29 seconds and also needs to be reestablished after you receive data via it. - -*Question*: I’m trying to understand how timeseries as part of a larger graph pattern are expressed in a binding set. +In this case, if logic does not allow the inverse, then you should not use an Ask/Answer Knowledge Interactions with a graph pattern like: +```sparql +?person :hasUsername ?userName . +?person :hasEmailaddress ?emailAddress . +``` -For instance: -Let's say we have some graph pattern like: +Instead, you want to use the Post/React Knowledge Interactions. +These have two Graph Patterns and the *argument* graph pattern would look something like: ```sparql -?timeseries rdf:type ex:Timeseries . -?timeseries ex:hasMeasurement ?measurement . -?measurement rdf:type saref:Measurement . -?measurement saref:hasFeatureOfInterest ?room . -?room rdf:type saref:Room . -?measurement saref:observedProperty saref:Temperature . -?measurement saref:hasSimpleResult ?temperature . -?measurement ex:hasTimestamp ?ts . +?person :hasUsername ?userName . ``` -And the timeseries returns an array of temperature values and timestamp for each value. +and the *result* graph pattern would look something like: -In the ANSWER Knowledge interaction will the binding set be something like: -```json -[ - { - "timeseries": "", - "measurement": "", - "room": "", - "temperature": "\"21.2\"^^", - "ts": "\"2020-10-16T22:00Z\"^^some_timestamp_type" - }, - { - "timeseries": "", - "measurement": "", - "room": "", - "temperature": "\"21.4\"^^", - "ts": "\"2020-10-16T23:00Z\"^^some_timestamp_type" - }, - { - "timeseries": "", - "measurement": "", - "room": "", - "temperature": "\"21.6\"^^", - "ts": "\"2020-10-16T24:00Z\"^^some_timestamp_type" - } -] +```sparql +?person :hasEmailaddress ?emailAddress . ``` +This way the Knowledge Engine can send a username and receive the email address, but it cannot do the inverse. -Is the following statement right: the IRI is filled in by the service specific adapter? -Can this IRI then be used to for example ask more info about `` ? That would mean that the service specific adapter should use an ID that is stored, and not some temporal/volatile ID for the IRI. Because that means that you can give a reference to an object in the answers. Of course you have to provide then a graph pattern to allow the retrieval of this object resulting in another binding set. -- *Answer*: You are correct with respect to ``. Ideally you should be able to retrieve more information about it and the service should not randomly generate it, but use a stored id. +### How do the reactive Knowledge Interactions compare to a publish-subscribe broker? +The Knowledge Engine functions similarly to a publish-subscribe broker. +The React Knowledge Interaction can be seen as a *subscribe* and the Post Knowledge Interaction can be seen as a *publish*. +All *matching* React Knowledge Interactions will receive the Post Knowledge Interaction's data. -*Question*: I'm wondering if the reactive Knowledge Interactions function the same way that a publish/subscribe broker would work. Is it possible for one service to POST something to the KE, while multiple other services listen to the interaction. This way they all receive the data they need whenever it is available. +Aside from this interaction pattern, POST/REACT can also be used for an interaction pattern similar to function calls. +The POST/REACT Knowledge Interactions support an (optional) result graph pattern that describes what data is sent back (the results) after receiving data (the arguments). +The results from all *matching* REACTs are aggregated before being returned to the POST side. -If I understand correctly, a REACT KI can also send data back upon receiving it. If publish/subscribe is possible, how would this communication work? Would the Knowledge Base on the POST side receive all responses? -- *Answer*: Yes, it functions similarly to a publish/subscribe broker. The React Knowledge Interaction can be seen as a *subscribe* and the Post Knowledge Interaction can be seen as a *publish*. All *matching* React Knowledge Interactions will receive the Post Knowledge Interaction's data. - For a more functional interaction pattern, the Post/React Knowledge Interaction also support a (optional) result graph pattern that describes the data that will be sent back (the results) after receiving data (the arguments). The result will indeed be aggregated and returned to POST side. +### Inconsistent or missing results with POST/REACT +Users are sometimes surprised that their POST Knowledge Interaction is not returning with results. +This situation typically occurs when there are multiple Knowledge Bases available that can answer or want to react to a Knowledge Interaction. +The Knowledge Engine will aggregate all results before returning and thus delays in answers are possible. +If your Knowledge Base has not received a request for data, it may be waiting until others have answered or reacted. +If your Knowledge Base has received a request but the results are not returned, it is likely because the Knowledge Engine is waiting for the results of other matching Knowledge Bases. +We have an [issue](https://github.com/TNO/knowledge-engine/issues/109) which would allow you to instruct the Knowledge Engine to not wait indefinitely for an answer, but this is still on our todo list. +Until then, we recommend using more specific graph patterns. -*Question*: It's not clear for me how the interaction between the service store and the KE should be seen. In our case we have a system acting as a knowledgebase requesting information for other partners. This information will then be combined and using some specific application logic new information will be exposed. Both will be implemented through the knowledge engine. By looking at the examples I managed to implement a small application which is working fine but still I have some questions: +We have also seen the following situation: +* POST requests were sent +* The corresponding Knowledge Base with a corresponding REACT did not always receive this +* When receiving a POST, the REACT side sent a confirmation properly +* The Knowledge Base with the POST rarely received the confirmation from the REACT +* There were 2 REACT Knowledge Interactions with the same argument graph pattern but different result graph patterns. -The registration of the KB and KIs should only be done once I suppose? -Let's say we have a ASK-ANSWER (our KB will create answer) and a POST-REACT KI (our KB will react). Will we need to restart the connection with the KE? I thought something was mentioned during the sync call that an interaction is only active for 30 mins. -Is there a complete example available where the KE and the service store is combined? -- *Answer*: In general, the exact link between service store and knowledge engine is still evolving and under discussion. Currently, as far as I know, this link is that the service store keeps a list of services with metadata that have a generic adapters that also provide access to the Interoperability layer (i.e. Knowledge Engine). +In this setting, there were 2 REACT Knowledge Interactions with the same argument graph pattern but different result graph patterns. +One result graph pattern matched the POST, but the other did not. +This prevented the REACT interaction to react to the POST and thus the POST never got a response. +So if you have the same argument graph pattern for several interactions, be careful that *all* REACTs and *all* POSTs use the same result graph pattern - - The registration of the KB and KIs should only be done once I suppose? - - Typically, the Generic Adapter (and Smart Connector) should be available as long as your system (Knowledge Base) is available and in that case you only need to register your KIs once. KIs are, however, dynamic and can be added and removed if this is useful for the use case. - - Let's say we have a ASK-ANSWER (our KB will create answer) and a POST-REACT KI (our KB will react). Will we need to restart the connection with the KE? I thought something was mentioned during the sync call that an interaction is only active for 30 mins. - - The Knowledge Engine REST Developer API uses long-polling to notify you when your KB needs to react. This long-polling connection will automatically return every *29 seconds* with status code 202 to prevent certain proxies from blocking it. So, you need to reestablish this long-polling connection when you receive a 202. This does not affect the Knowledge Base and Knowledge Interactions. - - Is there a complete example available where the KE and the service store is combined? - - I think @aleksandar.tomcic.vizlore.com is working on examples that use the generic-adapter which maintains the link between the Service Store and the Knowledge Engine. For the Knowledge Engine only, we do have an very simple Python example available here: https://gitlab.inesctec.pt/interconnect/ke-python-examples -*Question*: I use a very generic graph pattern like `?s ?p ?o` for my PostKnowledgeInteraction, but my other KB does not get a request. Or, you do get a request, but your post is not returning with the results. -- *Answer*: We noticed multiple knowledge bases that register graph patterns like "?s ?p ?o" (i.e. from the examples we provided). If this is the case, it might occur that you ask a question or post some data and there are multiple KBs available that can answer or want to react to that type of data (i.e. they use a matching graph pattern). This means that you may not receive a request for data on your KB until one of the others has answered or reacted, or you might get a request, but you do not see the expected reaction in your other KB, because the Interoperability layer is waiting for the other matching KBs to answer/react. -We have an issue #95 which would allow you to instruct the Knowledge Engine to not wait indefinitely for an answer, but this is still on our todo list. Until then, we recommend using more specific graph patterns for testing. For example: -```json -{ - "knowledgeInteractionType": "ReactKnowledgeInteraction", - "argumentGraphPattern": "?s ?o ." -} -``` +### We see a spike in memory usage whenever an ASK or POST is executed. What is happening? +Most likely you have enabled the reasoner when you created a Smart Connector for your Knowledge Base. +When an ASK or POST is executed, the Knowledge Engine will use the reasoner to infer new data and orchestrate the data exchange (for more details see [Reasoning](./reasoning.md)). +When you have large graph patterns and/or many bindings, the reasoner's time and memory consumption can be quite large. +If you have no need for this reasoning capability, you can limit its resource usage by disabling the reasoner. +When using the REST Developer API, you can disable the reasoner by setting the JSON property `reasonerEnabled` to `false` or leave the property out altogether because by default the reasoner is disabled. -*Question*: Some partners use a query like API request whereby they can specify via an input field what kind of results they expect back, together with the device id (temperature sensor, smart plug, meter,...) -For instance one can select the list of parameters (status, temperature, power, voltage, door_state, humidity, luminosity etc.) that should be returned. -In the result below is only the power_total returned, but additional fields could be selected/set in the input field (depending on the device type it can return more than one value) -This is a quite generic approach. So for this one API call there will be a lot of KIs, correct or can this realized with one KI? -Is the best approach to create a KI graph pattern per device type that returns all parameters? -What if I'm only interested in one parameter (not possible now because an exact match is required in this version, but possible in a next version)? -Result: + +### How to deal with a query-like Knowledge Base where you can specify what properties you want to be returned? +Some users use a data source where you can specify what kind of results you expect back via an input field. +A common example we have seen is a query-like API request where you can specify what properties should be returned, e.g. for a device: ```json { @@ -251,10 +199,6 @@ Result: [ "2021-05-12T08:53:30.052924161Z", 241.63 - ], - [ - "2021-05-12T09:03:00.050337173Z", - 119.67 ] ] } @@ -263,60 +207,46 @@ Result: ] } ``` +Naturally, in this case you want to put other data in a Knowledge Interaction depending on which fields are selected. +The difficulty is, however, that a Knowledge Interaction is predefined and not dynamic. -- *Answer*: This is indeed quite a generic approach that, unfortunately, cannot be done with the current version of the KE (as you already correctly mention: because of exact matching). You could in theory register a lot of Knowledge Interactions, although I am not sure that is the best approach. If there is a limited set of fields that are always available, I would recommend providing a single large knowledge interaction. This would, however, mean that the asker registers this large knowledge interaction as well. - An alternative approach, which maybe mimics the generic behaviour of the API, could be to provide a measurement graph pattern like: - - ```sparql - ?deviceId . - ?deviceId ?m . - ?m ?p . - ?p ?fieldType . - ?m ?ts . - ?m ?val . - ``` - - This would allow the asking side to provide a binding set with a particular deviceId and 'fieldTypes': - - ```json - [ - { - "deviceId": "", - "fieldType": "" - }, - { - "deviceId": "", - "fieldType": "" - } - ] - ``` - - The answer side would need to parse this correctly (which is not trivial) and fill the bindingset correctly. - When we use a reasoner instead of a matcher, the ask side would not need to use the large graph pattern, but only those fieldTypes that it is interested in. The reasoner would still call the answer side, but limit the results to what the ask side needs. - So, there are several ways to handle this and each approach has advantages and disadvantages. Unfortunately, there is not a single best practice to solve this. - -*Question*: Have there been any discussions about availability and scalability of knowledge engine? Seeing as a knowledge base can only have a single polling connection, it does not seem possible to spin up multiple instances of a reactive adapter to the same knowledge base. This would limit the scalability potential by quite a lot. Are there any workarounds around this perhaps? - - *Answer*: Although scalability and availability have been mentioned several times now, there has not been any thorough discussions about them. One reason for this is that other things have been our priority in the last couple of months. This also means that the Knowledge Engine has not been designed to handle enormous amounts of data (although it is event-based and multi-threaded), but we expect it to be good enough for most use cases. The exact limitations with respect to throughput and latency will probably become clearer in the comings months and since we have not been really concerned with performance I expect there is also still room for improvement. - - Regarding multiple instances of a reactive adapter, we advice a single smart connector per knowledge base and each knowledge base is indeed limited to a single long polling connection. There are several ways to circumvent this limitation: - 1) consider using the _Java_ Developer API (not sure if the generic adapter will provide this in a future version). It uses handlers and is multi threaded, so it is much better scalable than the _REST_ Developer API. - 2) divide Knowledge Interactions over multiple Smart Connectors. This would allow you to have a single long polling connection per Knowledge Interaction. - - B.T.W.: We did not choose HTTP as the protocol in order to support web-server like scalability, but because most partners were familiar with it and the tooling and specification is very accessible. - -*Question*: I can hardly figure how data are exchanged between services in the Interworking layer architecure. When a service A ask for data form another service (B) through the KE (for example give me the temperature setpoint for this household) it is not clear if the final data (ie the temprature setpoint) is sent directly from Servica A Endpoint to Service B Endpoint or if it transit through the Interworking layer infrastructure. +There are several ways to tackle this. +The first option is to register a lot of Knowledge Interactions that cover all the possibilities. +The second option is to register one single graph pattern that covers all properties and to enable the reasoner. +Alternatively, you can instantiate an ASK with a generalized graph pattern like: +```sparql +?deviceId . +?deviceId ?m . +?m ?p . +?p ?fieldType . +?m ?ts . +?m ?val . +``` +This allows the asking side to provide a binding set that specifies which properties should be returned (in this case 'fieldType' for a specific 'device'). +```json +[ + { + "deviceId": "", + "fieldType": "" + }, + { + "deviceId": "", + "fieldType": "" + } +] +``` +The ANSWER side will need to parse this correctly (which is not trivial) and fill the binding set correctly. - - *Answer*: If two services A and B want to exchange data in an interoperable manner, they both should use interoperability layer. They first register their capabilities (using the Ask and Answer Knowledge Interactions) and then the actual data exchange can happen. This data exchange is orchestrated by the interoperability layer and, so, the data transits through the interoperability layer. Service A asks the temperature set point to its Smart Connector and the interoperability layer will contact the Smart Connector of Service B to retrieve the answer from its Service B and sends the result back. +### Any thoughts on the scalability of the single long polling connection used by each Smart Connector? +Currently, each Smart Connector uses a single long polling connection to receive all interactions from the Knowledge Engine. +The Knowledge Engine is event-based and multithreaded, and while not designed to handle enormous amounts of data, this has not been a limiting factor in our use cases so far. - For more information about the Knowledge Engine and Knowledge Interactions, see the recorded workshop on our shared drive: https://drive.inesctec.pt/f/16182787 +If the current setup, a Smart Connector with a single long polling connection, is limiting for you, there are several ways to circumvent this: +1) Use the _Java_ Developer API. + It uses handlers and is multithreaded, so it is scales better than the _REST_ Developer API. +2) Divide Knowledge Interactions over multiple Smart Connectors. + This allows you to have a single long polling connection per Knowledge Interaction. -*Question*: We noticed that we cannot get consistent results when testing a POST/REACT exchange, both locally and with a partner. So to summarize: -* we can send a POST request -* the REACT sides don't receive 100% of the time (more like 25%), whether it is locally running or from our partner's platform -* When receiving the POST, the REACT side sends the confirmation properly -* The POST side rarely receives the confirmation from REACT (around 5% of the time) - -- *Answer*: We were doing tests with 2 REACT KB that had different result graph patterns. One matched our post but not the other. This prevented the react process to react to the post and so the post never got a response. So to remember to work properly: For the same argument graph pattern, ALL reacts and ALL posts need the same answer graph pattern. Also: the post will receive the answers from the react once they have all answered. The answers will be aggregated into one. *Question*: I have a question about how the compliance checker and reasoner are supposed to work. **If this is the wrong place for asking this, please direct me to the right people!** @@ -391,8 +321,63 @@ I guess my question is more specifically: “Will the compliance checker look at And last but not least, a short reaction to Georg’s remark: “I think it makes sense to think about the GP as the body of a query”. It certainly does, although within the context of the Knowledge Engine graph patterns are also used for non-query like interactions. -*Question*: What should our KB do When we receive a request for data (either via an ANSWER or REACT Knowledge Interaction), but we do not have a response? -- *Answer*: You should send an empty binding set when you do not have a response. Also, when your REACT Knowledge Interaction has no result graph pattern, you should always return an empty binding set to the Knowledge Engine. If an error occurs while responding, you can either return an empty BindingSet (although this does not give any information about the error occurring) or call the (in case you are using the asynchronous handler methods of the Java Developer API) `future.completeExceptionally(...)` method. +- *Question*: In the context of graph pattern matching, can you explain the subset/superset condition: "when the Graph Pattern" of the sender is a superset of the Graph Pattern of the receiver' ? Is it at definition level or at data level? I found (at ontology level) the following explanation: "Ontology O1 is a subset of ontology O2 if all definitions in O1 are contained in O2 (O2 is the superset of O1)". +1) More concrete: is 'a b c .' graph pattern a subset or a superset of 'a b c . d e f.' ? +2) In case of Post/React knowledge interactions, both argument graph patterns must match and also both result graph patterns must match? +3) In case of Post/React knowledge interactions graph pattern matching, is the POST side regarded as the sender for the argument graph pattern and the REACT side as the sender for the result graph pattern? +4) Let's assume the REACT side result pattern is like 'a b c . d e f.' and the POST side result pattern is 'a b c .'. Is this allowed? So it is similar to a 'SELECT' in SQL? The result binding set at the POST side is then also reduced, I assume (not in number of _records_, but _fields_). +- *Answer*: We do not have defined the subset/superset terms within the context of the Knowledge Engine and Graph Patterns, but it would indeed be helpful to do so. Since the idea of the Knowledge Engine is that the *data* is always kept at the source and is only retrieved when necessary for an interaction, it is more suitable to talk about subset/superset at the definition level and not at the data level. This is because all data is simply not available in a single location. The definition level is also the level where currently the *matching* between graph patterns happens. The *matcher* (as opposed to the *reasoner*) does not support subset/superset matching. + + 1) I would say graph pattern `a b c` is a subset of the graph pattern `a b c . d e f`. Note that graph pattern typically contain variables like `?a`. Note that graph pattern matching ignores variable names and triple order. + 2) Yes, the argument graph pattern and result graph pattern should both match if two Post/React Knowledge Interactions want to exchange data. Note that this probably changes when the Knowledge Engine uses a reasoner instead of a matcher. + 3) Yes, the PostKnowledgeInteraction sends the argument and the ReactKnowledgeInteraction sends the (optional) result. + 4) Currently, this will not work, because we are using a graph pattern *matcher* instead of a *reasoner*. I expect the reasoner to indeed allow them to interact if the POST side result pattern is a subset of the REACT side result pattern. In that case the result binding set at the POST side should also be a subset (in fields) of the binding set given from the REACT side. So, the results are always given to a Knowledge Base in its own terminology, this already happens by translating the variable names, but should also happen in the way you describe once the reasoner is active. -*Question*: Whenever I do a post or ask, the memory usage of the Knowledge Engine Runtime skyrockets and it fails with a error (`HTTP 500`) after a minute or two. -- *Answer*: Double check whether you are enabling the reasoner when you create a Smart Connector for your Knowledge Base. When using the REST Developer API, you can disable the reasoner by setting the JSON property `reasonerEnabled` to `false` or leave the property out altogether because by default the reasoner is disabled. Currently, the reasoner is not usable for scenario's where graph patterns are more than about 5 or 6 triple patterns, because the algorithm for graph pattern matching uses too much memory. We are working on improving this algorithm and hopefully allow more use cases to enable the reasoner and benefit the increased interoperability. \ No newline at end of file +*Question*: I’m trying to understand how timeseries as part of a larger graph pattern are expressed in a binding set. + +For instance: +Let's say we have some graph pattern like: + +```sparql +?timeseries rdf:type ex:Timeseries . +?timeseries ex:hasMeasurement ?measurement . +?measurement rdf:type saref:Measurement . +?measurement saref:hasFeatureOfInterest ?room . +?room rdf:type saref:Room . +?measurement saref:observedProperty saref:Temperature . +?measurement saref:hasSimpleResult ?temperature . +?measurement ex:hasTimestamp ?ts . +``` + +And the timeseries returns an array of temperature values and timestamp for each value. + +In the ANSWER Knowledge interaction will the binding set be something like: +```json +[ + { + "timeseries": "", + "measurement": "", + "room": "", + "temperature": "\"21.2\"^^", + "ts": "\"2020-10-16T22:00Z\"^^some_timestamp_type" + }, + { + "timeseries": "", + "measurement": "", + "room": "", + "temperature": "\"21.4\"^^", + "ts": "\"2020-10-16T23:00Z\"^^some_timestamp_type" + }, + { + "timeseries": "", + "measurement": "", + "room": "", + "temperature": "\"21.6\"^^", + "ts": "\"2020-10-16T24:00Z\"^^some_timestamp_type" + } +] +``` + +Is the following statement right: the IRI is filled in by the service specific adapter? +Can this IRI then be used to for example ask more info about `` ? That would mean that the service specific adapter should use an ID that is stored, and not some temporal/volatile ID for the IRI. Because that means that you can give a reference to an object in the answers. Of course you have to provide then a graph pattern to allow the retrieval of this object resulting in another binding set. +- *Answer*: You are correct with respect to ``. Ideally you should be able to retrieve more information about it and the service should not randomly generate it, but use a stored id. diff --git a/docs/docs/get-started/knowledge-base.md b/docs/docs/get-started/knowledge-base.md new file mode 100644 index 00000000..1215d060 --- /dev/null +++ b/docs/docs/get-started/knowledge-base.md @@ -0,0 +1,22 @@ +--- +sidebar_position: 99 +--- +# Implementing a Knowledge Base +This page describes how to implement your own Knowledge Base given a data source. + +There are three approaches to implement your own Knowledge Base: +1. Java +2. REST Developer API +3. Knowledge Mapper (based on a Python client) +4. JavaScript client + +The Knowledge Mapper is a tool we have built to easily connect to several common data sources (SQL, RDF, APIs). +If you're interested in using the Knowledge Mapper or JavaScript client, please reach out to us as they are not yet open source. + +## Implementing your own Knowledge Interaction +When you receive a request for data via an ANSWER or REACT Knowledge Interaction, you should return the expected results, e.g. by retrieving data from an API. + +If you do not have a response, then you should send an empty binding set. +Also, when your REACT Knowledge Interaction has no result graph pattern, you should always return an empty binding set to the Knowledge Engine. + +If an error occurs while responding, you can either return an empty BindingSet (although this does not give any information about the error occurring) or call the (in case you are using the asynchronous handler methods of the Java Developer API) `future.completeExceptionally(...)` method. diff --git a/docs/docs/get-started/knowledge-interactions.md b/docs/docs/get-started/knowledge-interactions.md index 3d73ae98..f130241f 100644 --- a/docs/docs/get-started/knowledge-interactions.md +++ b/docs/docs/get-started/knowledge-interactions.md @@ -1,4 +1,5 @@ --- +sidebar_position: 7 --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; @@ -7,6 +8,9 @@ import TabItem from '@theme/TabItem'; This page describes how to register and execute Knowledge Interactions. ## How to instantiate a Knowledge Interaction? +> You only need to register your Knowledge Interactions once. +> They are, however, dynamic and can be added and removed if needed. + diff --git a/docs/docs/get-started/smart-connector.md b/docs/docs/get-started/smart-connector.md index e8b868a0..25a7edf1 100644 --- a/docs/docs/get-started/smart-connector.md +++ b/docs/docs/get-started/smart-connector.md @@ -41,9 +41,13 @@ If you run a Knowledge Engine Runtime on your own computer, then the URL is typi ## How to start a Smart Connector? > *Before starting a Smart Connector, please ensure that there is a Knowledge Directory available to connect to.* +> Typically, you start a single Smart Connector which should be available as long as your system (Knowledge Base) is available. + +Assuming `this` is your knowledge base, you can make a `SmartConnector` as follows: + ```java SmartConnector sc = SmartConnectorBuilder.newSmartConnector(this).create(); ``` @@ -66,4 +70,10 @@ java -Dorg.slf4j.simpleLogger.logFile=ke.log -cp "smart-connector-rest-dist-1.2. ## How to remove a Smart Connector? -## How to renew the lease of a Smart Connector? \ No newline at end of file +## How to renew the lease of a Smart Connector? + +## How to deal with the long polling connection of a Smart Connector? +Each Smart Connector uses a single long polling connection to receive all interactions from the Knowledge Engine. +The Knowledge Engine REST Developer API uses long-polling to notify you when your KB needs to react. +This long-polling connection will automatically return every *29 seconds* with status code 202 to prevent certain proxies from blocking it. +You will need to reestablish this long polling connection when you receive a 202 and after you receive data via it. diff --git a/docs/docs/java_developer_api.md b/docs/docs/java_developer_api.md index 121f64e3..eab82efb 100644 --- a/docs/docs/java_developer_api.md +++ b/docs/docs/java_developer_api.md @@ -2,17 +2,16 @@ sidebar_position: 5 --- -# Java Developer API +# Getting Started As a developer, you might be thinking "this all seems awfully complex and painful". If that sounds like you, we have good news: you don't have to bother with most of it! -This section explains the Java Developer API of the Knowledge Engine. - -Note that there is also a [REST Developer API](https://github.com/TNO/knowledge-engine/blob/master/smart-connector-rest-server/src/main/resources/openapi-sc.yaml) available. +This section will take you through the main steps that are needed to get started. +We will show this using the Java Developer API, but it can also be done using the [REST Developer API](https://github.com/TNO/knowledge-engine/blob/master/smart-connector-rest-server/src/main/resources/openapi-sc.yaml). We provide an implementation of a `SmartConnector` that you can instantiate as a Java object. -You are responsible for: +When you want to exchange information within a Knowledge Network, you are responsible for: - Registering the knowledge that your knowledge base requests or provides. - Implementing handlers that are called when certain knowledge is requested or provided. @@ -20,9 +19,9 @@ You are responsible for: The SmartConnector uses this to register itself in the knowledge network. -The following subsections further explain how a SmartConnector can be instantiated and, how the different kinds of knowledge can be registered. +The following sections further explain how a SmartConnector can be instantiated and, how you can register the knowledge that your knowledge base provides. -## Instantiating and configuring a SmartConnector +## Instantiating and Configuring a SmartConnector @@ -32,8 +31,8 @@ Assuming `this` is your knowledge base, you can make a `SmartConnector` as follo SmartConnector sc = SmartConnectorBuilder.newSmartConnector(this).create(); ``` -## Registering and using Knowledge Interactions -Currently, a SmartConnector is required to register the patterns of knowledge that it will request from the network. (See #67) +## Registering and Using Knowledge Interactions +Currently, a SmartConnector is required to register the patterns of knowledge that it will request from the network. A knowledge request can be registered as follows: ```java @@ -44,10 +43,9 @@ sc.register( ``` where `graphPattern` is a string describing an RDF graph pattern where variables are prefixed with a `?`. -Graph patterns consist of one or more triples separated by a dot (.) and each triple consists of a subject, predicate and object node. Each node can be either a variable (using a question mark `?var` prefix), a URI (using the `` or a literal (using quotes `"hello"`). -More information on graph patterns can be found in: - -- Graph Pattern syntax is based on W3C's [SPARQL 1.1 graph pattern](https://www.w3.org/TR/rdf-sparql-query/#BasicGraphPatterns). +Graph patterns consist of one or more triples separated by a dot (.) and each triple consists of a subject, predicate and object node. +Each node can be either a variable (using a question mark `?var` prefix), a URI (using the `` or a literal (using quotes `"hello"`). +Graph Pattern syntax is based on W3C's [SPARQL 1.1 basic graph patterns](https://www.w3.org/TR/rdf-sparql-query/#BasicGraphPatterns). As an example, assume `graphPattern` is the following graph pattern: ```sparql @@ -64,7 +62,7 @@ where the variables are represented by circles and the fixed URIs are represente The graph pattern above matches on temperature measurements in rooms. -### Querying the network +### Querying the Network When querying the network for the pattern, the variables (`?measurement`, `?room`, and `?temperature`) can be bound to known values, to limit the possible matches. @@ -82,9 +80,12 @@ BindingSet resultBindings = askResult.getBindings(); The results from the knowledge network are in the set of bindings. The `AskResult` contains other useful information, such as `AskExchangeInfo`, which gives information about the data's origins. -## Registering and using other kinds of Knowledge Interactions +### Other Types of Knowledge Interactions + +Aside from `ASK` knowledge interactions, there are also `ANSWER`, `REACT`, and `POST` interactions. +`ASK` is used to request information, `ANSWER` is used to reply to a request, `POST` is used to publish information, and `REACT` can be used to subscribe to information. +They can be registered and executed in a similar way to the `ASK` explained above (see [here](./get-started/knowledge-interactions.md)). -Aside from `ASK` knowledge interactions, there are also `ANSWER`, `REACT`, and `POST` interactions, which will be explained here in the future. ## Bindings The data that is shared is inside the `Binding` objects. @@ -102,10 +103,11 @@ As you can see, a `Binding` object is essentially a map from variable names to t Two important things should be noted: -1. The keys of the bindings MUST correspond to the variable names in the graph pattern, and they must be complete (all variables must have a value bound to them). (This last restriction does not apply to the bindings given with ASK requests; they can be partial of even empty.) +1. The keys of the bindings MUST correspond to the variable names in the graph pattern, and they must be complete (all variables must have a value bound to them). + (This last restriction does not apply to the bindings given with ASK requests; they can be partial of even empty.) 2. The values of the bindings MUST be valid IRIs (https://www.w3.org/TR/turtle/#sec-iri) (for now without prefixes, so full IRIs) or valid literals (https://www.w3.org/TR/turtle/#literals). -### Binding sets +### Binding Sets A result of a knowledge interaction can have more than 1 match. These matches are collected in a `BindingSet`, which is simply a set of bindings. @@ -114,9 +116,13 @@ The reason why we call it a binding set is because the elements in a set are uno Due to the nature of RDF, the ordering of the bindings in a binding set cannot be used to encode any information when exchanging data. Thus, if you need ordering, this should be encoded explicitly, e.g. by using numbers or timestamps, and the receiving end should use this information to put the information in the correct order. -## Expressibility +## Expressiveness -The Graph Pattern syntax has a limited expressibility. This means there are certain things that you might want to express with them, but are unable to. Sometimes this means it limits the actual data exchange, but sometimes there are work-arounds. One of the limitations is related to one-to-many relations. Take the following RDF about parents and children in which a parent has a one-to-many relation with its children: +The Graph Pattern syntax has a limited expressivity. +This means there are certain things that you might want to express with them, but are unable to. +Sometimes this means it limits the actual data exchange, but sometimes there are workarounds. +One of the limitations is related to one-to-many relations. +Take the following RDF about parents and children in which a parent has a one-to-many relation with its children: ```sparql ex:parent1 rdf:type ex:Parent . @@ -131,7 +137,11 @@ ex:parent1 ex:hasChild ex:child3 . ex:child3 rdf:type ex:Child . ``` -As you can see, RDF is perfectly capable of expressing one-to-many relations. Now imagine that you want to use the Interoperability layer to exchange information about parents and their children. For this you need to let the Interoperability layer know that you can participate in data exchanges about parents and their children. You do this using a graph pattern (and for example an AnswerKnowledgeInteraction). Let's take the following graph pattern: +As you can see, RDF is perfectly capable of expressing one-to-many relations. +Now imagine that you want to use the Knowledge Engine to exchange information about parents and their children. +For this you need to let the Knowledge Engine know that you can participate in data exchanges about parents and their children. +You do this using a graph pattern (and for example an AnswerKnowledgeInteraction). +Let's take the following graph pattern: ```sparql ?parent rdf:type ex:Parent . @@ -139,9 +149,14 @@ As you can see, RDF is perfectly capable of expressing one-to-many relations. No ?someChild rdf:type ex:Child . ``` -Since the syntax of graph patterns is limited, you cannot express that the `ex:hasChild` relation between a parent and a child is a one to many relation. So, how does the interoperability layer exchange the RDF data about parents and their children above? This is where bindingsets come in. +Since the syntax of graph patterns is limited, you cannot express that the `ex:hasChild` relation between a parent and a child is a one-to-many relation. +So, how does the Knowledge Engine exchange the RDF data about parents and their children above? +This is where binding sets come in. -A bindingset provides a collection of 'solutions' to the graph pattern, i.e. combinations of values for the available variables (those start with a question mark `?`) in the graph pattern. So, in the graph pattern above there are two variables (note that both variables occur twice). A binding set consists of zero or more bindings and each binding represents a single mapping of some or all of the variables to a value. For example, when the graph pattern above is applied to the RDF data above, this results in the following bindingset (note that we use JSON here to express the bindingset): +A binding set provides a collection of 'solutions' to the graph pattern, i.e. combinations of values for the available variables (those start with a question mark `?`) in the graph pattern. +So, in the graph pattern above there are two variables (note that both variables occur twice). +A binding set consists of zero or more bindings and each binding represents a single mapping of some or all of the variables to a value. +For example, when the graph pattern above is applied to the RDF data above, this results in the following binding set (note that we use JSON here to express the binding set): ```json [ @@ -160,11 +175,12 @@ A bindingset provides a collection of 'solutions' to the graph pattern, i.e. com ] ``` -As you can see, the one-to-many relations in the RDF data is represented in the bindingset by having 3 bindings. Note that all bindings have the same value for the `parent` variable. +As you can see, the one-to-many relations in the RDF data is represented in the binding set by having 3 bindings. +Note that all bindings have the same value for the `parent` variable. -### Hierarchy example +### Hierarchy Example -Imagine your graph pattern looks something like this (note that we use a non existing ontology): +Imagine your graph pattern looks something like this (note that we use a non-existing ontology): ```sparql ?ts rdf:type ex:TimeSeries . @@ -192,7 +208,7 @@ Imagine you have JSON data of the form: } ``` -How would the bindingset look like that represents the JSON corresponding to the graph pattern? +How would the binding set look like that represents the JSON corresponding to the graph pattern? ```json [ From 0cd1064491661b9f31a52f8781c43bc0106d9c44 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Thu, 14 Nov 2024 16:32:23 +0100 Subject: [PATCH 46/64] fixed bug in reasonerProcessor about knowledge gap handling --- .../engine/smartconnector/impl/ReasonerProcessor.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java index 8bb75232..f5af07fe 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java @@ -181,11 +181,14 @@ public CompletableFuture executeAskInteraction(BindingSet someBinding this.finalBindingSetFuture = new CompletableFuture(); // this.reasonerPlan.optimize(); continueReasoningBackward(translateBindingSetTo(someBindings)); + LOG.info("In the executeAskInteraction of the ReasonerProcessor!"); return this.finalBindingSetFuture.thenApply((bs) -> { - if (bs.isEmpty() && myKnowledgeInteraction.getKnowledgeInteraction().knowledgeGapsEnabled()) { - this.knowledgeGaps = getKnowledgeGaps(this.reasonerPlan.getStartNode()); - } + if (myKnowledgeInteraction.getKnowledgeInteraction().knowledgeGapsEnabled()) { + this.knowledgeGaps = bs.isEmpty() + ? getKnowledgeGaps(this.reasonerPlan.getStartNode()) + : new HashSet(); + } return new AskResult(translateBindingSetFrom(bs), this.askExchangeInfos, this.reasonerPlan, this.knowledgeGaps); }); } From 8ce13508aa5fc4696765fdc91c933b9b0dc4553c Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Thu, 14 Nov 2024 17:02:28 +0100 Subject: [PATCH 47/64] When using knowledge gaps choose highest MatchStrategy. --- .../rest/api/impl/RestKnowledgeBase.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/RestKnowledgeBase.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/RestKnowledgeBase.java index fe141eb8..be97ce11 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/RestKnowledgeBase.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/RestKnowledgeBase.java @@ -47,6 +47,7 @@ import eu.knowledge.engine.smartconnector.api.KnowledgeBase; import eu.knowledge.engine.smartconnector.api.KnowledgeEngineException; import eu.knowledge.engine.smartconnector.api.KnowledgeInteraction; +import eu.knowledge.engine.smartconnector.api.MatchStrategy; import eu.knowledge.engine.smartconnector.api.PostKnowledgeInteraction; import eu.knowledge.engine.smartconnector.api.ReactExchangeInfo; import eu.knowledge.engine.smartconnector.api.ReactHandler; @@ -394,12 +395,12 @@ public String register(KnowledgeInteractionBase ki) { prefixMapping = new PrefixMappingZero(); } - boolean knowledgeGapsEnabled = ki.getKnowledgeGapsEnabled() == null ? false - : ki.getKnowledgeGapsEnabled(); + boolean knowledgeGapsEnabled = ki.getKnowledgeGapsEnabled() == null ? false : ki.getKnowledgeGapsEnabled(); if (knowledgeGapsEnabled && !this.sc.isReasonerEnabled()) { - throw new IllegalArgumentException("You can only set knowledgeGapsEnabled when the Knowledge Base is reasonerEnabled."); + throw new IllegalArgumentException( + "You can only set knowledgeGapsEnabled when the Knowledge Base is reasonerEnabled."); } - + String type = ki.getKnowledgeInteractionType(); URI kiId; if (type.equals("AskKnowledgeInteraction")) { @@ -409,8 +410,15 @@ public String register(KnowledgeInteractionBase ki) { if (aki.getGraphPattern() == null) { throw new IllegalArgumentException("graphPattern must be given for ASK knowledge interactions."); } + + MatchStrategy strategy = null; + if (aki.getKnowledgeGapsEnabled() != null && aki.getKnowledgeGapsEnabled()) { + strategy = MatchStrategy.SUPREME_LEVEL; + } + var askKI = new AskKnowledgeInteraction(ca, new GraphPattern(prefixMapping, aki.getGraphPattern()), - ki.getKnowledgeInteractionName(), knowledgeGapsEnabled); + ki.getKnowledgeInteractionName(), false, false, knowledgeGapsEnabled, strategy); + kiId = this.sc.register(askKI); this.knowledgeInteractions.put(kiId, askKI); } else if (type.equals("AnswerKnowledgeInteraction")) { From 3ed06ce0e61415e4407b7110981e2bf50f40ce8d Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Fri, 15 Nov 2024 12:13:11 +0100 Subject: [PATCH 48/64] Make sure ordering of knowledge gap triples does not matter in assert. --- .../TestAskAnswerReactWithGapsEnabled.java | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java index 7a57de4a..ef77a5b8 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerReactWithGapsEnabled.java @@ -1,13 +1,15 @@ package eu.knowledge.engine.rest.api; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.CountDownLatch; import org.junit.jupiter.api.AfterAll; @@ -23,6 +25,8 @@ import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; import jakarta.json.JsonReader; +import jakarta.json.JsonString; +import jakarta.json.JsonValue; @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class TestAskAnswerReactWithGapsEnabled { @@ -198,9 +202,30 @@ public void run() { "application/json", "Accept", "*/*")); var result = askKiWithoutGapsEnabled.getBody(); System.out.println("Result is:" + result); - assertTrue(result.contains( - "\"knowledgeGaps\":[[\"?a ?c\",\"?a ?b\"]]")); - assertTrue(result.contains("\"bindingSet\":[]")); + + JsonObject jsonObject = Json.createReader(new StringReader(result)).readObject(); + JsonArray knowledgeGapsArray = jsonObject.getJsonArray("knowledgeGaps"); + + // tried to convert it using streams, but failed. So, just using for old + // fashioned loops :( + Set> actualKnowledgeGaps = new HashSet>(); + Set set; + for (JsonValue jv : knowledgeGapsArray) { + set = new HashSet(); + for (JsonString js : jv.asJsonArray().getValuesAs(JsonString.class)) { + set.add(js.getString()); + } + actualKnowledgeGaps.add(set); + } + + var expectedKnowledgeGaps = Set + .of(Set.of("?a ?c", "?a ?b")); + assertEquals(expectedKnowledgeGaps, actualKnowledgeGaps); + + JsonArray bindingArray = jsonObject.getJsonArray("bindingSet"); + var actualBindingSet = Set.of(bindingArray.toArray()); + var expectedBindingSet = Set.>of( /* empty */ ); + assertEquals(expectedBindingSet, actualBindingSet); } From a391c4ccca94cf7feaa974ef0e841fe589645c3f Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Fri, 15 Nov 2024 12:14:13 +0100 Subject: [PATCH 49/64] Remove comment. --- .../java/eu/knowledge/engine/reasoner/api/TriplePattern.java | 1 - 1 file changed, 1 deletion(-) diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TriplePattern.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TriplePattern.java index 1801a312..ee66c117 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TriplePattern.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TriplePattern.java @@ -184,7 +184,6 @@ public boolean equals(Object obj) { public Triple asTriple() { return Triple.create(this.getSubject(), this.getPredicate(), this.getObject()); - //return null; } } From b5f8aed5ed38ce6e9e5826d9ac48a22e101c8d66 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Fri, 15 Nov 2024 21:38:25 +0100 Subject: [PATCH 50/64] commented out an assertion before fixing the RestAPI further --- .../knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java index 9a13d629..95a03b7f 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java @@ -61,7 +61,8 @@ public void testAskWithoutGaps() throws IOException { "https://www.tno.nl/example/relationAsker/interaction/askRelations", "Content-Type", "application/json", "Accept", "*/*")); var result = askKiWithoutGapsEnabled.getBody(); - assertFalse(result.contains("\"knowledgeGaps\":")); + System.out.println("Result is:" + result); + //assertFalse(result.contains("\"knowledgeGaps\":")); } @AfterAll From c9f0699e28a53460c0b11ac37a2de301b27ffc46 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sat, 16 Nov 2024 12:10:46 +0100 Subject: [PATCH 51/64] made knowledge gaps in AskResult of RestAPI really optional --- .../api/impl/ProactiveApiServiceImpl.java | 15 ++++++++---- .../src/main/resources/openapi-sc.yaml | 24 ++++++++++++++++++- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java index 671b7ea1..566d238f 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java @@ -21,6 +21,7 @@ import eu.knowledge.engine.rest.api.NotFoundException; import eu.knowledge.engine.rest.model.AskExchangeInfo; import eu.knowledge.engine.rest.model.AskResult; +import eu.knowledge.engine.rest.model.AskResultWithGaps; import eu.knowledge.engine.rest.model.KnowledgeInteractionWithId; import eu.knowledge.engine.rest.model.PostExchangeInfo; import eu.knowledge.engine.rest.model.PostResult; @@ -150,16 +151,20 @@ public void scAskPost( .collect(Collectors.toList()); LOG.debug("Bindings in result is {}", askResult.getBindings()); - AskResult ar = new AskResult().bindingSet(this.bindingSetToList(askResult.getBindings())).exchangeInfo(infos); - LOG.debug("KnowledgeGapsEnabled is {}", ki.getKnowledgeGapsEnabled()); - // add knowledge gaps if knowledgeGapsEnabled is true + + // distinguish between knowledge gaps enabled or not to produce an AskResult or an AskResultWithGaps + // this is UGLY!! if (ki.getKnowledgeGapsEnabled()) { + AskResultWithGaps ar = new AskResultWithGaps().bindingSet(this.bindingSetToList(askResult.getBindings())).exchangeInfo(infos); LOG.info("Knowledge gaps in result is {}", askResult.getKnowledgeGaps()); ar.knowledgeGaps(this.knowledgeGapsToList(askResult.getKnowledgeGaps())); + asyncResponse.resume(Response.status(Status.OK).entity(ar).build()); + } + else { + AskResult ar = new AskResult().bindingSet(this.bindingSetToList(askResult.getBindings())).exchangeInfo(infos); + asyncResponse.resume(Response.status(Status.OK).entity(ar).build()); } - - asyncResponse.resume(Response.status(Status.OK).entity(ar).build()); }); } catch (URISyntaxException | InterruptedException | ExecutionException e) { diff --git a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml index 7853dc19..fff99111 100644 --- a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml +++ b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml @@ -343,7 +343,19 @@ paths: content: application/json; charset=UTF-8: schema: - $ref: '#/components/schemas/AskResult' + oneOf: + - $ref: '#/components/schemas/AskResult' + - $ref: '#/components/schemas/AskResultWithGaps' + examples: + knowledge gaps NOT enabled: + value: + bindingSet: [{"a": "","b": ""}] + exchangeInfo: [] + knowledge gaps enabled: + value: + bindingSet: [{"a": "","b": ""}] + exchangeInfo: [] + knowledgeGaps: [["?a ?c", "?a ?b"]] '400': description: If the ask failed. content: @@ -700,6 +712,16 @@ components: AskResult: type: object required: [bindingSet, exchangeInfo] + properties: + bindingSet: + $ref: '#/components/schemas/BindingSet' + exchangeInfo: + type: array + items: + $ref: '#/components/schemas/AskExchangeInfo' + AskResultWithGaps: + type: object + required: [bindingSet, exchangeInfo, knowledgeGaps] properties: bindingSet: $ref: '#/components/schemas/BindingSet' From dd7cef79698d6164caaef38cf57674975ce897a2 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sat, 16 Nov 2024 12:11:49 +0100 Subject: [PATCH 52/64] made new unit test for RestAPI to cover kg-enabled with bindings --- .../TestAskAnswerWithGapsEnabledNoGaps.java | 137 ++++++++++++++++++ .../rest/api/TestAskWithGapsNotEnabled.java | 2 +- 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabledNoGaps.java diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabledNoGaps.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabledNoGaps.java new file mode 100644 index 00000000..8ef721fe --- /dev/null +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerWithGapsEnabledNoGaps.java @@ -0,0 +1,137 @@ +package eu.knowledge.engine.rest.api; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import eu.knowledge.engine.rest.RestServerHelper; +import eu.knowledge.engine.test_utils.AsyncTester; +import eu.knowledge.engine.test_utils.HttpTester; +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonReader; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestAskAnswerWithGapsEnabledNoGaps { + private final RestServerHelper rsh = new RestServerHelper(); + private static int PORT = 8280; + + @BeforeAll + public void setUpServer() { + rsh.start(PORT); + } + + @Test + public void testAskAnswerWithGaps() throws IOException, InterruptedException { + + // In this test there will be an Ask KB with a single AskKI and + // an AnswerKB with a single AnswerKI that answers only part of the Ask pattern. + // The test will execute the AskKI with knowledge gaps enabled. + // As a result, there are no knowledge gaps, so the set should be empty. + + CountDownLatch KBReady = new CountDownLatch(1); + + URL url = new URL("http://localhost:" + PORT + "/rest"); + + // activate the answer SC, KB, KI in a separate thread + var answeringSc = new AsyncTester(new Runnable() { + @Override + public void run() { + String answerKBId = "https://www.tno.nl/example/relationProvider"; + String answerKIId = answerKBId + "/interaction/answerRelations"; + try { + // register the AnswerKB + HttpTester registerAnswerKb = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"https://www.tno.nl/example/relationProvider\", \"knowledgeBaseName\": \"RelationProvider\", \"knowledgeBaseDescription\": \"A KB that provides relations between people\", \"reasonerEnabled\" : true}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerAnswerKb.expectStatus(200); + + // register the AnswerKI + HttpTester registerAnswerKi = new HttpTester(new URL(url + "/sc/ki"), "POST", + "{\"knowledgeInteractionType\": \"AnswerKnowledgeInteraction\", \"knowledgeInteractionName\": \"answerRelations\", \"graphPattern\": \"?a ?b .\"}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationProvider", "Content-Type", + "application/json", "Accept", "*/*")); + registerAnswerKi.expectStatus(200); + + KBReady.countDown(); + // get the handle for the answerKB to see if there are requests to be handled + var test = new HttpTester(new URL(url.toString() + "/sc/handle"), "GET", null, Map + .of("Knowledge-Base-Id", answerKBId, "Content-Type", "application/json", "Accept", "*/*")); + test.expectStatus(200); + + // build the body to answer the request: add handle request ID and dummy data + // bindingset + JsonObjectBuilder builder = Json.createObjectBuilder(); + JsonReader jp = Json.createReader(new StringReader(test.getBody())); + JsonObject jo = jp.readObject(); + int handleRequestId = jo.getInt("handleRequestId"); + builder.add("handleRequestId", handleRequestId); + JsonReader jr = Json.createReader(new StringReader( + "[{\"a\": \"\",\"b\": \"\"}]")); + JsonArray bs = jr.readArray(); + builder.add("bindingSet", bs); + JsonObject jo2 = builder.build(); + String body = jo2.toString(); + System.out.println("Handle an answer to a request with body: " + body); + + // fire the POST handle to execute the answer + var test2 = new HttpTester(new URL(url.toString() + "/sc/handle"), "POST", body, + Map.of("Knowledge-Base-Id", answerKBId, "Knowledge-Interaction-Id", answerKIId, + "Content-Type", "application/json", "Accept", "*/*")); + test2.expectStatus(200); + + } catch (MalformedURLException e) { + fail(); + } + } + }); + answeringSc.start(); + + KBReady.await(); + // register the AskKB + HttpTester registerKb = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"https://www.tno.nl/example/relationAsker\", \"knowledgeBaseName\": \"RelationAsker\", \"knowledgeBaseDescription\": \"A KB that asks for relations between people\", \"reasonerEnabled\" : true}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerKb.expectStatus(200); + + + // register the AskKI + HttpTester registerKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ki"), "POST", + "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", \"knowledgeInteractionName\": \"askRelations\", \"graphPattern\": \"?a ?b . \", \"knowledgeGapsEnabled\": true}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Content-Type", + "application/json", "Accept", "*/*")); + registerKiWithoutGapsEnabled.expectStatus(200); + + // fire the ask KI + HttpTester askKiWithoutGapsEnabled = new HttpTester(new URL(url + "/sc/ask"), "POST", + "{\"recipientSelector\": {\"knowledgeBases\": []}, \"bindingSet\": []}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/relationAsker", "Knowledge-Interaction-Id", + "https://www.tno.nl/example/relationAsker/interaction/askRelations", "Content-Type", + "application/json", "Accept", "*/*")); + var result = askKiWithoutGapsEnabled.getBody(); + System.out.println("Result is:" + result); + assertTrue(result.contains("\"knowledgeGaps\":[]")); + + } + + @AfterAll + public void cleanUp() throws MalformedURLException { + + TestUtil.unregisterAllKBs("http://localhost:" + PORT + "/rest"); + rsh.cleanUp(); + } + +} diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java index 95a03b7f..8f4a29c0 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java @@ -62,7 +62,7 @@ public void testAskWithoutGaps() throws IOException { "application/json", "Accept", "*/*")); var result = askKiWithoutGapsEnabled.getBody(); System.out.println("Result is:" + result); - //assertFalse(result.contains("\"knowledgeGaps\":")); + assertFalse(result.contains("\"knowledgeGaps\":")); } @AfterAll From d900b4bc19f258190be80004be8c3da6b6b90d33 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Sat, 16 Nov 2024 13:49:04 +0100 Subject: [PATCH 53/64] added system.out statement for result --- .../eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java index 87b5348f..ddf1b83c 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java @@ -61,7 +61,7 @@ public void testAskWithGaps() throws IOException { "https://www.tno.nl/example/relationAsker/interaction/askRelations", "Content-Type", "application/json", "Accept", "*/*")); var result = askKiWithoutGapsEnabled.getBody(); - System.out.println(result); + System.out.println("Result is:" + result); assertTrue(result.contains("\"knowledgeGaps\":[[\"?a ?b\"]]")); } From 4058a74bc994f13c3e25682b4335f9489f7ad592 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Mon, 18 Nov 2024 15:51:16 +0100 Subject: [PATCH 54/64] used nullable in openapi spec to distinguish between kg enabled --- .../rest/api/impl/ProactiveApiServiceImpl.java | 9 ++------- .../src/main/resources/openapi-sc.yaml | 16 +++------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java index 566d238f..b4b06282 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java @@ -153,18 +153,13 @@ public void scAskPost( LOG.debug("Bindings in result is {}", askResult.getBindings()); LOG.debug("KnowledgeGapsEnabled is {}", ki.getKnowledgeGapsEnabled()); + AskResult ar = new AskResult().bindingSet(this.bindingSetToList(askResult.getBindings())).exchangeInfo(infos); // distinguish between knowledge gaps enabled or not to produce an AskResult or an AskResultWithGaps - // this is UGLY!! if (ki.getKnowledgeGapsEnabled()) { - AskResultWithGaps ar = new AskResultWithGaps().bindingSet(this.bindingSetToList(askResult.getBindings())).exchangeInfo(infos); LOG.info("Knowledge gaps in result is {}", askResult.getKnowledgeGaps()); ar.knowledgeGaps(this.knowledgeGapsToList(askResult.getKnowledgeGaps())); - asyncResponse.resume(Response.status(Status.OK).entity(ar).build()); - } - else { - AskResult ar = new AskResult().bindingSet(this.bindingSetToList(askResult.getBindings())).exchangeInfo(infos); - asyncResponse.resume(Response.status(Status.OK).entity(ar).build()); } + asyncResponse.resume(Response.status(Status.OK).entity(ar).build()); }); } catch (URISyntaxException | InterruptedException | ExecutionException e) { diff --git a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml index fff99111..c9b8ab02 100644 --- a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml +++ b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml @@ -340,12 +340,11 @@ paths: bindings together with additional exchange info. The returned bindings form a set and there is no quaranteed ordering. The exchange info shows the other Knowledge Bases that contributed to the answer with timing, initiator information. + If KnowledgeGaps are enabled, a list of knowledge gaps that are found will be returned as well. content: application/json; charset=UTF-8: schema: - oneOf: - - $ref: '#/components/schemas/AskResult' - - $ref: '#/components/schemas/AskResultWithGaps' + $ref: '#/components/schemas/AskResult' examples: knowledge gaps NOT enabled: value: @@ -712,16 +711,6 @@ components: AskResult: type: object required: [bindingSet, exchangeInfo] - properties: - bindingSet: - $ref: '#/components/schemas/BindingSet' - exchangeInfo: - type: array - items: - $ref: '#/components/schemas/AskExchangeInfo' - AskResultWithGaps: - type: object - required: [bindingSet, exchangeInfo, knowledgeGaps] properties: bindingSet: $ref: '#/components/schemas/BindingSet' @@ -733,6 +722,7 @@ components: type: array items: $ref: '#/components/schemas/KnowledgeGap' + nullable: true PostResult: type: object required: [resultBindingSet, exchangeInfo] From 329df535f5ebdd8ae75297edc945edd7dea7b5cd Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Mon, 18 Nov 2024 17:06:21 +0100 Subject: [PATCH 55/64] deleted AskResultWithGaps from import as the build on github failed --- .../knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java index b4b06282..0d00c2ad 100644 --- a/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java +++ b/smart-connector-rest-server/src/main/java/eu/knowledge/engine/rest/api/impl/ProactiveApiServiceImpl.java @@ -21,7 +21,6 @@ import eu.knowledge.engine.rest.api.NotFoundException; import eu.knowledge.engine.rest.model.AskExchangeInfo; import eu.knowledge.engine.rest.model.AskResult; -import eu.knowledge.engine.rest.model.AskResultWithGaps; import eu.knowledge.engine.rest.model.KnowledgeInteractionWithId; import eu.knowledge.engine.rest.model.PostExchangeInfo; import eu.knowledge.engine.rest.model.PostResult; From bd0bd1b06790e7991e89da4ac6673dc7061aaa69 Mon Sep 17 00:00:00 2001 From: Jack Verhoosel Date: Wed, 20 Nov 2024 15:27:10 +0100 Subject: [PATCH 56/64] made some final adjustments to the openapi spec for gaps --- .../src/main/resources/openapi-sc.yaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml index c9b8ab02..60663eb9 100644 --- a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml +++ b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml @@ -228,13 +228,6 @@ paths: knowledgeInteractionType: AskKnowledgeInteraction graphPattern: "?a ?b ." knowledgeGapsEnabled: "true" - description: - The knowledge gap enabled option is disabled by default. If enabled, - each activation of the knowledge interaction will provide a set of - knowledge gaps as part of the result. If this set is empty, the - binding set of the result contains the answer. If not empty, - one or more of the knowledge gaps need to be fixed for the - knowledge interaction to produce an answer. responses: '200': description: If the Knowledge Interaction is successfully registered, it returns the KnowledgeInteractionId object. @@ -340,7 +333,8 @@ paths: bindings together with additional exchange info. The returned bindings form a set and there is no quaranteed ordering. The exchange info shows the other Knowledge Bases that contributed to the answer with timing, initiator information. - If KnowledgeGaps are enabled, a list of knowledge gaps that are found will be returned as well. + If KnowledgeGaps are enabled, a list of knowledge gaps that are found will be returned + as well. See, the different examples below. content: application/json; charset=UTF-8: schema: From c59b7d5a9e3b9532325d1a94083b04888ef62331 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:57:21 +0100 Subject: [PATCH 57/64] Bump jetty-version from 11.0.15 to 11.0.24 (#565) Bumps `jetty-version` from 11.0.15 to 11.0.24. Updates `org.eclipse.jetty:jetty-util` from 11.0.15 to 11.0.24 Updates `org.eclipse.jetty:jetty-servlet` from 11.0.15 to 11.0.24 Updates `org.eclipse.jetty:jetty-server` from 11.0.15 to 11.0.24 --- updated-dependencies: - dependency-name: org.eclipse.jetty:jetty-util dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-servlet dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-server dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- admin-ui/pom.xml | 2 +- knowledge-directory/pom.xml | 2 +- smart-connector-rest-server/pom.xml | 2 +- smart-connector/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/admin-ui/pom.xml b/admin-ui/pom.xml index dc36f745..5be156a5 100644 --- a/admin-ui/pom.xml +++ b/admin-ui/pom.xml @@ -16,7 +16,7 @@ 2.2.22 - 11.0.15 + 11.0.24 3.1.9 2.18.1 UTF-8 diff --git a/knowledge-directory/pom.xml b/knowledge-directory/pom.xml index 4f72a41c..f448a247 100644 --- a/knowledge-directory/pom.xml +++ b/knowledge-directory/pom.xml @@ -16,7 +16,7 @@ 2.2.22 3.1.0 - 11.0.15 + 11.0.24 3.1.9 2.18.1 4.13.1 diff --git a/smart-connector-rest-server/pom.xml b/smart-connector-rest-server/pom.xml index 567c84d1..34b81c22 100644 --- a/smart-connector-rest-server/pom.xml +++ b/smart-connector-rest-server/pom.xml @@ -12,7 +12,7 @@ 2.2.22 - 11.0.15 + 11.0.24 3.1.9 2.18.1 UTF-8 diff --git a/smart-connector/pom.xml b/smart-connector/pom.xml index ad9af2e0..375f93da 100644 --- a/smart-connector/pom.xml +++ b/smart-connector/pom.xml @@ -193,7 +193,7 @@ 2.2.22 - 11.0.15 + 11.0.24 3.1.9 2.18.1 6.1.0 From 215a0d376705d91170fb2f633a7760f8fab1931c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Nov 2024 13:35:08 +0100 Subject: [PATCH 58/64] Bump org.apache.maven:maven-model from 3.9.4 to 3.9.9 (#566) Bumps org.apache.maven:maven-model from 3.9.4 to 3.9.9. --- updated-dependencies: - dependency-name: org.apache.maven:maven-model dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- smart-connector-rest-server/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smart-connector-rest-server/pom.xml b/smart-connector-rest-server/pom.xml index 34b81c22..9c5084ce 100644 --- a/smart-connector-rest-server/pom.xml +++ b/smart-connector-rest-server/pom.xml @@ -140,7 +140,7 @@ org.apache.maven maven-model - 3.9.4 + 3.9.9 From d0e58a66adf5c34291774c18ed62a7bf23f6bbb2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:42:33 +0100 Subject: [PATCH 59/64] Bump ch.qos.logback:logback-core from 1.4.14 to 1.5.12 (#568) * Bump ch.qos.logback:logback-core from 1.4.14 to 1.5.12 Bumps [ch.qos.logback:logback-core](https://github.com/qos-ch/logback) from 1.4.14 to 1.5.12. - [Commits](https://github.com/qos-ch/logback/compare/v_1.4.14...v_1.5.12) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Remove logback dependency --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: lathouwerssam --- knowledge-directory/pom.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/knowledge-directory/pom.xml b/knowledge-directory/pom.xml index f448a247..55406f9c 100644 --- a/knowledge-directory/pom.xml +++ b/knowledge-directory/pom.xml @@ -20,7 +20,6 @@ 3.1.9 2.18.1 4.13.1 - 1.4.14 UTF-8 @@ -36,12 +35,6 @@ swagger-jaxrs2-servlet-initializer-v2-jakarta ${swagger-core-version} - - ch.qos.logback - logback-core - ${logback-version} - compile - org.junit.jupiter junit-jupiter-api From e13eebf91e3ef15ad08389bac012b0e0651f5e33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:41:37 +0100 Subject: [PATCH 60/64] Bump jackson-version from 2.18.1 to 2.18.2 (#570) Bumps `jackson-version` from 2.18.1 to 2.18.2. Updates `com.fasterxml.jackson.datatype:jackson-datatype-joda` from 2.18.1 to 2.18.2 - [Commits](https://github.com/FasterXML/jackson-datatype-joda/compare/jackson-datatype-joda-2.18.1...jackson-datatype-joda-2.18.2) Updates `com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider` from 2.18.1 to 2.18.2 Updates `com.fasterxml.jackson.core:jackson-core` from 2.18.1 to 2.18.2 - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.18.1...jackson-core-2.18.2) Updates `com.fasterxml.jackson.core:jackson-annotations` from 2.18.1 to 2.18.2 - [Commits](https://github.com/FasterXML/jackson/commits) Updates `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.18.1 to 2.18.2 Updates `com.fasterxml.jackson.core:jackson-databind` from 2.18.1 to 2.18.2 - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-joda dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: com.fasterxml.jackson.core:jackson-annotations dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-jsr310 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- admin-ui/pom.xml | 2 +- knowledge-directory/pom.xml | 2 +- smart-connector-rest-server/pom.xml | 2 +- smart-connector/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/admin-ui/pom.xml b/admin-ui/pom.xml index 5be156a5..4b880e8b 100644 --- a/admin-ui/pom.xml +++ b/admin-ui/pom.xml @@ -18,7 +18,7 @@ 2.2.22 11.0.24 3.1.9 - 2.18.1 + 2.18.2 UTF-8 diff --git a/knowledge-directory/pom.xml b/knowledge-directory/pom.xml index 55406f9c..50a168c7 100644 --- a/knowledge-directory/pom.xml +++ b/knowledge-directory/pom.xml @@ -18,7 +18,7 @@ 3.1.0 11.0.24 3.1.9 - 2.18.1 + 2.18.2 4.13.1 UTF-8 diff --git a/smart-connector-rest-server/pom.xml b/smart-connector-rest-server/pom.xml index 9c5084ce..409a2fac 100644 --- a/smart-connector-rest-server/pom.xml +++ b/smart-connector-rest-server/pom.xml @@ -14,7 +14,7 @@ 2.2.22 11.0.24 3.1.9 - 2.18.1 + 2.18.2 UTF-8 diff --git a/smart-connector/pom.xml b/smart-connector/pom.xml index 375f93da..24cbe3f5 100644 --- a/smart-connector/pom.xml +++ b/smart-connector/pom.xml @@ -195,7 +195,7 @@ 2.2.22 11.0.24 3.1.9 - 2.18.1 + 2.18.2 6.1.0 From 07cf05dc816af222c3e02cd468fffd4a0b7e0c78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:58:43 +0100 Subject: [PATCH 61/64] Bump swagger-core-version from 2.2.22 to 2.2.26 (#572) Bumps `swagger-core-version` from 2.2.22 to 2.2.26. Updates `io.swagger.core.v3:swagger-jaxrs2-jakarta` from 2.2.22 to 2.2.26 Updates `io.swagger.core.v3:swagger-jaxrs2-servlet-initializer-v2-jakarta` from 2.2.22 to 2.2.26 --- updated-dependencies: - dependency-name: io.swagger.core.v3:swagger-jaxrs2-jakarta dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.swagger.core.v3:swagger-jaxrs2-servlet-initializer-v2-jakarta dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- admin-ui/pom.xml | 2 +- knowledge-directory/pom.xml | 2 +- smart-connector-rest-server/pom.xml | 2 +- smart-connector/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/admin-ui/pom.xml b/admin-ui/pom.xml index 4b880e8b..c6fd43b2 100644 --- a/admin-ui/pom.xml +++ b/admin-ui/pom.xml @@ -15,7 +15,7 @@ A user interface for managing a Knowledge Network. - 2.2.22 + 2.2.26 11.0.24 3.1.9 2.18.2 diff --git a/knowledge-directory/pom.xml b/knowledge-directory/pom.xml index 50a168c7..3fcdc395 100644 --- a/knowledge-directory/pom.xml +++ b/knowledge-directory/pom.xml @@ -14,7 +14,7 @@ - 2.2.22 + 2.2.26 3.1.0 11.0.24 3.1.9 diff --git a/smart-connector-rest-server/pom.xml b/smart-connector-rest-server/pom.xml index 409a2fac..b1053fa1 100644 --- a/smart-connector-rest-server/pom.xml +++ b/smart-connector-rest-server/pom.xml @@ -11,7 +11,7 @@ - 2.2.22 + 2.2.26 11.0.24 3.1.9 2.18.2 diff --git a/smart-connector/pom.xml b/smart-connector/pom.xml index 24cbe3f5..a41dd594 100644 --- a/smart-connector/pom.xml +++ b/smart-connector/pom.xml @@ -192,7 +192,7 @@ - 2.2.22 + 2.2.26 11.0.24 3.1.9 2.18.2 From f43b4618d607e5d7220f781c2f418bae1dc037aa Mon Sep 17 00:00:00 2001 From: bnouwt <97681626+bnouwt@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:30:16 +0100 Subject: [PATCH 62/64] Update docker-compose.yml --- examples/rest-api/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rest-api/docker-compose.yml b/examples/rest-api/docker-compose.yml index 20783f8f..a21d49db 100644 --- a/examples/rest-api/docker-compose.yml +++ b/examples/rest-api/docker-compose.yml @@ -2,7 +2,7 @@ services: knowledge-engine: image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 healthcheck: - test: curl -f http://localhost:8280/rest/sc + test: wget http://localhost:8280/rest/sc -O /dev/null interval: 1s sensor: build: From dcf4fc3f958f50ae56da2134fa247cd91200bf34 Mon Sep 17 00:00:00 2001 From: Sophie Lathouwers Date: Mon, 9 Dec 2024 09:51:35 +0100 Subject: [PATCH 63/64] Improve documentation (#575) * Rename docs-pages to align URLs with page titles * Restructure/move final questions in FAQ --- README.md | 2 +- ...tke-errors.md => common_error_messages.md} | 0 docs/docs/faq.md | 156 +++--------------- docs/docs/get-started/graph-patterns.md | 136 +++++++++++++++ ...va_developer_api.md => getting_started.md} | 0 docs/static/img/graph-pattern-pitfall.drawio | 83 ++++++++++ docs/static/img/graph-pattern-pitfall.png | Bin 0 -> 13681 bytes 7 files changed, 242 insertions(+), 135 deletions(-) rename docs/docs/{tke-errors.md => common_error_messages.md} (100%) create mode 100644 docs/docs/get-started/graph-patterns.md rename docs/docs/{java_developer_api.md => getting_started.md} (100%) create mode 100644 docs/static/img/graph-pattern-pitfall.drawio create mode 100644 docs/static/img/graph-pattern-pitfall.png diff --git a/README.md b/README.md index 7b2d1ce4..3c68f368 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ This section gives more detailed information about the project's structure, and The Knowledge Engine project consists of the following Maven modules: - `smart-connector` - - This is the implementation of the smart connector, with the Java developer API. For instructions on how to use it, refer to [the documentation](./docs/docs/java_developer_api.md). + - This is the implementation of the smart connector, with the Java developer API. For instructions on how to use it, refer to [the documentation](./docs/docs/getting_started.md). - `smart-connector-api` - This module contains interfaces for the smart connector and other classes. It is made as a separate module so that it is easy to use different implementations of the interfaces. - `smart-connector-rest-server` diff --git a/docs/docs/tke-errors.md b/docs/docs/common_error_messages.md similarity index 100% rename from docs/docs/tke-errors.md rename to docs/docs/common_error_messages.md diff --git a/docs/docs/faq.md b/docs/docs/faq.md index 90e35fc4..b786ca13 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -247,137 +247,25 @@ If the current setup, a Smart Connector with a single long polling connection, i 2) Divide Knowledge Interactions over multiple Smart Connectors. This allows you to have a single long polling connection per Knowledge Interaction. - -*Question*: I have a question about how the compliance checker and reasoner are supposed to work. **If this is the wrong place for asking this, please direct me to the right people!** - -An example: the following graph pattern defines a **device** and the **type** of it’s **property**. - -```sparql -?device ?property . -?property a ?propertyType . -``` - -The only parameters we need here are **device** and **propertyType**. The **property** variable is redundant, but it still needs to be in the graph pattern. What we decided to do, is replace this variable by a placeholder individual : `http://interconnectproject.eu/pilots/greek/property#property`. The resulting pattern would then look like this: - -```sparql -?device . - a ?propertyType . -``` - -I’m wondering if referring to individuals that are not in any ontology, in **subjects** and **objects**, will cause problems with compliance or reasoning? - -I guess my question is more specifically: “Will the compliance checker look at **subjects** and **objects** in graph patterns, or only at predicates? And will the reasoner be able to handle these kinds of structures?” - -- *Answer*: I think it helps if we distinguish between syntax and semantics here. Using a pattern like: - - ```sparql - ?device . - a ?propertyType . - ``` - - is syntactically correct and it should be accepted by both the validator and reasoner. The difficulty here is with the semantics that it expresses. Imagine the following bindingset: - - | ?device | ?propertyType | - |-----------|---------------| - | \ | saref:Motion | - | \ | saref:Smoke | - | \ | saref:Energy | - - If we combine the above Graph Pattern with the above BindingSet, we get the following RDF triples (I am a bit inconsistent with the prefixes): - - 1. ` .` - 2. ` a saref:Motion .` - - 3. ` .` - 4. ` a saref:Smoke .` - - 5. ` .` - 6. ` a saref:Energy .` - - Now, if we draw these triples as a graph figure, we get something like the following: - [Image no longer available] - - Note: - - that there are 6 triples in the above graph pattern, but only 5 edges in this figure. This is caused by the fact that the 1st and 3rd triple of the graph pattern above are exactly the same and triples can only occur once in a graph. So, if you write them twice, they will still only occur once. - - the effect that using a fixed individual greek:property in the graph pattern is causing; it makes it semantically impossible to determine that sensor1 is measuring property Motion and Smoke, while sensor2 is measuring Energy! - - Conclusion: while the fixed property is syntactically correct, it is semantically incorrect. So, using a fixed property in the current version of the KE (with a matcher instead of a reasoner) will probably work fine and the data is exchanged as expected. This is, however, probably not the case with the future version of the KE with reasoner, because the reasoner will actually semantically interpret the graph pattern and bindingset and when you ask something like: - - ```sparql - ?device . - a saref:Energy . - ``` - - (note that ?propertyType has been substituted with saref:Energy) - - This graph pattern represents the query: “give me all devices that measure energy” and it will answer with the following bindingset: - - | ?device | - |-----------| - | \ | - | \ | - - Which is not correct and is caused by the fixed property. So, there are two solutions here. The practical one, which I think happens quite a lot, is for the ontology to provide an individual per property. So, you would have a `interconnect:motionIndividual`, `interconnect:energyIndividual` and `interconnect:smokeIndividual`. These can be used instead of the greek:property and will make sure that it remains semantically correct. A less practical (and philosophically debatable) one is to have a unique property individual for every property a device measures. So, you would get something like ``, `` and `` and even more for all other devices. - - And last but not least, a short reaction to Georg’s remark: “I think it makes sense to think about the GP as the body of a query”. It certainly does, although within the context of the Knowledge Engine graph patterns are also used for non-query like interactions. - -- *Question*: In the context of graph pattern matching, can you explain the subset/superset condition: "when the Graph Pattern" of the sender is a superset of the Graph Pattern of the receiver' ? Is it at definition level or at data level? I found (at ontology level) the following explanation: "Ontology O1 is a subset of ontology O2 if all definitions in O1 are contained in O2 (O2 is the superset of O1)". -1) More concrete: is 'a b c .' graph pattern a subset or a superset of 'a b c . d e f.' ? -2) In case of Post/React knowledge interactions, both argument graph patterns must match and also both result graph patterns must match? -3) In case of Post/React knowledge interactions graph pattern matching, is the POST side regarded as the sender for the argument graph pattern and the REACT side as the sender for the result graph pattern? -4) Let's assume the REACT side result pattern is like 'a b c . d e f.' and the POST side result pattern is 'a b c .'. Is this allowed? So it is similar to a 'SELECT' in SQL? The result binding set at the POST side is then also reduced, I assume (not in number of _records_, but _fields_). -- *Answer*: We do not have defined the subset/superset terms within the context of the Knowledge Engine and Graph Patterns, but it would indeed be helpful to do so. Since the idea of the Knowledge Engine is that the *data* is always kept at the source and is only retrieved when necessary for an interaction, it is more suitable to talk about subset/superset at the definition level and not at the data level. This is because all data is simply not available in a single location. The definition level is also the level where currently the *matching* between graph patterns happens. The *matcher* (as opposed to the *reasoner*) does not support subset/superset matching. - - 1) I would say graph pattern `a b c` is a subset of the graph pattern `a b c . d e f`. Note that graph pattern typically contain variables like `?a`. Note that graph pattern matching ignores variable names and triple order. - 2) Yes, the argument graph pattern and result graph pattern should both match if two Post/React Knowledge Interactions want to exchange data. Note that this probably changes when the Knowledge Engine uses a reasoner instead of a matcher. - 3) Yes, the PostKnowledgeInteraction sends the argument and the ReactKnowledgeInteraction sends the (optional) result. - 4) Currently, this will not work, because we are using a graph pattern *matcher* instead of a *reasoner*. I expect the reasoner to indeed allow them to interact if the POST side result pattern is a subset of the REACT side result pattern. In that case the result binding set at the POST side should also be a subset (in fields) of the binding set given from the REACT side. So, the results are always given to a Knowledge Base in its own terminology, this already happens by translating the variable names, but should also happen in the way you describe once the reasoner is active. - -*Question*: I’m trying to understand how timeseries as part of a larger graph pattern are expressed in a binding set. - -For instance: -Let's say we have some graph pattern like: - -```sparql -?timeseries rdf:type ex:Timeseries . -?timeseries ex:hasMeasurement ?measurement . -?measurement rdf:type saref:Measurement . -?measurement saref:hasFeatureOfInterest ?room . -?room rdf:type saref:Room . -?measurement saref:observedProperty saref:Temperature . -?measurement saref:hasSimpleResult ?temperature . -?measurement ex:hasTimestamp ?ts . -``` - -And the timeseries returns an array of temperature values and timestamp for each value. - -In the ANSWER Knowledge interaction will the binding set be something like: -```json -[ - { - "timeseries": "", - "measurement": "", - "room": "", - "temperature": "\"21.2\"^^", - "ts": "\"2020-10-16T22:00Z\"^^some_timestamp_type" - }, - { - "timeseries": "", - "measurement": "", - "room": "", - "temperature": "\"21.4\"^^", - "ts": "\"2020-10-16T23:00Z\"^^some_timestamp_type" - }, - { - "timeseries": "", - "measurement": "", - "room": "", - "temperature": "\"21.6\"^^", - "ts": "\"2020-10-16T24:00Z\"^^some_timestamp_type" - } -] -``` - -Is the following statement right: the IRI is filled in by the service specific adapter? -Can this IRI then be used to for example ask more info about `` ? That would mean that the service specific adapter should use an ID that is stored, and not some temporal/volatile ID for the IRI. Because that means that you can give a reference to an object in the answers. Of course you have to provide then a graph pattern to allow the retrieval of this object resulting in another binding set. -- *Answer*: You are correct with respect to ``. Ideally you should be able to retrieve more information about it and the service should not randomly generate it, but use a stored id. +### How does the Knowledge Engine deal with subsets/supersets in graph patterns? +We have not defined the subset/superset terms within the context of the Knowledge Engine but this would indeed be helpful. +For ontologies, the subset/superset definition is sometimes explained as follows: +"Ontology O1 is a subset of ontology O2 if all definitions in O1 are contained in O2 (O2 is the superset of O1)". + +Since the idea of the Knowledge Engine is that the *data* is always kept at the source and is only retrieved when necessary for an interaction, it is more suitable to talk about subset/superset at the definition level and not at the data level. +This is because all data is simply not available in a single location. +The definition level is also the level where currently the *matching* between graph patterns happens. +The *matcher* (as opposed to the *reasoner*) does not support subset/superset matching. + +We can consider the following concrete questions when dealing with subset/superset: +1. Is `a b c.` a subset or superset of `a b c . d e f .`? + - I would say graph pattern `a b c` is a subset of the graph pattern `a b c . d e f`. + Note that graph pattern typically contain variables like `?a`. + Graph pattern _matching_ ignores variable names and triple order. +2. When using POST/REACT, do argument and result graph patterns *both* need to match or can they be a subset/superset? + - Yes, the argument graph pattern and result graph pattern should both match if two POST/REACT interactions want to exchange data. + This may change when you use the reasoner instead of the matcher. +3. Would the following interactions match? A REACT with result graph pattern `a b c. d e f.` and a POST with result graph pattern `a b c.` + - This will not match if you are using the graph pattern _matcher_ instead of a _reasoner_. + The reasoner would allow them to interact if the POST result pattern is a subset of the REACT result pattern. + In that case, the result binding set at the POST side should also be a subset (in fields) of the binding set given by the REACT side. diff --git a/docs/docs/get-started/graph-patterns.md b/docs/docs/get-started/graph-patterns.md new file mode 100644 index 00000000..b40c06d0 --- /dev/null +++ b/docs/docs/get-started/graph-patterns.md @@ -0,0 +1,136 @@ +--- +sidebar_position: 8 +--- +# Writing Graph Patterns +This page discusses some good practices for writing graph patterns. + +## How to handle redundant variables if they are required in a graph pattern +:::tip +This is a common pitfall, so we recommend having a look at this section! +::: + +Let's take the following graph pattern as an example, which defines a device which measures a property, and the type of that property. + +```sparql +?device ?property . +?property a ?propertyType . +``` + +This graph pattern contains three variables: `device`, `property` and `propertyType`. +What should you do when you are only interested in `device` and `propertyType`, but not in `property`? + +This case often occurs and a common pitfall is that people replace the `property` variable by a placeholder individual, +e.g. : `http://example.org/property#property`. +The resulting pattern would then look like this: + +```sparql +?device . + a ?propertyType . +``` + +However, this causes a problem. +Let's distinguish between syntax and semantics here. +The graph pattern above is syntactically correct, and is accepted by the Knowledge Engine. +However, there is some difficulty with the semantics that it expresses. + +Imagine the following binding set: + +```json +{ + "device": "", + "propertyType": "saref:Motion" +}, +{ + "device": "", + "propertyType": "saref:Smoke" +}, +{ + "device": "", + "propertyType": "saref:Energy" +} +``` + +If we combine the above Graph Pattern with the above BindingSet, we get the following RDF triples: + +1. ` .` +2. ` a saref:Motion .` +3. ` .` +4. ` a saref:Smoke .` +5. ` .` +6. ` a saref:Energy .` + +Now, if we draw these triples as a graph, we get something like: + +![Illustration of aforementioned RDF triples. It contains 2 nodes on the left, both connected to a central node, which is then connected to three nodes on the right.](./../../static/img/graph-pattern-pitfall.png) + +While we had 6 RDF triples, we only see 5 edges in this figure! +This is caused by the fact that the 1st and 3rd triple of the graph pattern above are exactly the same and triples can only occur once in a graph. +So, if you write them twice, they will still only occur once. +So, by using a fixed individual for `property` in the graph pattern, it becomes semantically impossible to determine that `sensor1` is measuring the property Motion and Smoke, while sensor2 is measuring Energy! + +Therefore, while using a fixed property is syntactically correct, it is semantically incorrect. +Using a fixed property in the current version of the KE (with a matcher instead of a reasoner) will probably work fine and the data is exchanged as expected. +This is, however, probably not the case with the future version of the KE with reasoner, because the reasoner will actually semantically interpret the graph pattern and binding set and when you ask something like: + + ```sparql + ?device . + a saref:Energy . + ``` + This graph pattern represents the query: “give me all devices that measure energy” and it will answer with the following bindingset: + +```json +{ + "device": "" +}, +{ + "device": "" +} +``` + +Which is not correct and is caused by the fixed property. +So, there are two solutions here. +1. The practical one, which I think happens quite a lot, is for the ontology to provide an individual per property. +So, you would have a `ex:motionIndividual`, `ex:energyIndividual` and `ex:smokeIndividual`. +These can be used instead of the ex:property and will make sure that it remains semantically correct. +2. A less practical (and philosophically debatable) one is to have a unique property individual for every property a device measures. +So, you would get something like ``, `` and `` and even more for all other devices. + +## Can you use volatile IRIs or should you use static or stored reference IRIs? +Let's say we have some graph pattern like: + +```sparql +?timeseries rdf:type ex:Timeseries . +?timeseries ex:hasMeasurement ?measurement . +?measurement rdf:type saref:Measurement . +?measurement saref:hasFeatureOfInterest ?room . +?room rdf:type saref:Room . +?measurement saref:observedProperty saref:Temperature . +?measurement saref:hasSimpleResult ?temperature . +?measurement ex:hasTimestamp ?ts . +``` + +And the timeseries returns an array of temperature values and timestamp for each value. +The binding set will be something like: +```json +[ + { + "timeseries": "", + "measurement": "", + "room": "", + "temperature": "\"21.2\"^^", + "ts": "\"2020-10-16T22:00Z\"^^some_timestamp_type" + }, + { + "timeseries": "", + "measurement": "", + "room": "", + "temperature": "\"21.4\"^^", + "ts": "\"2020-10-16T23:00Z\"^^some_timestamp_type" + } +] +``` + +If you use a temporal or volatile IRI for the measurement, that would mean that you cannot retrieve more details for the object later (assuming an appropriate interaction exists). +It is therefore recommended to use a stored or static IRI. +In the case of measurements or observations, you could also choose an IRI like so: ``, where you add the timestamp (e.g. in unix format) and a reference to the sensor doing the measurement. +This makes it easy to later reconstruct the IRI when you want to request more info. diff --git a/docs/docs/java_developer_api.md b/docs/docs/getting_started.md similarity index 100% rename from docs/docs/java_developer_api.md rename to docs/docs/getting_started.md diff --git a/docs/static/img/graph-pattern-pitfall.drawio b/docs/static/img/graph-pattern-pitfall.drawio new file mode 100644 index 00000000..6490fbba --- /dev/null +++ b/docs/static/img/graph-pattern-pitfall.drawio @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static/img/graph-pattern-pitfall.png b/docs/static/img/graph-pattern-pitfall.png new file mode 100644 index 0000000000000000000000000000000000000000..71d79a66c55d31e1dc6305171d995483810d209c GIT binary patch literal 13681 zcmch8bzGF&_b#9a(g+fQq>=(N3@HsmcMc6RbPX}Y(4c^XfJq!06;Mejk?vGPQc}8G zLh^3TIp3R~`}^bmasPnX?0Mhy?!Di=*Lv2o9%6K~lu3#15aHn9kgBS{^>A=-F`!*V zcmw?AU2$UtAGjDjWqF+9Z;Y!rI5&j66^*^!u=Y-_b~yK83jeh33ql;-G2ZuK@cV*- zNDmKwTPLK07t#&GkGAs$UEq79gB|+c3pCvWoLpUz_XU-N`61vdE`$@>*4+;Snpy_n z7Zd`zVe+5_z7P@mr->C2;s!l(9v-fC2s@Oz6R@i!3=`#tiGXe{brl0mjr)QMpzZ49 zVh28y?QC4!uWnIr^m2CtJxWj*lpmP>`=4#Zwc3v1Kcl19K z6BObX(zC;k>WcyE_osF|HL`}uP&OpQkC8y*s zujpv?j}c)bSCoy2s-KdM5LCy+Nwi!e6@73rp88U;wWYCd=F!+y1J;n zvqFHkJ_2ovkoQq{Q#Eyh7-%@!=-wBU*YS{7@bq!@`%zQuT$pb;wIxpMBJn5$VK;2ORNEoHc=nw+k_ zslJDk0YuPSRYy=&QQ25jN5{+C4TjV<(su**sv(r!Fes>M0_qN^;Z zsHke{sHBLIw{_4I^;K1YIhi1oh2`Wt#2l~zD7X>U+1*K5&=I8pQ&iPA^$>%bpw!*{ zL>z39ibw^xu#J$A1H?&5&EL%d>neg367=-e5Ym(rw>MEygF^Kj{X8`_y*%yZlxteYHOhGFRtv3h4?7z7^{ius|t&VcpK`V z{E=c>4ltOLpt7&6uP?$+&fP`~Z71ew>Z2-%#5#xyt6nXHpr4V7kv~`kcZ^~HMhSxU zlSc}vn2ISXiFmk+2Z-x=tBC7D^yS^1V9su?Dli{02T^S!TTvlB6$DJ&!_!{%>iwx` zdKjaH6kx7seXOIrI7(1XSqCL14EF+t<%O_*PU`k>g#Z(8FSLUV6lQM|AdY~ckt%9B zx+3E8el`$MEdx=kH&oTv0}Y&uyt9aj3R>RDTR1@2%SA{J0lI7h41`4t?L_SCk>ZNp z2xlGFs|)osj5W0E^-M(U#1T$TP&;pXeT1hDN>|%NOw3eX8!oToWMZNU2IvEa3aX%y zjz;1>ntn=77?h%pF-A)?z}?d)K-XJc$WBe&#YbL4!ADU~6@oQ%5H#`9)ko?(3+p@j z`Wt#W=^zo_uDZr1hGK?jV;iUsIBH6s0iI6ANIMe^UkyR1zOs&iNC3 z-A$dLVq*SshDzctN)F!s7!^BP1(c|ds5a739_^_w=Z7_NKxz28xCE&9pwUWR&YCu+ zE>LBdgNl)`Dwv&;GfGYw<^}^T5l;srs2EJlL&aB20k}la^&hYJw~GXS{~I7+irpWW zi*RsQa8%)P23X6@3}SBs#V^~991*e$_wb%Ssbf~Vag!yLzD11sJbN8>U9w4ciA&v<46mH!_!k$|vj`8m6kJ8d@8Q=~+GGiH zN-m{pPYgurYyFOf=@rLjTis;p+~-Jr-oZSu_-#Pjks3X7&& zT8hGLolFl=Lr2x%vwgbR$=d`G6}=Anf7_&(WizK)<7eZvPELwhr=$%gBGKU?u8(em zHuE(S%>Es(ly*GQpVxVtGHYs};KqFBz$KcMP7Y(-!@jSHxKTC!q(5QRvI-*2M5PXB zGzp~d>uP6a4gN{lO`|X=%)U*NF2t%KNc>=HDorVo$<0!{8_!*{!az%8Ne4f7H5FlC z@LgY9O7Zax(w5>oYcq|FKZrTCGuz1VxM8rDG!Kkr-6pCGr5ghdc)q5ICXz8pjP_NP zW3NbHl9lw^GFQ{oe3uGys0x$B>LC!yCH04Y9qr3Ps_*>r@=t1FEFa(aS^k>-L*UU? z&(lg|>y3Ae^qHu`)@3RV4ITT3<|;FlE(rXp&n%{La^ZW`VWo!{s11`;z^hN?<*Q#s zXYLV33MTQHDJ}PBA?)>sTn2I#V}s3GV2#*aHqA7V_>QOaHg~o*Qq3^1^4ku)6og9w z51!TF;a8)l)J#lFM9XmM-WL}eNqVHpO;o$*uMXx1{@h=9sXe`*Ium>}^(rZ8$qxUQ zmA<|Lm~<{Z%$^H!)P9c+-{Ej;>Z1pd(`cc|8|>;M#Q4c|RPHp_P5LFgsDig7RziDe z?hU%uPle)W1@cVF?YR?aQ)VG?H-B9#)z8z=wN6lv=Hs}r;J;meRQJ8}O%JSJQsJ(I zuPZLfd+%r_G*mxix8UBLZQi~|C%NpEiHPP&Ba(gVp}C5}Fn9d3X2zz&c0A)-=FR5` zr~a20=h7xDb*_Amn|M;qyf)#{3#<8Ac~XIg71jgniIVbmc6LPB=g`%@RG4(@gI^5t zVjdzeLBTq$A{h?nRE|;Mdns>bDU7hKpX8);ZJ$&6-O6B>T&84K;woq22ouLFap84C zXv=tB-S5nX)OKe?NQPCFeEAcX8b?RRLAPsQ{X5Slg~2d(ung~nh%U0G53fj={$!`c zj%Ec)sCBOnGaGU8NSx6KPS$kae7kO+Ofyq5m4@GZf{C<$joMngkHih9@rt~&d^}xv6;G11#*Oxx&ioeRCg%Rv1rkh2W z1R0uV;(QNRvRT@5Ekua&y0em%8^7n6@cMvvVa?xaWFO9G za(hwrAtY1pWA33Ze%xcLT{`;JbRN!6kLPdSl*ftkr$DXV+sqGG>sQTus4!Jm?Ak0&^!DTIt55^h3$taR zsV_-_eMN&Wa?lw`uHE!R-iEAX%7nH0G?W8A4nk8WM&R2~gQzcrjYFnZBxbhwD5LNy zo;FI_C$wbG_6G~Qw`^0=sK09;UN|4$M-VkahFbGQ%@RoQi42~F7RaB$T@5BKDs5Aq zG$zum&5ow1mp0EoI6qDS&c39%&I{XaDRCDnnhrdODmJK19+(raf3?En{dmSLm z(ICN7=^>j#vft&mha-F@sC#9feTLi|g}qJpPQLE(T1+GJ^xW9P*7RseCUWLIs-?i@ zMXz0B;_?m=sEm2!@f1(n==fVpPb<=fX1XOx`UR5x=q|>u@}+UaPpqYidwN8Kb7Ke&0YNJW=$Hz_ok`8|OHA^Pz1{ot(KNXnpRuCdu z1+!BW=16G&8s%uvwrVHXH-CMpq~V0eZM-7pq0br|QEZ;`=n>wR%ZpQ|v*TS#`uWa8 z=6qU9Z1^;Go;1oIPMxU1dLjReKiQ?2XispMp)=e#NKq9xN?cc>dSVI^bnIbT>3rMJ z&~N~ZWaa#5CLiDt)6X8P^?NO_&4V22n)T(fg?+5Un#kojy+TX6gBFMU5%0%K&G4g>J>E)KmTFXwM?nK=-;^- zvoWn(ISNl+eQ7*DoKS1HayGKcr?~#u_3egJ^c~7w=~J$$l^hxdoFy>{{LJG4olT7N zIym7UAy3{Cs=NODq!>;gW~#40A9)XlPz-OwOJz3646PgRMmdp*Pg=&~SD0u$&3KE= zAwH%kSXMX8e`BnShz>06`&Zm8cf6!3GyS!+67Ys^QgLuyzHTOGIM&cGA{=()%#aQ~ z;i4j|XpZbAtokuHXg2m*3Nz%;pD7hQov^yA84-o9$T3+{Th?$9PyZROba~yac-r<$ zz@9&o0>@}buE{WDd}XwRx%)fKv>1m#5NDm2E-iL>=eQk?Q!Cgv_~XaQVzO1SXgB?=6l{kI3dB}+>a;|IMeeHlD>AT7<2L>3%5ST1A0MQ!r#xD9_8ww9Fim&{D~`t@ryR+1_G0WVArz*^4~Qv^*^6jrJBypM5`vXO3G3bNGg}S;{5Dd&MU7NKoedxBg#( zcCmUFkMN02BP1V%*EB-n3Ia+8ybu^SE2w6hFQRYzpPW#=F%YE^}5i=nZig8DQK zGhX2Y`||$1ld+c=_u(Nyj>Bb{Y zkzJvf22Cw3`;f7`-ssOTl7OY1XTf}7J(ha_H@$CYu#Dl3Yu74+@W}Idsa&5WvVwFT zKdWc;#~m6)y)D7lk?Lp6>J^Dd#%BogP3E_Fg>UWe@@5*ZfYF}vA1Akxv!eEA_?HqZ=kMljxE7gH?=5tHaTzVnaCUYM__^tOnLlg? z@0!*aX1L5C+ST`}!COMrt3=!o@4J;VeKVIXO1xDvdiGX;9tuj?>f7>20>+H8hiMCh;ajej$CTgMs-pV6zEPk%8uEw07&Ql`; zEkRlYC(M0cQ{%4-mPRsgMDP9W4%`;*WrOI@jgsOz;eigR_wVntO8TDkxN3&xG1W6r z>2z+rYHMqY&j1l2^ZE1Vk21J?8%r7TzI^%8P*ruPp|3w!E;4bxS4!l`IP$ptYmw4# z+Y|=fZYC%27FV9AjL6HLhknd_7&7j}t6dLHfWWcE%jeH?&QiEXiuLogaMubB_tDj- zMO>qov^;GC+hNBO%|lO+OixV*GOHS>MKmqpPsrMai7=!4`MioR@Z(JcBh{J1c zoj+W!9r&z|1V01YDdY9)*Us(IyN+@3!E3i~-}X5;I0)p_{3`;}!0zc|#F{B^S2d9D z7Cwf4zz~2Hi}Ou`huH~{dUfTV^xy8X=SKUlt&f%nY!AI3?1#Z%6BDY`ylY-NznXh1 zD=P^&w9=2WZy$QQ-g`eweda|a15;ALk4p~{hXY`z@bF=@{q(yitLsC!hs=+=n7#GF z>M+aS8tFt_jlI67+T2qBNlt4GX8D4SBl|LLE#4hbzl%Ndaen&S`}c=#NMh!puYfq4 zjF>(#PeYf zTLhy2#O}o1&wNND;a-0*qIxtP=((+6q;J0NF`p}w){oQI*L}jI7W|}8r%2$&eh@HWS?)8@NW`x_RUAl^x%k|WlQa8{ zqKeP{BSoWTGA0 z9FkfSmAjkI3?h<&3(`L{Ka>4c_P*u!rw=IV1IFjod6+xSf zF6+h2^J3y;+6k}-Hno=<=BKRA$BN+ro}Rl|VE9$&p#t56pHYq#nAq6ZJ0j>w?81+C z>SKHr{f^RFokU&WHh;+oGhF;NkQ$yYE`3d#m{@JUHH-eNdx~vV^zrdAM5FnAi+0+H zBHjW7dGDu~K7{C3Mg{;Uf2KZr&hHefHUx2%0?6^DH(B5klU}F)>Qsyf*nPwDWiS?A zC@Cc+CGpqxMn&xsHh-nerjtT~d>Lu(n;gFGAP=_q&0R4UYkV~|wJpb~F9);A%)vt! zB}7D%>7{vN4Z)|w;OLNd_xJlv%9*@-_b#(Eb{%J)1ViBMC6snNNF26&v^n87@~io= zC!E(Rgjx(_xAOpb)Q@fY_WS*%6ZVHn+Zw@ZmBUr)HX(vad6LVx3@wy3G5Kkw{FH8> zUV-3@EJ`4h$h;2w{BV0FltWas`pMucR7%>q=T%-M=7)ptaYJ2C@5l5L=8zOxY0`uw zi(23F?_*;rZaaN6kBv67o+8o3iC1~r{A4&%I^TV!A(9aVl{OgC>6NdZu&|XxY~+Ld zVO`-r21km_{H(`Hk*OA6ypcsDJUi8m=kYweyyRMc3Ekwp_i*Xz@y`Fb8o--iDNat# zX4U1_X?Ll`wu3qo?tb<6_b>W79{cVLq>%OPSB%;6;hUzl;g97nUcS6+l9?e7N%=(p zCnY7le3hKMoHE)3?R(73=|%Rp*=lC|9gds{_r~BFs_9Lq1KFlpc>o&CC|c5@<%?rG z=Le>iH@#|F8rI{HOkShh^z?sEFN7MqUQ`_txSZjq#SF2=S*L!B$ElT`Epz#&pEoOT ztLBoycI4ShSf4`DHyUtH)k@THAsp~ce@c<9pK5(K?_`nw9^hegsI~3B1~yX1 z@*?oTqb&eq;>Wa4Gl($h+%oRdlH;YwYhTmFUlI`!nf~4YxaR%m&ts=Q!P0T_^7g)f zx(#EI{gW@Ytl0yIUcG*OzL*)fMgQxqUy!K}X6|l|RnTsG%;e;x1e=i}Ug`1Juf($d>DiFmo>(AV=TO#MA#MrpS#YI{U?aWDT zy;cHDgY?tJ9hTvl&`>`(%CW{Z52*~jsTpf1xGr>U;HKE zSG-5xa!#jB$B9!W*|AgKmFT&jHggs=0*Jk*P{!e>P$;S`nkM*0)BF{L?T0?THu$T% z_uy51=)&#kdYYxZg>akal?Mk$6`jZ=l__qr2Mz~o!w8I**9$y1b!yRjOzyaD{<2mz zUhV6`ualGY!TxgJi@xs29Jm2pq@@2o*%;bL3lgyY!xoFfLL9TnLc(h?4EtbH9!wvH zHM^A5D*>l%fW}%Kw6-EAhj5Fo)* zl14(=HxGWQhEZh~rAg*3LfIwmd$4(Y;Nl1jJs8F$&@F{;@mxsT;;X5vr!j|~?HYxP z$TzLGSU)+s-mkD;M7VuaT)U+w$+2mji9b&2Gd}N!D-cgipWgLUA#(HK%)@pdr`C+Ntu=-B-TnOy~E2fekQ}kb7$% z{{R?dA#8b}<2K~WutrD(&B&ro4BgqB{r3^sKq@CBf2-NyxFQKP69$RQ!Nb&$&@atV zTA9hnnpSPEM=^c^uoe;Q6EFvrw!gf)5mSbr zV$;GX;afP@7X3LmJMa;b2`lMIM0}gD1C|_KP=tqL4uB%6wAZO3v*zs5bnMPS-(ng~ zW{u@}#bJ8k;Su*W6Y10dC?;Fr*lfi}7r_`9si1YIYo}gTE zvGC3a7GF3WCCn6x!7gH7G~sL0hslwM@$vCFLos~!6#LBAcjxvEKN@i#j07x=(^X79 zQTe!>dX-XH=#Jqw#Rn|Wy;MRic}Z2Q3wZ?}(8$p*DQ!K_qI!Xsvt>`2*weriy4a~- z_MOYO9ON}zNL+u~Z~=1VkciI_A?Vfx+(v0wc(66OU2}^r9O_d72>0P)ZSe zwgY0F+7@$$i5s@(a6BLceUC@^=>i3Ye3_O*1j<&abx++S{T_ ziHzbujt@qy1iWjf6g>yIu942B)Zjipy9bIB|3rCkx}R{~zrPuLJp1G_yj?J_3Gh)N zj;^kKzO_?UcX}zA--8%`^8AWQ{pKbc{^`@F#soTOZf&aQHXRoS|BGMBN~cHLGB4B8 zCfND!P{Y~jmgJ%r*}1P+-}H$vJeiEw&_)0b%Eg9m89xwi`*fVYZ?i<8b=Qjf%J9qo z7zP=K1W53Mj|Y^QR)nb#*V3{*9gq1<3{-h9pv+w>Fepez%2H> zp1Fz`wDLXygfi4$g8uBuEH5u_m)dc9y>l%AWc8s1_5N)}d3fX6XvvI#sQdIJlR(en z$9H&QTNNO);}I40qjPB`iueFm#Ghh>AdRkLI0(MBXl5*9a43RCxfI8kBc9E6RqIonrgPab+GSS7b zmchoA&?DtJcOA9Z$bQ8>-=ySY%f0lCy)mif0_ZifH|%Dtcx|&L(l@ou&+}!?&F|m8 zuRwL{77tUvN)Eiv{_85OWo#L!vE?l-Eft=foqfofHhpsHnM8CDs-F|mvHH;SG#-F| z(LAdB9|#tfHep$-??q-Ao$;gCeD#elU3^^3YT4}(OGm2@e zXF#TcVPehUff$qK6KXTgzPiEQ(bLnDke8o-S*V>Qt@NVP?k|wNB6;Vsyf7GoKTa&y zFt%d}^u-srb(=BJfCU*;K_@k)1`{1paJ_BFM(n+Et>dQ!X3SowKlL@DocatZ|h z3MbG5duWsHxu@lyPUcYaneX3sUQBF~(e(UuK3)`IlV4esVPt0%70L$bR@g0ZX`aBIy!-TZy1_Mbi0`_cFI*sQRN0$PFw8Hq2^YfpUwwHoTGPYC#)cwQy5TkP&42 ztqrX%@2=#uvX>MUNl|b-i+ldKT0BN-?c>l+Z_NrhZ?W84u$h$AoKBP2SrRtg=Q^b+ zAAc5E00;5!-2V=FT(0p(VL?H`$C8px0m&kwqU(25Eer~l)dPo0@O#>3Ivc=N&}0sf zWRgaR_sx$ldaWi%JctEcoroyN`0poG^Px^>n;#Rb1M1Jz-m45YH2qnhw0@yLCzRbl zAsGJ56$giG=1L2YE+7I11M6d#);n)zeBE57$Yu(t5V^ABR<<*if-hh0X!=V>Y(6nM z>J%_2$p5I*3B?U+&$(Lv$A3V+ zGt+SyWWFb}AQAL%9ARU2D1yWN9!EYk%()P~y~cdnvn(*_Xe%gUOsi#oHLE046{dEI zwDk#jK8T^ZhK9E{$1CHeLob6RPX=2eiC14EEOov9AF2ei_sO{ae-;Ub@7(PabAxho zJ1K&7pSngsrcpUa%)f(*`JXvGU8(c$j)S?W{MwNx%)>jxT5i8BN%kFmH686>eZftP z%&ZT}?0RYN?QxtnHREYt;IRB6EbV2DG(*=YmW_aV0!29h;%kr zlcev4G5%9tM6p2+HIlR@)3jfblT$f-E#fBCtIM8WPe_9}ICvwRD_)Y#jiz{Hlk#gQ zmA=l*oNg)JNPQy>7+Kwo*e|p+G*LoZAWAZ;YiI=31Cc@S$L^G!kAANW_YT(w9=#>I>*4H*Edip1A3(Hm2RQFdRTY)v z+_JJ=tNDeD46G>cVZpo=G<$0o%fI%%8=16|P_9#y_T{}i-%!UNl2%6R!H#Tv6)YM4xG$5X`DV76Tp zCo69fsSCh(O$4{Dm=KQ|^LZ*Nsw|h`4~9LP)icveRTF6SM_NW3`km6A6`N;&_TvHL zQtvz85q|W73koR#|a7@hSHzAf1=^LvLD^;vDuAHm__VTCcC{(dD6PhdBw!Mr^= z3Ams4fJUOdrGT)qijC$_@&gn|GjFl{9jfrY7X;E>H~a|I_H~R)}pGMhlw0Y zk9M$(9cJ`%qRG$@#t^l8W~1kkPL?yon;5<_nsFB83E zbGAgFr-oTB)Z|2TV-A@tcWcb@_ppE>%K!Kw1F<}I;@s9Lj#WW2D4=|C zOeb45^2_n;K+IS{)wxSIq`D#TdfP6OdYMEdE4u!hE7bRP)5rC;=e3Q7tpdVj6O4gF z=}5GNj3X-$4B%91SyfSo0+~U*x0v+Ux9G?p>${_UT@}$l-m%=^EVk&+o!&wrTEc7Q zk$|_#Gjyqo|FF)d;gJE@B5$qryl_41)O92D>E?3QyTwhy1BZ5qLr6&lg0HEg%mHQU zSp-+(Ys1~!Z%&(KEX3i>ej}Ser_WFR0M?JX z2ylm*cpL0WPjxP4KExr>uV1)!b`!ygmju~u&++?+iHNuWoq1?sbF&Xx{uMB=sl#9V zwx?UDENi^p(Wawryppm#BkivNRYU`G^Fl%q%#y~Vu+K>XR$Ke~poBHY3F1^$k-F13F|^@x)ER3tBfh-(ENL&^%1w6Scx>egOIRR~Sl*LoPQh8~OMTO);bpTx zSDiu$4QydZmLT$H`B3sDN27ilPN+olb7jThf5-ws&A*bL{=|c5G0-AW21f>{0>e%o z)ri>hk4)KGUn}+cQyN1UW}F3Ukr;dShalh3H)_Uz`EF!fz@3Qe7$M_+Sf}3}`*Z9D z1ykt*nih8TWd3)zd5yR%MVJy7I--9XN#2Ng7EjZo#DODDeXrkjBNb+8j2p=HOQ4T! zUx93@r%;afx+5SBzg`OoI13*{SXyq;E&**`%ln}ga}V``^*Fyz>>nb+4dAL zC4&9B-A!3!l8$vTX>;^mMI9B@tqb0{BQ|?}NuNUU!;PP?UrEmDl9*6W zj{0GXvTKUoj|5UqC+O$hMdK!8#ycT@bo)NkF9F>id-MeQHP|?#7e??1lZF1Wa9jo` zC;?ovbS&mHA-dKx|>R5{MoiyNnZt1C_)2Bh=ATrfq9=GLwFw zpCY^a@NHX1BrU&0*F{AYw}+w@6vPkwAlO*_8bytv5q#ZKPw_QAufx!--j}M zgX$)0l}rkVTX#m_rxRi}^`F!!n${jbbsEbYm{eG=&A|FdquW%C!*PhjD3^@Lh=9i0 z8HkT5o@21%Q!DA`q!>OX31(C!2Q zyDkbW@qZz#2A Date: Mon, 9 Dec 2024 09:52:55 +0100 Subject: [PATCH 64/64] Bump org.slf4j:slf4j-simple from 2.0.13 to 2.0.16 (#577) Bumps org.slf4j:slf4j-simple from 2.0.13 to 2.0.16. --- updated-dependencies: - dependency-name: org.slf4j:slf4j-simple dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- admin-ui/pom.xml | 2 +- examples/java-api/pom.xml | 2 +- knowledge-directory/pom.xml | 2 +- reasoner/pom.xml | 2 +- smart-connector-api/pom.xml | 2 +- smart-connector-rest-server/pom.xml | 2 +- smart-connector/pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/admin-ui/pom.xml b/admin-ui/pom.xml index c6fd43b2..89336c8b 100644 --- a/admin-ui/pom.xml +++ b/admin-ui/pom.xml @@ -52,7 +52,7 @@ org.slf4j slf4j-simple - 2.0.13 + 2.0.16 diff --git a/examples/java-api/pom.xml b/examples/java-api/pom.xml index e1a8588f..d3c048da 100644 --- a/examples/java-api/pom.xml +++ b/examples/java-api/pom.xml @@ -43,7 +43,7 @@ org.slf4j slf4j-simple - 2.0.13 + 2.0.16 diff --git a/knowledge-directory/pom.xml b/knowledge-directory/pom.xml index 3fcdc395..ca319ffc 100644 --- a/knowledge-directory/pom.xml +++ b/knowledge-directory/pom.xml @@ -117,7 +117,7 @@ org.slf4j slf4j-simple - 2.0.13 + 2.0.16 diff --git a/reasoner/pom.xml b/reasoner/pom.xml index 355254c5..bf282b0d 100644 --- a/reasoner/pom.xml +++ b/reasoner/pom.xml @@ -39,7 +39,7 @@ org.slf4j slf4j-simple - 2.0.13 + 2.0.16 diff --git a/smart-connector-api/pom.xml b/smart-connector-api/pom.xml index 545faa38..b7f5c165 100644 --- a/smart-connector-api/pom.xml +++ b/smart-connector-api/pom.xml @@ -15,7 +15,7 @@ org.slf4j slf4j-simple - 2.0.13 + 2.0.16 eu.knowledge.engine diff --git a/smart-connector-rest-server/pom.xml b/smart-connector-rest-server/pom.xml index b1053fa1..57012e4f 100644 --- a/smart-connector-rest-server/pom.xml +++ b/smart-connector-rest-server/pom.xml @@ -41,7 +41,7 @@ org.slf4j slf4j-simple - 2.0.13 + 2.0.16 diff --git a/smart-connector/pom.xml b/smart-connector/pom.xml index a41dd594..0668f7a8 100644 --- a/smart-connector/pom.xml +++ b/smart-connector/pom.xml @@ -25,7 +25,7 @@ org.slf4j slf4j-simple - 2.0.13 + 2.0.16