diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerIncludeMetaKIs.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerIncludeMetaKIs.java new file mode 100644 index 000000000..9a7f4232f --- /dev/null +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskAnswerIncludeMetaKIs.java @@ -0,0 +1,140 @@ +package eu.knowledge.engine.rest.api; + +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.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.JsonReader; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestAskAnswerIncludeMetaKIs { + private final RestServerHelper rsh = new RestServerHelper(); + private static int PORT = 8280; + + @BeforeAll + public void setUpServer() { + rsh.start(PORT); + } + + @Test + public void testAskAnswer() 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 + var otherSc = new AsyncTester(new Runnable() { + @Override + public void run() { + try { + // register the AnswerKB + HttpTester registerOtherKb = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"https://www.tno.nl/example/otherKB\", \"knowledgeBaseName\": \"RelationProvider\", \"knowledgeBaseDescription\": \"A KB that provides relations between people\", \"reasonerLevel\" : 2}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerOtherKb.expectStatus(200); + KBReady.countDown(); + } catch (MalformedURLException e) { + fail(); + } + } + }); + otherSc.start(); + + KBReady.await(); + + // register the AnswerKB + HttpTester registerAskKb = new HttpTester(new URL(url + "/sc"), "POST", + "{\"knowledgeBaseId\": \"https://www.tno.nl/example/metadataAsker\", \"knowledgeBaseName\": \"RelationProvider\", \"knowledgeBaseDescription\": \"A KB that provides relations between people\", \"reasonerLevel\" : 2}", + Map.of("Content-Type", "application/json", "Accept", "*/*")); + registerAskKb.expectStatus(200); + + // register the AskKI with IncludeMetaKIs enabled + HttpTester registerAskKiWithIncludeMetaKIsEnabled = new HttpTester(new URL(url + "/sc/ki"), "POST", + "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", \"knowledgeInteractionName\": \"askMetadataWithIncludeMetaKIs\", \"includeMetaKIs\": \"true\", \"graphPattern\": \"?kb .\"}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/metadataAsker", "Content-Type", + "application/json", "Accept", "*/*")); + registerAskKiWithIncludeMetaKIsEnabled.expectStatus(200); + + // register the AskKI without IncludeMetaKIs (= disabled) + HttpTester registerAskKiWithoutIncludeMetaKIs = new HttpTester(new URL(url + "/sc/ki"), "POST", + "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", \"knowledgeInteractionName\": \"askMetadataWithoutIncludeMetaKIs\", \"graphPattern\": \"?kb .\"}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/metadataAsker", "Content-Type", + "application/json", "Accept", "*/*")); + registerAskKiWithoutIncludeMetaKIs.expectStatus(200); + + // register the AskKI with IncludeMetaKIs disabled + HttpTester registerAskKiWithIncludeMetaKIsDisabled = new HttpTester(new URL(url + "/sc/ki"), "POST", + "{\"knowledgeInteractionType\": \"AskKnowledgeInteraction\", \"knowledgeInteractionName\": \"askMetadataWithIncludeMetaKIsDisabled\", \"includeMetaKIs\": \"false\", \"graphPattern\": \"?kb .\"}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/metadataAsker", "Content-Type", + "application/json", "Accept", "*/*")); + registerAskKiWithIncludeMetaKIsDisabled.expectStatus(200); + + // start asking + + HttpTester askAskKiWithIncludeMetaKIs = new HttpTester(new URL(url + "/sc/ask"), "POST", + "{\"recipientSelector\": {\"knowledgeBases\": []}, \"bindingSet\": []}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/metadataAsker", "Knowledge-Interaction-Id", + "https://www.tno.nl/example/metadataAsker/interaction/askMetadataWithIncludeMetaKIs", + "Content-Type", "application/json", "Accept", "*/*")); + System.out.println("Result is:" + askAskKiWithIncludeMetaKIs.getBody()); + + JsonReader jsonReader = Json.createReader(new StringReader(askAskKiWithIncludeMetaKIs.getBody())); + JsonObject jsonRoot = jsonReader.readObject(); + JsonArray jsonBindingSet = jsonRoot.getJsonArray("bindingSet"); + assertEquals(1, jsonBindingSet.size()); + + HttpTester askAskKiWithoutIncludeMetaKIs = new HttpTester(new URL(url + "/sc/ask"), "POST", + "{\"recipientSelector\": {\"knowledgeBases\": []}, \"bindingSet\": []}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/metadataAsker", "Knowledge-Interaction-Id", + "https://www.tno.nl/example/metadataAsker/interaction/askMetadataWithoutIncludeMetaKIs", + "Content-Type", "application/json", "Accept", "*/*")); + System.out.println("Result is:" + askAskKiWithoutIncludeMetaKIs.getBody()); + jsonReader = Json.createReader(new StringReader(askAskKiWithoutIncludeMetaKIs.getBody())); + jsonRoot = jsonReader.readObject(); + jsonBindingSet = jsonRoot.getJsonArray("bindingSet"); + assertEquals(0, jsonBindingSet.size()); + + HttpTester askAskKiWithIncludeMetaKIsDisabled = new HttpTester(new URL(url + "/sc/ask"), "POST", + "{\"recipientSelector\": {\"knowledgeBases\": []}, \"bindingSet\": []}", + Map.of("Knowledge-Base-Id", "https://www.tno.nl/example/metadataAsker", "Knowledge-Interaction-Id", + "https://www.tno.nl/example/metadataAsker/interaction/askMetadataWithIncludeMetaKIsDisabled", + "Content-Type", "application/json", "Accept", "*/*")); + System.out.println("Result is:" + askAskKiWithIncludeMetaKIsDisabled.getBody()); + jsonReader = Json.createReader(new StringReader(askAskKiWithIncludeMetaKIsDisabled.getBody())); + jsonRoot = jsonReader.readObject(); + jsonBindingSet = jsonRoot.getJsonArray("bindingSet"); + assertEquals(0, jsonBindingSet.size()); + + } + + @AfterAll + public void cleanUp() throws MalformedURLException { + + TestUtil.unregisterAllKBs("http://localhost:" + PORT + "/rest"); + rsh.cleanUp(); + } + +} 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 df7a9a9fd..be5955227 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 @@ -181,6 +181,10 @@ public BindingSet react(ReactKnowledgeInteraction aRKI, ReactExchangeInfo aReact private boolean suspended = false; + private Thread shutdownHook = new Thread(() -> { + this.stop(); + }); + public RestKnowledgeBase(eu.knowledge.engine.rest.model.SmartConnector scModel, final Runnable onReady) { this.knowledgeBaseId = scModel.getKnowledgeBaseId(); this.knowledgeBaseName = scModel.getKnowledgeBaseName(); @@ -236,9 +240,7 @@ protected boolean removeEldestEntry(Map.Entry eldest) { if (scModel.getReasonerLevel() != null) this.sc.setReasonerLevel(scModel.getReasonerLevel()); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - this.stop(); - })); + Runtime.getRuntime().addShutdownHook(this.shutdownHook); } protected void tryProcessHandleRequestElseEnqueue(HandleRequest handleRequest) { @@ -383,7 +385,11 @@ public String register(KnowledgeInteractionBase ki) { prefixMapping = new PrefixMappingZero(); } + // TODO both {@code knowledgeGapsEnabled} and {@code includeMetaKIs} are only + // used for proactive KIs, so maybe move them from the {@code + // KnowledgeInteractionBase} to the specific KI type classes. boolean knowledgeGapsEnabled = ki.getKnowledgeGapsEnabled() == null ? false : ki.getKnowledgeGapsEnabled(); + boolean includeMetaKIs = ki.getIncludeMetaKIs() == null ? false : ki.getIncludeMetaKIs(); String type = ki.getKnowledgeInteractionType(); URI kiId; @@ -403,7 +409,7 @@ public String register(KnowledgeInteractionBase ki) { } var askKI = new AskKnowledgeInteraction(ca, new GraphPattern(prefixMapping, aki.getGraphPattern()), - ki.getKnowledgeInteractionName(), false, false, knowledgeGapsEnabled, strategy); + ki.getKnowledgeInteractionName(), false, includeMetaKIs, knowledgeGapsEnabled, strategy); kiId = this.sc.register(askKI); this.knowledgeInteractions.put(kiId, askKI); @@ -439,7 +445,8 @@ public String register(KnowledgeInteractionBase ki) { "At least one of argumentGraphPattern and resultGraphPattern must be given for POST knowledge interactions."); } - var postKI = new PostKnowledgeInteraction(ca, argGP, resGP, ki.getKnowledgeInteractionName()); + var postKI = new PostKnowledgeInteraction(ca, argGP, resGP, ki.getKnowledgeInteractionName(), false, + includeMetaKIs, null /* default strategy */); kiId = this.sc.register(postKI); this.knowledgeInteractions.put(kiId, postKI); @@ -746,6 +753,8 @@ public void stop() { this.cancelInactivityTimeout(); try { this.sc.stop().get(); + Runtime.getRuntime().removeShutdownHook(this.shutdownHook); + } catch (InterruptedException | ExecutionException e) { LOG.error("An error occurred while stopping SC of KB '{}'.", this.knowledgeBaseId); } 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 600baff31..2e6542b9d 100644 --- a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml +++ b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml @@ -183,6 +183,11 @@ paths: 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. + + The body of a KI can also contain the option "includeMetaKIs" (see example 'ASK with meta KIs included'). + This options is disabled by defauilt and when enabled will take meta knowledge interactions of other + Smart Connector into account. This means that meta data of the knowledge network can be requested + such as KB id, names, descriptions, knowledge interactions, etc. For more information see https://www.knowledge-engine.eu/ontology content: application/json; charset=UTF-8: schema: @@ -224,6 +229,11 @@ paths: knowledgeInteractionType: AskKnowledgeInteraction graphPattern: "?a ?b ." knowledgeGapsEnabled: "true" + ASK with meta KIs included: + value: + knowledgeInteractionType: AskKnowledgeInteraction + graphPattern: "?a ?b ." + includeMetaKIs: true responses: '200': description: If the Knowledge Interaction is successfully registered, it returns the KnowledgeInteractionId object. @@ -670,6 +680,8 @@ components: pattern: '^[a-zA-Z0-9-]*$' knowledgeGapsEnabled: type: boolean + includeMetaKIs: + type: boolean communicativeAct: $ref: '#/components/schemas/CommunicativeAct' prefixes: