diff --git a/.github/workflows/docker-image-release.yml b/.github/workflows/docker-image-release.yml new file mode 100644 index 00000000..9365e708 --- /dev/null +++ b/.github/workflows/docker-image-release.yml @@ -0,0 +1,48 @@ +name: Release Docker images + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + release: + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'release' && github.event.prerelease == false) + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build JARs + run: mvn -Dmaven.test.skip=true package + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build and push Knowledge Directory Docker image + uses: docker/build-push-action@v6 + with: + context: ./knowledge-directory + platforms: linux/amd64, linux/arm64 + tags: ghcr.io/tno/knowledge-engine/knowledge-directory:${{ github.event.release.tag_name }} + push: true + - name: Build and push Smart Connector Docker image + uses: docker/build-push-action@v6 + with: + context: ./smart-connector-rest-dist + platforms: linux/amd64, linux/arm64 + tags: ghcr.io/tno/knowledge-engine/smart-connector:${{ github.event.release.tag_name }} + push: true + - name: Build and push Admin UI Docker image + uses: docker/build-push-action@v6 + with: + context: ./admin-ui + platforms: linux/amd64, linux/arm64 + tags: ghcr.io/tno/knowledge-engine/admin-ui:${{ github.event.release.tag_name }} + push: true \ No newline at end of file diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml new file mode 100644 index 00000000..4e5bcecc --- /dev/null +++ b/.github/workflows/draft-release.yml @@ -0,0 +1,42 @@ +name: Make draft release + +on: + workflow_dispatch: + inputs: + version: + description: "Version for new release (X.Y.Z)" + required: true + type: string + +env: + MAVEN_OPTS: -Dmaven.test.skip=true -Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true + MAVEN_CLI_OPTS: --batch-mode --errors --fail-at-end --show-version + +jobs: + setup: + runs-on: ubuntu-latest + container: + image: maven:3.9.9-eclipse-temurin-17-focal + options: --user 1001 + + steps: + - uses: actions/checkout@v4 + - name: Build JARs + run: mvn $MAVEN_CLI_OPTS package + - name: Create tag + run: | + git config user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + git tag -a ${{ inputs.version }} -m ${{ inputs.version }} + git push origin ${{ inputs.version }} + - name: Draft release + uses: softprops/action-gh-release@v2 + with: + working-directory: ${{ github.workspace }} + files: | + smart-connector-rest-dist/target/smart-connector-rest-dist-${{ inputs.version }}-with-dependencies.jar + knowledge-directory/target/knowledge-directory-${{ inputs.version }}-with-dependencies.jar + tag_name: ${{ inputs.version }} + draft: true + generate_release_notes: true + make_latest: true \ No newline at end of file diff --git a/README.md b/README.md index 7b2d1ce4..e109bbfe 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,11 @@ The rest of this README is structured as follows: - [Developer information](#developer-information) - [Components](#components) - [Release steps](#release-steps) + - [Code conventions](#code-conventions) - [(advanced) Administering a Knowledge Engine runtime](#advanced-administering-a-knowledge-engine-runtime) - [Starting the Knowledge Engine in local mode](#starting-the-knowledge-engine-in-local-mode) - [Starting the Knowledge Engine in distributed mode](#starting-the-knowledge-engine-in-distributed-mode) + - [Configuration](#configuration) # Demonstration videos and tutorials @@ -71,7 +73,7 @@ The easiest way to start a Knowledge Engine runtime is with Docker: ```bash docker run \ -p 8280:8280 \ - ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 ``` The Knowledge Engine runtime is now available to use via the REST API at base URL `http://localhost:8280/rest` on your host machine. @@ -82,10 +84,10 @@ However, running it in the above way **does not support** data exchange with rem To interact with other runtimes, it needs additional configuration: - An additional port mapping for the socket that listens for communication from other runtimes. -- `KE_RUNTIME_EXPOSED_URL`: The URL via which the above socket is available for communication from the other runtime(s). You need to make sure the traffic is correctly routed to the container's port 8081 with a reverse proxy. -- `KD_URL`: The URL on which to find the knowledge directory (to discover peers). +- `ke.runtime.exposed.url`: The URL via which the above socket is available for communication from the other runtime(s). You need to make sure the traffic is correctly routed to the container's port 8081 with a reverse proxy. +- `kd.url`: The URL on which to find the knowledge directory (to discover peers). -The configuration can be set as follows: +The configuration can be set as follows (note that the configuration properties below use [underscores and capital letters](#configuration)) ```bash docker run \ @@ -93,7 +95,7 @@ docker run \ -p 8081:8081 \ -e KD_URL=https://knowledge-directory.example.org \ -e KE_RUNTIME_EXPOSED_URL=https://your-domain.example.org:8081 \ - ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 ``` ### Running with Java @@ -111,7 +113,7 @@ export KE_RUNTIME_EXPOSED_URL=https://your-domain.example.org:8081 # Start it. The argument (8280) denotes the port number at which it # will listen for connections to the Knowledge Engine REST API. java -jar -Dorg.slf4j.simpleLogger.logFile=smart-connector.log \ - smart-connector-rest-dist-1.2.5-with-dependencies.jar 8280 + smart-connector-rest-dist-1.3.0-with-dependencies.jar 8280 ``` The JAR can be retrieved by compiling the project: @@ -181,15 +183,19 @@ 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). The high-level design of the smart connector can be found in [the wiki](https://github.com/TNO/knowledge-engine/wiki/High%E2%80%90level-Design). - `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` - This module contains the REST API layer that is built on top of the Java Developer API. - `smart-connector-rest-dist` - - A distribution of the server that provides the REST API layer for your smart connector(s), and uses the smart connector implementation from the `smart-connector` module. For instructions on how to use it, refer to [the section below](#using-the-rest-api). For instructions on how to set it up, refer to [this section](#advanced-administering-a-knowledge-engine-runtime). + - A distribution of the server that provides the REST API layer for your smart connector(s), and uses the smart connector implementation from the `smart-connector` module. For instructions on how to use it, refer to [the section below](#using-the-rest-api). For instructions on how to set it up, refer to [this section](#advanced-administering-a-knowledge-engine-runtime). - `admin-ui` - A REST API which provides meta-data about smart connectors in a knowledge network. Can be used in an administration inferface for a knowledge network. It is implemented as a knowledge base that uses metadata of other knowledge bases. +- `reasoner` + - This module contains the reasoner specifically designed for the distributive nature of the knowledge engine. +- `knowledge-directory` + - This module contains the Knowledge Directory which is used to find other knowledge engine runtimes. ## Release steps These are instructions on what to do when we release a new version of the knowledge engine. @@ -199,40 +205,21 @@ These are instructions on what to do when we release a new version of the knowle - all `pom.xml` files - `openapi-sc.yaml` version - Docker image tags in the Docker Compose examples. -2. Make a commit for the release, and tag it with `git tag {x}.{y}.{z}`. -3. `mvn deploy` (for this you need `Deploy-Token` or `Private-Token` configured in your Maven's `settings.xml`, see [GitLab's documentation on this](https://docs.gitlab.com/ee/user/packages/maven_repository/#authenticate-to-the-package-registry-with-maven)) -4. Push the commit and the tag. -5. Build and push the new Docker images to GitLab: - -```bash -docker buildx build ./smart-connector-rest-dist --platform linux/arm64,linux/amd64 --tag docker-registry.inesctec.pt/interconnect/knowledge-engine/smart-connector-rest-dist:1.2.5 --push -docker buildx build ./knowledge-directory --platform linux/arm64,linux/amd64 --tag docker-registry.inesctec.pt/interconnect/knowledge-engine/knowledge-directory:1.2.5 --push -docker buildx build ./admin-ui --platform linux/arm64,linux/amd64 --tag docker-registry.inesctec.pt/interconnect/knowledge-engine/admin-ui:1.2.5 --push -``` - -6. Build and push the new Docker images to GitHub: - -```bash -docker buildx build ./smart-connector-rest-dist --platform linux/arm64,linux/amd64 --tag ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 --push -docker buildx build ./knowledge-directory --platform linux/arm64,linux/amd64 --tag ghcr.io/tno/knowledge-engine/knowledge-directory:1.2.5 --push -docker buildx build ./admin-ui --platform linux/arm64,linux/amd64 --tag ghcr.io/tno/knowledge-engine/admin-ui:1.2.5 --push -``` - -7. Prepare the next SNAPSHOT version and make a commit for that too. +2. Make a commit and push it to a new branch for the release. +3. Make a Pull Request and merge it into master after tests have succeeded. +4. Go to Actions on GitHub and trigger the workflow "Make draft release" with the new version as input. +5. When the workflow is finished, go to the newly generated draft release. Check the release notes, make any changes if necessary, and publish the release. +6. Prepare the next SNAPSHOT version and make a commit for that in a PR and merge into master. 1. `openapi-sc.yaml` 2. `pom.xml` 3. Leave the non-SNAPSHOT version in this README, and in the Docker Compose examples. -8. In GitLab, create a new release at https://gitlab.inesctec.pt/interconnect/knowledge-engine/-/releases - 1. Use the new tag for the release - 2. Write release notes - 3. Find the latest version of the package at https://gitlab.inesctec.pt/interconnect/knowledge-engine/-/packages/976 and copy the link to the JAR with dependencies. Include it in "Release assets" with type "Package" and link title "Knowledge Engine REST server JAR (with dependencies)" (see previous releases) -9. In GitHub, create a new release at https://github.com/TNO/knowledge-engine/releases/new - 1. Use the new tag for the release - 2. Include the same release notes -10. Inform mailing list(s) (and [the blog](https://www.knowledge-engine.eu/blog/)) about the new release. +7. Inform mailing list(s) (and [the blog](https://www.knowledge-engine.eu/blog/)) about the new release. + +## Code conventions +The code conventions of the knowledge-engine can be found in the `/ide` folder in the Eclipse IDE format. The format can often also be imported in other Java IDEs like IntelliJ, VSCode or Netbeans. ## (advanced) Administering a Knowledge Engine runtime -To start a new instance of the REST API knowledge engine version 1.2.5, make sure you have `git checkout 1.2.5` the tag `1.2.5`. Now make sure you run the `mvn clean install` command successfully from the root of the repository. +To start a new instance of the REST API knowledge engine version 1.3.0, make sure you have `git checkout 1.3.0` the tag `1.3.0`. Now make sure you run the `mvn clean install` command successfully from the root of the repository. ### Starting the Knowledge Engine in local mode When no additional configuration parameters are provided, the Knowledge Engine will by default run in local mode. This means you can create multiple smart connectors that can communicate with each other through the REST API, but the Knowledge Engine will not connect to a knowledge directory and will not be able to connect with smart connectors running in other runtimes. @@ -246,24 +233,27 @@ cd smart-connector-rest-dist/target Finally, start the server (note that you can configure a log file by including the `-Dorg.slf4j.simpleLogger.logFile=ke.log` system property to the JVM): ```bash -java -Dorg.slf4j.simpleLogger.logFile=ke.log -cp "smart-connector-rest-dist-1.2.5.jar:dependency/*" eu.knowledge.engine.rest.Main 8280 +java -Dorg.slf4j.simpleLogger.logFile=ke.log -cp "smart-connector-rest-dist-1.3.0.jar:dependency/*" eu.knowledge.engine.rest.Main 8280 ``` If you want to run in it in the background, you can use the `nohup` linux command (which does not use the simpleLogger configuration system property, but redirects the standard err/out): ```bash -nohup java -cp "smart-connector-rest-dist-1.2.5.jar:dependency/*" eu.knowledge.engine.rest.Main 8280 > ke.log +nohup java -cp "smart-connector-rest-dist-1.3.0.jar:dependency/*" eu.knowledge.engine.rest.Main 8280 > ke.log ``` ### Starting the Knowledge Engine in distributed mode The Knowledge Engine can also start in distributed mode, where it connects with a remote knowledge directory and where different instances of the Knowledge Engine (each instance hosting one or more smart connectors) can communicate with each other. More information about starting the Knowledge Engine in distributed mode can be found in the [documentation](docs/docs/distributed_mode.md). -### Additional configuration environment variables +### Configuration +TNO Knowledge Engine uses the [MicroProfile Config 3.1](https://microprofile.io/specifications/config/) specification to configure its behaviour and we use [SmallRye](https://smallrye.io/smallrye-config/) as the implementation of this specification. The default configuration values can be found in the [microprofile-config.properties](./smart-connector/src/main/resources/META-INF/microprofile-config.properties) configuration file. And, as described in the specification, these configuration values can be overridden by [environment variables and system properties](https://download.eclipse.org/microprofile/microprofile-config-3.1/microprofile-config-spec-3.1.html#default_configsources). Note that environment variables can use underscores and capital letters to adhere to their naming conventions and the MicroProfile Config automatically maps those to corresponding configuration properties using [specific rules](https://download.eclipse.org/microprofile/microprofile-config-3.1/microprofile-config-spec-3.1.html#default_configsources.env.mapping). + +A description of all configuration properties can be found in the [`SmartConnectorConfig`](./smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorConfig.java) class. The rest of this section highlights some of these configuration properties. *Increasing the wait time for other KBs to respond* -By default, a Smart Connector waits `10` seconds max for a reply from another Smart Connector when sending an ASK/POST message. This time is configurable via the `KE_KB_WAIT_TIMEOUT` environment variable and setting it to `0` means the Smart Connector will wait indefinitely (this can be useful when dealing with Human KBs). +By default, a Smart Connector waits `10` seconds max for a reply from another Smart Connector when sending an ASK/POST message. This time is configurable via the `ke.kb.wait.timeout` property and setting it to `0` means the Smart Connector will wait indefinitely (this can be useful when dealing with Human KBs). *Increasing the HTTP timeouts* -By default, a KER waits `5` seconds max for a HTTP response from another KER when sending a message via the inter-KER protocol. The time is configurable via the `KE_HTTP_TIMEOUT` environment variable. \ No newline at end of file +By default, a KER waits `5` seconds max for a HTTP connection response from another KER when sending a message via the inter-KER protocol. The time is configurable via the `ke.http.timeout` property \ No newline at end of file diff --git a/admin-ui/pom.xml b/admin-ui/pom.xml index dc36f745..58cee5d5 100644 --- a/admin-ui/pom.xml +++ b/admin-ui/pom.xml @@ -15,10 +15,7 @@ A user interface for managing a Knowledge Network. - 2.2.22 - 11.0.15 - 3.1.9 - 2.18.1 + 2.2.27 UTF-8 @@ -39,12 +36,10 @@ com.fasterxml.jackson.datatype jackson-datatype-joda - ${jackson-version} com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider - ${jackson-version} @@ -52,25 +47,20 @@ org.slf4j slf4j-simple - 2.0.13 - org.eclipse.jetty jetty-util - ${jetty-version} org.eclipse.jetty jetty-servlet - ${jetty-version} org.eclipse.jetty jetty-server - ${jetty-version} @@ -78,29 +68,25 @@ org.glassfish.jersey.containers jersey-container-servlet-core - ${jersey3-version} org.glassfish.jersey.inject jersey-hk2 - ${jersey3-version} org.glassfish.jersey.media jersey-media-multipart - ${jersey3-version} org.glassfish.jersey.containers jersey-container-servlet - ${jersey3-version} jakarta.xml.bind jakarta.xml.bind-api - 4.0.1 + 4.0.2 @@ -115,22 +101,19 @@ org.junit.jupiter junit-jupiter-api - 5.11.3 test org.junit.jupiter junit-jupiter-engine - 5.11.3 test org.junit.jupiter junit-jupiter - 5.11.3 test - + eu.knowledge.engine smart-connector-rest-server @@ -143,7 +126,7 @@ org.openapitools openapi-generator-maven-plugin - 7.9.0 + 7.10.0 generate-sources diff --git a/admin-ui/src/main/java/eu/knowledge/engine/admin/AdminUI.java b/admin-ui/src/main/java/eu/knowledge/engine/admin/AdminUI.java index ab8a3e55..6ec5209e 100644 --- a/admin-ui/src/main/java/eu/knowledge/engine/admin/AdminUI.java +++ b/admin-ui/src/main/java/eu/knowledge/engine/admin/AdminUI.java @@ -1,86 +1,35 @@ package eu.knowledge.engine.admin; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.HashSet; import java.util.Set; import java.util.UUID; +import java.util.concurrent.Phaser; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.Resource; -import org.apache.jena.shared.PrefixMapping; -import org.apache.jena.sparql.graph.PrefixMappingMem; -import org.apache.jena.sparql.lang.arq.ParseException; -import org.apache.jena.update.UpdateAction; -import org.apache.jena.update.UpdateFactory; -import org.apache.jena.update.UpdateRequest; -import org.apache.jena.vocabulary.RDF; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import eu.knowledge.engine.smartconnector.api.AskKnowledgeInteraction; import eu.knowledge.engine.smartconnector.api.BindingSet; -import eu.knowledge.engine.smartconnector.api.CommunicativeAct; -import eu.knowledge.engine.smartconnector.api.GraphPattern; -import eu.knowledge.engine.smartconnector.api.KnowledgeBase; import eu.knowledge.engine.smartconnector.api.ReactExchangeInfo; -import eu.knowledge.engine.smartconnector.api.ReactKnowledgeInteraction; -import eu.knowledge.engine.smartconnector.api.SmartConnector; -import eu.knowledge.engine.smartconnector.api.Vocab; -import eu.knowledge.engine.smartconnector.impl.SmartConnectorBuilder; /** * Knowledge Base that regularly prints an overview of the currently available - * Knowledge Bases within the network. - * - * We use Apache Jena's API extensively to work with the results of our ask. - * + * Knowledge Bases within the network and also provides REST APIs to access the + * same information including relations between knowledge bases. */ -public class AdminUI implements KnowledgeBase { +public class AdminUI extends MetadataKB { private static final Logger LOG = LoggerFactory.getLogger(AdminUI.class); - private static final String META_GRAPH_PATTERN_STR = "?kb rdf:type ke:KnowledgeBase . ?kb ke:hasName ?name . ?kb ke:hasDescription ?description . ?kb ke:hasKnowledgeInteraction ?ki . ?ki rdf:type ?kiType . ?ki ke:isMeta ?isMeta . ?ki ke:hasCommunicativeAct ?act . ?act rdf:type ke:CommunicativeAct . ?act ke:hasRequirement ?req . ?act ke:hasSatisfaction ?sat . ?req rdf:type ?reqType . ?sat rdf:type ?satType . ?ki ke:hasGraphPattern ?gp . ?gp rdf:type ?patternType . ?gp ke:hasPattern ?pattern ."; - - private static final String CONF_KEY_INITIAL_ADMIN_UI_DELAY = "INITIAL_ADMIN_UI_DELAY"; - - private SmartConnector sc; - private final PrefixMapping prefixes; - - // used for getting initial knowledge about other KBs - private AskKnowledgeInteraction aKI; - // used triggered when new knowledge about other KBs is available - private ReactKnowledgeInteraction rKINew; - // used triggered when knowledge about other KBs changed - private ReactKnowledgeInteraction rKIChanged; - // used triggered when knowledge about other KBs is deleted - private ReactKnowledgeInteraction rKIRemoved; - private static AdminUI instance; private static boolean continuousLog = true; - private static String knowledgeBaseId = "https://knowledge-engine.eu/adminui-" + UUID.randomUUID(); - private Model model; - - private GraphPattern metaGraphPattern; + private static String knowledgeBaseId = "https://knowledge-engine.eu/adminui-" + UUID.randomUUID(); + private static String knowledgeBaseName = "Admin UI"; + private static String knowledgeBaseDescription = "Publishes an overview of all the available Knowledge Bases via a REST API."; - /** - * Intialize a AdminUI that regularly retrieves and prints metadata about the - * available knowledge bases. - */ private AdminUI() { - // store some predefined prefixes - this.prefixes = new PrefixMappingMem(); - this.prefixes.setNsPrefixes(PrefixMapping.Standard); - this.prefixes.setNsPrefix("ke", Vocab.ONTO_URI); - - this.metaGraphPattern = new GraphPattern(this.prefixes, META_GRAPH_PATTERN_STR); - // create a new Smart Connector for this Admin UI - SmartConnectorBuilder.newSmartConnector(this).create(); - - // we wait for the Smart Connector to be ready, before registering our Knowledge - // Interactions and starting the Ask job. + super(knowledgeBaseId, knowledgeBaseName, knowledgeBaseDescription); } public static AdminUI newInstance(boolean useLog) { @@ -88,255 +37,98 @@ public static AdminUI newInstance(boolean useLog) { continuousLog = useLog; instance = new AdminUI(); } - return instance; - } - - @Override - public URI getKnowledgeBaseId() { - try { - return new URI(knowledgeBaseId); - } catch (URISyntaxException e) { - LOG.error("{}", e); - return null; - } - } - @Override - public String getKnowledgeBaseName() { - return "Admin UI"; - } + instance.setPhaser(new Phaser(1)); + instance.start(); + instance.syncKIs(); - @Override - public String getKnowledgeBaseDescription() { - return "Publishes an overview of all the available Knowledge Bases via a REST API."; + return instance; } @Override - public void smartConnectorReady(SmartConnector aSC) { - this.sc = aSC; - - LOG.info("Smart connector ready, now registering Knowledge Interactions."); - - // create the correct Knowledge Interactions - this.aKI = new AskKnowledgeInteraction(new CommunicativeAct(), this.metaGraphPattern, true); - this.rKINew = new ReactKnowledgeInteraction( - new CommunicativeAct(new HashSet(Arrays.asList(Vocab.NEW_KNOWLEDGE_PURPOSE)), - new HashSet(Arrays.asList(Vocab.INFORM_PURPOSE))), - this.metaGraphPattern, null); - this.rKIChanged = new ReactKnowledgeInteraction( - new CommunicativeAct(new HashSet(Arrays.asList(Vocab.CHANGED_KNOWLEDGE_PURPOSE)), - new HashSet(Arrays.asList(Vocab.INFORM_PURPOSE))), - this.metaGraphPattern, null); - this.rKIRemoved = new ReactKnowledgeInteraction( - new CommunicativeAct(new HashSet(Arrays.asList(Vocab.REMOVED_KNOWLEDGE_PURPOSE)), - new HashSet(Arrays.asList(Vocab.INFORM_PURPOSE))), - this.metaGraphPattern, null); - - // register the knowledge interactions with the smart connector. - this.sc.register(this.aKI); - this.sc.register(this.rKINew, (rki, ei) -> this.handleNewKnowledgeBaseKnowledge(ei)); - this.sc.register(this.rKIChanged, (rki, ei) -> this.handleChangedKnowledgeBaseKnowledge(ei)); - this.sc.register(this.rKIRemoved, (rki, ei) -> this.handleRemovedKnowledgeBaseKnowledge(ei)); + public BindingSet handleNewKnowledgeBase(ReactExchangeInfo ei) { + BindingSet bs = super.handleNewKnowledgeBase(ei); - // to receive the initial state, we do a single Ask (after sleeping for a - // specific amount of time) - try { - Thread.sleep(Integer.parseInt(AdminUI.getConfigProperty(CONF_KEY_INITIAL_ADMIN_UI_DELAY, "5000"))); - } catch (InterruptedException e) { - LOG.info("{}", e); - } - this.fetchInitialData(); + // when result available (and the config is enabled), we print the + // knowledge bases to the console. + this.printKnowledgeBases(this.getMetadata()); + return bs; } - public BindingSet handleNewKnowledgeBaseKnowledge(ReactExchangeInfo ei) { - if (!this.canReceiveUpdates()) - return new BindingSet(); - - try { - Model model = eu.knowledge.engine.smartconnector.impl.Util.generateModel(this.aKI.getPattern(), - ei.getArgumentBindings()); - - // this we can simply add to our model - this.model.add(model); - - // when result available (and the config is enabled), we print the - // knowledge bases to the console. - if (continuousLog) { - this.printKnowledgeBases(this.model); - } - } catch (ParseException e) { - e.printStackTrace(); - } - return new BindingSet(); - } - - public BindingSet handleChangedKnowledgeBaseKnowledge(ReactExchangeInfo ei) { - if (!this.canReceiveUpdates()) - return new BindingSet(); - - try { - Model model = eu.knowledge.engine.smartconnector.impl.Util.generateModel(this.aKI.getPattern(), - ei.getArgumentBindings()); - - // this is a little more complex... we have to: - // - extract the knowledge base that this message is about - // - delete all old data about that knowledge base - // - insert the *new* data about that knowledge base - - Resource kb = model.listSubjectsWithProperty(RDF.type, Vocab.KNOWLEDGE_BASE).next(); - String query = String.format("DELETE { %s } WHERE { %s FILTER (?kb = <%s>) } ", this.metaGraphPattern.getPattern(), this.metaGraphPattern.getPattern(), kb.toString()); - - UpdateRequest updateRequest = UpdateFactory.create(query); - UpdateAction.execute(updateRequest, this.model); - - this.model.add(model); + @Override + public BindingSet handleChangedKnowledgeBase(ReactExchangeInfo ei) { + BindingSet bs = super.handleChangedKnowledgeBase(ei); - // when result available (and the config is enabled), we print the - // knowledge bases to the console. - if (continuousLog) { - this.printKnowledgeBases(this.model); - } - } catch (ParseException e) { - e.printStackTrace(); - } - return new BindingSet(); + // when result available (and the config is enabled), we print the + // knowledge bases to the console. + this.printKnowledgeBases(this.getMetadata()); + return bs; } - public BindingSet handleRemovedKnowledgeBaseKnowledge(ReactExchangeInfo ei) { - if (!this.canReceiveUpdates()) - return new BindingSet(); - - try { - Model model = eu.knowledge.engine.smartconnector.impl.Util.generateModel(this.aKI.getPattern(), - ei.getArgumentBindings()); - - // this is also a little complex... we have to: - // - extract the knowledge base that this message is about - // - delete all old data about that knowledge base - - Resource kb = model.listSubjectsWithProperty(RDF.type, Vocab.KNOWLEDGE_BASE).next(); - String query = String.format("DELETE { %s } WHERE { %s FILTER (?kb = <%s>) } ", this.metaGraphPattern.getPattern(), this.metaGraphPattern.getPattern(), kb.toString()); + @Override + public BindingSet handleRemovedKnowledgeBase(ReactExchangeInfo ei) { + BindingSet bs = super.handleRemovedKnowledgeBase(ei); - UpdateRequest updateRequest = UpdateFactory.create(query); - UpdateAction.execute(updateRequest, this.model); + // when result available (and the config is enabled), we print the + // knowledge bases to the console. + this.printKnowledgeBases(this.getMetadata()); - // when result available (and the config is enabled), we print the - // knowledge bases to the console. - if (continuousLog) { - this.printKnowledgeBases(this.model); - } - } catch (ParseException e) { - e.printStackTrace(); - } - return new BindingSet(); + return bs; } + @Override public void fetchInitialData() { - LOG.info("Retrieving initial other Knowledge Base info..."); + super.fetchInitialData(); - // execute actual *ask* and use previously defined Knowledge Interaction. - this.sc.ask(this.aKI, new BindingSet()).thenAccept(askResult -> { - try { - // using the BindingSet#generateModel() helper method, we can combine the graph - // pattern and the bindings for its variables into a valid RDF Model. - this.model = eu.knowledge.engine.smartconnector.impl.Util.generateModel(this.aKI.getPattern(), - askResult.getBindings()); - this.model.setNsPrefixes(this.prefixes); - // when result available (and the config is enabled), we print the - // knowledge bases to the console. - if (continuousLog) - this.printKnowledgeBases(this.model); - } catch (ParseException e) { - LOG.error("{}", e); - } - }).handle((r, e) -> { - if (r == null && e != null) { - LOG.error("An exception has occured while retrieving other Knowledge Bases info", e); - return null; - } else { - return r; - } - }); - } + // when result available (and the config is enabled), we print the + // knowledge bases to the console. - private boolean canReceiveUpdates() { - return this.model != null; + this.printKnowledgeBases(this.getMetadata()); } - private void printKnowledgeBases(Model model) throws ParseException { + private void printKnowledgeBases(Model model) { - // LOG.info("{}", this.getRDF(model)); + if (continuousLog) { + // LOG.info("{}", this.getRDF(model)); - LOG.info("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"); - LOG.info("-=-=-=-=-=-=-= Admin UI -=-=-=-=-=-=-=-"); - LOG.info("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"); - if (!model.isEmpty()) { - Set kbs = Util.getKnowledgeBaseURIs(model); + LOG.info("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"); + LOG.info("-=-=-=-=-=-=-= Admin UI -=-=-=-=-=-=-=-"); + LOG.info("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"); + if (!model.isEmpty()) { + Set kbs = Util.getKnowledgeBaseURIs(model); - int i = 0; - for (Resource kbRes : kbs) { - i++; + int i = 0; + for (Resource kbRes : kbs) { + i++; - if (i > 1) { - LOG.info(""); - } + if (i > 1) { + LOG.info(""); + } - LOG.info("Knowledge Base <{}>", kbRes); + LOG.info("Knowledge Base <{}>", kbRes); - LOG.info("\t* Name: {}", Util.getName(model, kbRes)); - LOG.info("\t* Description: {}", Util.getDescription(model, kbRes)); + LOG.info("\t* Name: {}", Util.getName(model, kbRes)); + LOG.info("\t* Description: {}", Util.getDescription(model, kbRes)); - Set kiResources = Util.getKnowledgeInteractionURIs(model, kbRes); + Set kiResources = Util.getKnowledgeInteractionURIs(model, kbRes); - for (Resource kiRes : kiResources) { - String knowledgeInteractionType = Util.getKnowledgeInteractionType(model, kiRes); - LOG.info("\t* {}{}", knowledgeInteractionType, (Util.isMeta(model, kiRes) ? " (meta)" : "")); - if (knowledgeInteractionType.equals("AskKnowledgeInteraction") - || knowledgeInteractionType.equals("AnswerKnowledgeInteraction")) { - LOG.info("\t\t- GraphPattern: {}", Util.getGraphPattern(model, kiRes)); - } else if (knowledgeInteractionType.equals("PostKnowledgeInteraction") - || knowledgeInteractionType.equals("ReactKnowledgeInteraction")) { - LOG.info("\t\t- Argument GP: {}", Util.getArgument(model, kiRes)); - LOG.info("\t\t- Result GP: {}", Util.getResult(model, kiRes)); + for (Resource kiRes : kiResources) { + String knowledgeInteractionType = Util.getKnowledgeInteractionType(model, kiRes); + LOG.info("\t* {}{}", knowledgeInteractionType, (Util.isMeta(model, kiRes) ? " (meta)" : "")); + if (knowledgeInteractionType.equals("AskKnowledgeInteraction") + || knowledgeInteractionType.equals("AnswerKnowledgeInteraction")) { + LOG.info("\t\t- GraphPattern: {}", Util.getGraphPattern(model, kiRes)); + } else if (knowledgeInteractionType.equals("PostKnowledgeInteraction") + || knowledgeInteractionType.equals("ReactKnowledgeInteraction")) { + LOG.info("\t\t- Argument GP: {}", Util.getArgument(model, kiRes)); + LOG.info("\t\t- Result GP: {}", Util.getResult(model, kiRes)); + } } } + } else { + LOG.info("No other knowledge bases found."); } - } else { - LOG.info("No other knowledge bases found."); } } - @Override - public void smartConnectorConnectionLost(SmartConnector aSC) { - LOG.info("Our Smart Connector lost its connection with the Knowledge Network."); - } - - @Override - public void smartConnectorConnectionRestored(SmartConnector aSC) { - LOG.info("Our Smart Connector restored its connection with the Knowledge Network."); - } - - @Override - public void smartConnectorStopped(SmartConnector aSC) { - LOG.info("Our Smart Connector has been succesfully stopped."); - } - - public void close() { - this.sc.stop(); - } - - public Model getModel() { - return model; - } - - public static String getConfigProperty(String key, String defaultValue) { - // We might replace this with something a bit more fancy in the future... - String value = System.getenv(key); - if (value == null) { - value = defaultValue; - LOG.info("No value for the configuration parameter '" + key + "' was provided, using the default value '" - + defaultValue + "'"); - } - return value; - } } diff --git a/admin-ui/src/main/java/eu/knowledge/engine/admin/AdminUIConfig.java b/admin-ui/src/main/java/eu/knowledge/engine/admin/AdminUIConfig.java new file mode 100644 index 00000000..dfd0e230 --- /dev/null +++ b/admin-ui/src/main/java/eu/knowledge/engine/admin/AdminUIConfig.java @@ -0,0 +1,13 @@ +package eu.knowledge.engine.admin; + +public class AdminUIConfig { + + /** + * The key to configure how long (in milliseconds) should the Admin UI wait + * until it tries to ask for all KBs in the network. This value should probably + * be higher in distributed mode, to allow the participants to reach equilibrium + * with respect to knowledge about each other. + */ + public static final String CONF_KEY_INITIAL_ADMIN_UI_DELAY = "initial.admin.ui.delay"; + +} diff --git a/admin-ui/src/main/java/eu/knowledge/engine/admin/MetadataKB.java b/admin-ui/src/main/java/eu/knowledge/engine/admin/MetadataKB.java new file mode 100644 index 00000000..dd0946e0 --- /dev/null +++ b/admin-ui/src/main/java/eu/knowledge/engine/admin/MetadataKB.java @@ -0,0 +1,233 @@ +package eu.knowledge.engine.admin; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.concurrent.ExecutionException; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.shared.PrefixMapping; +import org.apache.jena.sparql.graph.PrefixMappingMem; +import org.apache.jena.sparql.lang.arq.ParseException; +import org.apache.jena.update.UpdateAction; +import org.apache.jena.update.UpdateFactory; +import org.apache.jena.update.UpdateRequest; +import org.apache.jena.vocabulary.RDF; +import org.eclipse.microprofile.config.ConfigProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.knowledge.engine.smartconnector.api.AskKnowledgeInteraction; +import eu.knowledge.engine.smartconnector.api.BindingSet; +import eu.knowledge.engine.smartconnector.api.CommunicativeAct; +import eu.knowledge.engine.smartconnector.api.GraphPattern; +import eu.knowledge.engine.smartconnector.api.ReactExchangeInfo; +import eu.knowledge.engine.smartconnector.api.ReactKnowledgeInteraction; +import eu.knowledge.engine.smartconnector.api.Vocab; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; + +/** + * Knowledge Base that keeps track of all other KBs in the network. It does this + * by subscribing to meta knowledge interactions and updating its state + * accordingly. + * + * We use Apache Jena's API extensively to work with the results of our + * interactions. + * + */ +public class MetadataKB extends KnowledgeBaseImpl { + + private static final Logger LOG = LoggerFactory.getLogger(MetadataKB.class); + + private static final String META_GRAPH_PATTERN_STR = "?kb rdf:type ke:KnowledgeBase . ?kb ke:hasName ?name . ?kb ke:hasDescription ?description . ?kb ke:hasKnowledgeInteraction ?ki . ?ki rdf:type ?kiType . ?ki ke:isMeta ?isMeta . ?ki ke:hasCommunicativeAct ?act . ?act rdf:type ke:CommunicativeAct . ?act ke:hasRequirement ?req . ?act ke:hasSatisfaction ?sat . ?req rdf:type ?reqType . ?sat rdf:type ?satType . ?ki ke:hasGraphPattern ?gp . ?gp rdf:type ?patternType . ?gp ke:hasPattern ?pattern ."; + + private final PrefixMapping prefixes; + + // used for getting initial knowledge about other KBs + private AskKnowledgeInteraction aKI; + // used triggered when new knowledge about other KBs is available + private ReactKnowledgeInteraction rKINew; + // used triggered when knowledge about other KBs changed + private ReactKnowledgeInteraction rKIChanged; + // used triggered when knowledge about other KBs is deleted + private ReactKnowledgeInteraction rKIRemoved; + + private Model metadata; + + private GraphPattern metaGraphPattern; + + private boolean timeToSleepAndFetch = true; + + /** + * Intialize a MetadataKB that collects metadata about the available knowledge + * bases. + */ + public MetadataKB(String id, String name, String description) { + super(id, name, description); + + // store some predefined prefixes + this.prefixes = new PrefixMappingMem(); + this.prefixes.setNsPrefixes(PrefixMapping.Standard); + this.prefixes.setNsPrefix("ke", Vocab.ONTO_URI); + + this.metaGraphPattern = new GraphPattern(this.prefixes, META_GRAPH_PATTERN_STR); + + // create the correct Knowledge Interactions + this.aKI = new AskKnowledgeInteraction(new CommunicativeAct(), this.metaGraphPattern, true); + this.rKINew = new ReactKnowledgeInteraction( + new CommunicativeAct(new HashSet(Arrays.asList(Vocab.NEW_KNOWLEDGE_PURPOSE)), + new HashSet(Arrays.asList(Vocab.INFORM_PURPOSE))), + this.metaGraphPattern, null); + this.rKIChanged = new ReactKnowledgeInteraction( + new CommunicativeAct(new HashSet(Arrays.asList(Vocab.CHANGED_KNOWLEDGE_PURPOSE)), + new HashSet(Arrays.asList(Vocab.INFORM_PURPOSE))), + this.metaGraphPattern, null); + this.rKIRemoved = new ReactKnowledgeInteraction( + new CommunicativeAct(new HashSet(Arrays.asList(Vocab.REMOVED_KNOWLEDGE_PURPOSE)), + new HashSet(Arrays.asList(Vocab.INFORM_PURPOSE))), + this.metaGraphPattern, null); + + // register the knowledge interactions with the smart connector. + this.register(this.aKI); + this.register(this.rKINew, (rki, ei) -> this.handleNewKnowledgeBase(ei)); + this.register(this.rKIChanged, (rki, ei) -> this.handleChangedKnowledgeBase(ei)); + this.register(this.rKIRemoved, (rki, ei) -> this.handleRemovedKnowledgeBase(ei)); + + } + + @Override + public void syncKIs() { + super.syncKIs(); + + if (timeToSleepAndFetch) { + // to receive the initial state, we do a single Ask (after sleeping for a + // specific amount of time) + try { + Thread.sleep(ConfigProvider.getConfig().getValue(AdminUIConfig.CONF_KEY_INITIAL_ADMIN_UI_DELAY, + Integer.class)); + } catch (InterruptedException e) { + LOG.error("Initial metadata KB delay should not fail.", e); + } + this.fetchInitialData(); + this.timeToSleepAndFetch = false; + } + } + + public BindingSet handleNewKnowledgeBase(ReactExchangeInfo ei) { + if (!this.canReceiveUpdates()) + return new BindingSet(); + + try { + Model model = eu.knowledge.engine.smartconnector.impl.Util.generateModel(this.aKI.getPattern(), + ei.getArgumentBindings()); + + Resource kb = model.listSubjectsWithProperty(RDF.type, Vocab.KNOWLEDGE_BASE).next(); + this.metadata.add(model); + LOG.debug("Modified metadata with new KB '{}'.", kb); + } catch (ParseException e) { + LOG.error("{}", e); + } + return new BindingSet(); + } + + public BindingSet handleChangedKnowledgeBase(ReactExchangeInfo ei) { + + if (!this.canReceiveUpdates()) + return new BindingSet(); + + try { + Model model = eu.knowledge.engine.smartconnector.impl.Util.generateModel(this.aKI.getPattern(), + ei.getArgumentBindings()); + + // this is a little more complex... we have to: + // - extract the knowledge base that this message is about + // - delete all old data about that knowledge base + // - insert the *new* data about that knowledge base + + Resource kb = model.listSubjectsWithProperty(RDF.type, Vocab.KNOWLEDGE_BASE).next(); + String query = String.format("DELETE { %s } WHERE { %s FILTER (?kb = <%s>) } ", + this.metaGraphPattern.getPattern(), this.metaGraphPattern.getPattern(), kb.toString()); + + UpdateRequest updateRequest = UpdateFactory.create(query); + UpdateAction.execute(updateRequest, this.metadata); + + this.metadata.add(model); + + LOG.debug("Modified metadata with changed KB '{}'.", kb); + + } catch (ParseException e) { + LOG.error("{}", e); + } + return new BindingSet(); + } + + public BindingSet handleRemovedKnowledgeBase(ReactExchangeInfo ei) { + if (!this.canReceiveUpdates()) + return new BindingSet(); + + try { + Model model = eu.knowledge.engine.smartconnector.impl.Util.generateModel(this.aKI.getPattern(), + ei.getArgumentBindings()); + + // this is also a little complex... we have to: + // - extract the knowledge base that this message is about + // - delete all old data about that knowledge base + + Resource kb = model.listSubjectsWithProperty(RDF.type, Vocab.KNOWLEDGE_BASE).next(); + String query = String.format("DELETE { %s } WHERE { %s FILTER (?kb = <%s>) } ", + this.metaGraphPattern.getPattern(), this.metaGraphPattern.getPattern(), kb.toString()); + + UpdateRequest updateRequest = UpdateFactory.create(query); + UpdateAction.execute(updateRequest, this.metadata); + + LOG.debug("Modified metadata with deleted KB '{}'.", kb); + + } catch (ParseException e) { + LOG.error("{}", e); + } + return new BindingSet(); + } + + public void fetchInitialData() { + LOG.info("Retrieving initial other Knowledge Base info..."); + + try { + + // execute actual *ask* and use previously defined Knowledge Interaction. + this.getSC().ask(this.aKI, new BindingSet()).thenAccept(askResult -> { + try { + // using the BindingSet#generateModel() helper method, we can combine the graph + // pattern and the bindings for its variables into a valid RDF Model. + this.metadata = eu.knowledge.engine.smartconnector.impl.Util.generateModel(this.aKI.getPattern(), + askResult.getBindings()); + this.metadata.setNsPrefixes(this.prefixes); + + } catch (ParseException e) { + LOG.error("{}", e); + } + }).handle((r, e) -> { + if (r == null && e != null) { + LOG.error("An exception has occured while retrieving other Knowledge Bases info", e); + return null; + } else { + return r; + } + }).get(); + } catch (ExecutionException | InterruptedException ee) { + LOG.error("{}", ee); + } + + } + + protected boolean canReceiveUpdates() { + return this.metadata != null; + } + + public void close() { + this.stop(); + } + + public Model getMetadata() { + return metadata; + } +} diff --git a/admin-ui/src/main/java/eu/knowledge/engine/admin/api/RestServer.java b/admin-ui/src/main/java/eu/knowledge/engine/admin/api/RestServer.java index 5845fa55..1335080c 100644 --- a/admin-ui/src/main/java/eu/knowledge/engine/admin/api/RestServer.java +++ b/admin-ui/src/main/java/eu/knowledge/engine/admin/api/RestServer.java @@ -1,8 +1,5 @@ package eu.knowledge.engine.admin.api; -import eu.knowledge.engine.admin.AdminUI; -import eu.knowledge.engine.rest.api.CORSFilter; - import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; @@ -12,6 +9,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import eu.knowledge.engine.admin.AdminUI; +import eu.knowledge.engine.rest.api.CORSFilter; + public class RestServer { private static final Logger LOG = LoggerFactory.getLogger(RestServer.class); @@ -45,7 +45,7 @@ public static void main(String[] args) { ServletContainer scRuntime = new ServletContainer(rcRuntime); ServletHolder jerseyRuntimeServlet = new ServletHolder(scRuntime); ctx.addServlet(jerseyRuntimeServlet, "/runtime/*"); - + ResourceConfig rcAdmin = new ResourceConfig(); rcAdmin.property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true); rcAdmin.property(ServerProperties.WADL_FEATURE_DISABLE, true); diff --git a/admin-ui/src/main/java/eu/knowledge/engine/admin/api/impl/AdminApiServiceImpl.java b/admin-ui/src/main/java/eu/knowledge/engine/admin/api/impl/AdminApiServiceImpl.java index c04ee69a..fcafa67e 100644 --- a/admin-ui/src/main/java/eu/knowledge/engine/admin/api/impl/AdminApiServiceImpl.java +++ b/admin-ui/src/main/java/eu/knowledge/engine/admin/api/impl/AdminApiServiceImpl.java @@ -15,6 +15,7 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.admin.AdminUI; +import eu.knowledge.engine.admin.MetadataKB; import eu.knowledge.engine.admin.Util; import eu.knowledge.engine.admin.model.AnswerKnowledgeInteraction; import eu.knowledge.engine.admin.model.AskKnowledgeInteraction; @@ -91,7 +92,7 @@ public void getSCOverview( @Suspended final AsyncResponse asyncResponse, @Context SecurityContext securityContext) throws NotFoundException { admin = AdminUI.newInstance(false); - model = this.admin.getModel(); // todo: needs locking for multi-threading? Read while write is busy. + model = this.admin.getMetadata(); // todo: needs locking for multi-threading? Read while write is busy. if (model != null && !model.isEmpty()) { Set kbs = Util.getKnowledgeBaseURIs(model); SmartConnector[] responses = findAndAddConnections(convertToModel(kbs, model, includeMeta)); diff --git a/admin-ui/src/main/resources/META-INF/microprofile-config.properties b/admin-ui/src/main/resources/META-INF/microprofile-config.properties new file mode 100644 index 00000000..01929b25 --- /dev/null +++ b/admin-ui/src/main/resources/META-INF/microprofile-config.properties @@ -0,0 +1 @@ +initial.admin.ui.delay = 5000 \ No newline at end of file diff --git a/admin-ui/src/main/resources/openapi-admin-ui.yaml b/admin-ui/src/main/resources/openapi-admin-ui.yaml index d23d291d..5be523bb 100644 --- a/admin-ui/src/main/resources/openapi-admin-ui.yaml +++ b/admin-ui/src/main/resources/openapi-admin-ui.yaml @@ -5,7 +5,7 @@ info: description: This API provides information on Knowledge Engine Runtimes (todo), Smart Connectors, Knowledge Bases, and Knowledge Interactions in a Knowledge Engine Network. - version: 1.2.6-SNAPSHOT + version: 1.3.0 paths: /rest/admin/sc/all/{include-meta}: @@ -36,6 +36,17 @@ paths: text/plain; charset=UTF-8: schema: type: string + /rest/admin/reload: + get: + summary: Manually reload the admin-ui's smart connectors within the network. This is sometimes necessary when the initial load did not pick up all SCs correctly. + tags: + - admin API + operationId: reloadSCs + responses: + '200': + description: If the SC were reloaded. + '500': + description: If a problem occurred. components: schemas: diff --git a/admin-ui/src/test/java/eu/knowledge/engine/admin/api/TestApiRoutes.java b/admin-ui/src/test/java/eu/knowledge/engine/admin/api/TestApiRoutes.java index 290029cd..8e5ea27b 100644 --- a/admin-ui/src/test/java/eu/knowledge/engine/admin/api/TestApiRoutes.java +++ b/admin-ui/src/test/java/eu/knowledge/engine/admin/api/TestApiRoutes.java @@ -1,19 +1,9 @@ package eu.knowledge.engine.admin.api; -import com.fasterxml.jackson.databind.ObjectMapper; - -import eu.knowledge.engine.admin.AdminUI; -import eu.knowledge.engine.smartconnector.api.*; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; - -import org.apache.jena.shared.PrefixMapping; -import org.apache.jena.sparql.graph.PrefixMappingMem; -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 org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.io.OutputStream; @@ -29,15 +19,35 @@ import java.util.Collections; import java.util.concurrent.CountDownLatch; -import static org.junit.jupiter.api.Assertions.*; +import org.apache.jena.shared.PrefixMapping; +import org.apache.jena.sparql.graph.PrefixMappingMem; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.knowledge.engine.admin.AdminUI; +import eu.knowledge.engine.smartconnector.api.AnswerHandler; +import eu.knowledge.engine.smartconnector.api.AnswerKnowledgeInteraction; +import eu.knowledge.engine.smartconnector.api.AskKnowledgeInteraction; +import eu.knowledge.engine.smartconnector.api.Binding; +import eu.knowledge.engine.smartconnector.api.BindingSet; +import eu.knowledge.engine.smartconnector.api.CommunicativeAct; +import eu.knowledge.engine.smartconnector.api.GraphPattern; +import eu.knowledge.engine.smartconnector.api.SmartConnector; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class TestApiRoutes { private Thread thread; private static final Logger LOG = LoggerFactory.getLogger(TestApiRoutes.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; private static AdminUI admin; private HttpClient httpClient; @@ -242,7 +252,7 @@ public void startKbs() throws InterruptedException { int wait = 2; final CountDownLatch kb2ReceivedData = new CountDownLatch(1); - kb1 = new MockedKnowledgeBase("kb1") { + kb1 = new KnowledgeBaseImpl("kb1") { @Override public void smartConnectorReady(SmartConnector aSC) { LOG.info("smartConnector of {} ready.", this.name); @@ -269,7 +279,7 @@ public void smartConnectorReady(SmartConnector aSC) { // todo: ask/poll if ready instead of waiting Thread.sleep(5000); kb2 = null; - kb2 = new MockedKnowledgeBase("kb2") { + kb2 = new KnowledgeBaseImpl("kb2") { @Override public void smartConnectorReady(SmartConnector aSC) { LOG.info("smartConnector of {} ready.", this.name); @@ -294,7 +304,7 @@ public void stopKbs() { stopKb(kb2); } - public void stopKb(MockedKnowledgeBase aKb) { + public void stopKb(KnowledgeBaseImpl aKb) { if (aKb != null) { aKb.stop(); } diff --git a/docs/docs/common_error_messages.md b/docs/docs/common_error_messages.md new file mode 100644 index 00000000..31d90a73 --- /dev/null +++ b/docs/docs/common_error_messages.md @@ -0,0 +1,41 @@ +--- + 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 `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. + +### 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 diff --git a/docs/docs/distributed_mode.md b/docs/docs/distributed_mode.md index e142c2c1..3714681e 100644 --- a/docs/docs/distributed_mode.md +++ b/docs/docs/distributed_mode.md @@ -20,7 +20,7 @@ First of all, you need to start a knowledge directory. The desired port number f ```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 +java -Dorg.slf4j.simpleLogger.logFile=kd.log -cp "knowledge-directory-1.3.0.jar:dependency/*" eu.knowledge.engine.knowledgedirectory.Main 8080 ``` The `nohup` command can be used to run the process in the background. On overview of the registered Knowledge Engine runtimes can be found on `http://localhost:8080/ker/` (or another host or port if you desire). @@ -43,7 +43,7 @@ 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 +java -Dorg.slf4j.simpleLogger.logFile=ke.log -cp "smart-connector-rest-dist-1.3.0.jar:dependency/*" eu.knowledge.engine.rest.Main 8280 ``` ### Using Basic Authentication to secure data exchange diff --git a/docs/docs/faq.md b/docs/docs/faq.md index 909367ec..b786ca13 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,162 +61,127 @@ 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. -*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 . - ``` +### 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 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: +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. - ```sparql - ?person :hasUsername ?userName . - ``` +| | | 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 | - and the *result* graph pattern would look something like: +When your two Knowledge Interaction types have a 'yes', then you can take a look at whether the graph patterns match. - ```sparql - ?person :hasEmailaddress ?emailAddress . - ``` +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. - This tells the Knowledge Engine that you cannot send it an email address and receive the username. +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*: 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): +### 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. - ```json - { - "knowledgeInteractionType": "PostKnowledgeInteraction", - "argumentGraphPattern": "?s ?p ?o", - "resultGraphPattern": "?x ?y ?z" - } - ``` - Note that the result graph pattern is optional. +### 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. -*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. +### 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*: 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. +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*: 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. +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*: 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 ANWSER 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*: 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) -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 { @@ -206,10 +199,6 @@ Result: [ "2021-05-12T08:53:30.052924161Z", 241.63 - ], - [ - "2021-05-12T09:03:00.050337173Z", - 119.67 ] ] } @@ -218,188 +207,65 @@ 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. - - - *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. - - 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: -* 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!** - -An example: the following graph pattern defines a **device** and the **type** of it’s **property**. - +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 -?device ?property . -?property a ?propertyType . +?deviceId . +?deviceId ?m . +?m ?p . +?p ?fieldType . +?m ?ts . +?m ?val . ``` - -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 . +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": "" + } +] ``` - -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*: 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*: 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? -- *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"`. - -*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). - -*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. +The ANSWER side will need to parse this correctly (which is not trivial) and fill the binding set correctly. + +### 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. + +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. + +### 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/_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/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/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-directory.md b/docs/docs/get-started/knowledge-directory.md new file mode 100644 index 00000000..54f58918 --- /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.3.0.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 3f373365..f130241f 100644 --- a/docs/docs/get-started/knowledge-interactions.md +++ b/docs/docs/get-started/knowledge-interactions.md @@ -1,32 +1,82 @@ --- +sidebar_position: 7 --- 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? +> You only need to register your Knowledge Interactions once. +> They are, however, dynamic and can be added and removed if needed. + ```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 +100,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/get-started/smart-connector.md b/docs/docs/get-started/smart-connector.md index 04ffb75a..945e0fca 100644 --- a/docs/docs/get-started/smart-connector.md +++ b/docs/docs/get-started/smart-connector.md @@ -5,12 +5,49 @@ 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.3.0/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.3.0/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.* + +> Typically, you start a single Smart Connector which should be available as long as your system (Knowledge Base) is available. -## How to instantiate a Smart Connector? +Assuming `this` is your knowledge base, you can make a `SmartConnector` as follows: + ```java SmartConnector sc = SmartConnectorBuilder.newSmartConnector(this).create(); ``` @@ -25,15 +62,18 @@ 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 +java -Dorg.slf4j.simpleLogger.logFile=ke.log -cp "smart-connector-rest-dist-1.3.0.jar:dependency/*" eu.knowledge.engine.rest.Main 8280 ``` - -## 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 +## 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/getting_started.md similarity index 63% rename from docs/docs/java_developer_api.md rename to docs/docs/getting_started.md index a8928b5e..eab82efb 100644 --- a/docs/docs/java_developer_api.md +++ b/docs/docs/getting_started.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,16 +103,26 @@ 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. -## Expressibility +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. + +## 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 . @@ -126,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 . @@ -134,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 [ @@ -155,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 . @@ -187,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 [ 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 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 00000000..71d79a66 Binary files /dev/null and b/docs/static/img/graph-pattern-pitfall.png differ diff --git a/examples/authentication/docker-compose.yml b/examples/authentication/docker-compose.yml index 0c80a697..d45781eb 100644 --- a/examples/authentication/docker-compose.yml +++ b/examples/authentication/docker-compose.yml @@ -9,15 +9,15 @@ services: depends_on: - knowledge-directory knowledge-directory: - image: ghcr.io/tno/knowledge-engine/knowledge-directory:1.2.5 + image: ghcr.io/tno/knowledge-engine/knowledge-directory:1.3.0 runtime-1: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 environment: KE_RUNTIME_PORT: 8081 # The port that the KE uses to listen for inter-KE-runtime communication. KD_URL: http://thisisausername:thisisapassword@nginx/kd KE_RUNTIME_EXPOSED_URL: http://thisisausername:thisisapassword@nginx/ker1 runtime-2: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 environment: KE_RUNTIME_PORT: 8081 # The port that the KE uses to listen for inter-KE-runtime communication. KD_URL: http://thisisausername:thisisapassword@nginx/kd diff --git a/examples/java-api/pom.xml b/examples/java-api/pom.xml index e1a8588f..b4394b48 100644 --- a/examples/java-api/pom.xml +++ b/examples/java-api/pom.xml @@ -1,5 +1,6 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 examples jar @@ -11,7 +12,8 @@ ../.. - A Knowledge Base that shares power measurements of an EEBUS submeter. + A Knowledge Base that shares power measurements of an EEBUS + submeter. @@ -43,20 +45,17 @@ org.slf4j slf4j-simple - 2.0.13 org.junit.jupiter junit-jupiter-api - 5.11.3 test org.junit.jupiter junit-jupiter-engine - 5.11.3 test diff --git a/examples/multiple-runtimes/docker-compose.yml b/examples/multiple-runtimes/docker-compose.yml index 9ad39497..f11eec0b 100644 --- a/examples/multiple-runtimes/docker-compose.yml +++ b/examples/multiple-runtimes/docker-compose.yml @@ -2,25 +2,25 @@ services: # This is the knowledge directory, facilitating discovery between different # runtimes. It exposes its service over port 8282. knowledge-directory: - image: ghcr.io/tno/knowledge-engine/knowledge-directory:1.2.5 + image: ghcr.io/tno/knowledge-engine/knowledge-directory:1.3.0 # These services are seperate Knowledge Engine runtime, which can host # multiple smart connectors. Note that the REST API port is a DIFFERENT port # number than the ones configured below. It is still the default 8280. runtime-1: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 environment: KE_RUNTIME_PORT: 8081 # The port that the KE uses to listen for inter-KE-runtime communication. KE_RUNTIME_EXPOSED_URL: http://runtime-1:8081 # The URL where the runtime is available for inter-runtime communication from the outside. KD_URL: http://knowledge-directory:8282 runtime-2: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 environment: KE_RUNTIME_PORT: 8081 KE_RUNTIME_EXPOSED_URL: http://runtime-2:8081 KD_URL: http://knowledge-directory:8282 runtime-3: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 environment: KE_RUNTIME_PORT: 8081 KE_RUNTIME_EXPOSED_URL: http://runtime-3:8081 diff --git a/examples/reasoner/docker-compose.yml b/examples/reasoner/docker-compose.yml index fc23e7bb..00e6449d 100644 --- a/examples/reasoner/docker-compose.yml +++ b/examples/reasoner/docker-compose.yml @@ -1,6 +1,6 @@ services: knowledge-engine: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 kb1: build: ../common/asking_kb environment: diff --git a/examples/rest-api/docker-compose.yml b/examples/rest-api/docker-compose.yml index 20783f8f..acb337e4 100644 --- a/examples/rest-api/docker-compose.yml +++ b/examples/rest-api/docker-compose.yml @@ -1,8 +1,8 @@ services: knowledge-engine: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 healthcheck: - test: curl -f http://localhost:8280/rest/sc + test: wget http://localhost:8280/rest/sc -O /dev/null interval: 1s sensor: build: diff --git a/examples/runtime-tests/docker-compose.yml b/examples/runtime-tests/docker-compose.yml index 4f686f08..e904da21 100644 --- a/examples/runtime-tests/docker-compose.yml +++ b/examples/runtime-tests/docker-compose.yml @@ -2,25 +2,25 @@ services: # This is the knowledge directory, facilitating discovery between different # runtimes. It exposes its service over port 8282. knowledge-directory: - image: ghcr.io/tno/knowledge-engine/knowledge-directory:1.2.5 + image: ghcr.io/tno/knowledge-engine/knowledge-directory:1.3.0 # These services are seperate Knowledge Engine runtime, which can host # multiple smart connectors. Note that the REST API port is a DIFFERENT port # number than the ones configured below. It is still the default 8280. runtime-1: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 environment: KE_RUNTIME_PORT: 8081 # The port that the KE uses to listen for inter-KE-runtime communication. KE_RUNTIME_EXPOSED_URL: http://runtime-1:8081 # The URL where the runtime is available for inter-runtime communication from the outside. KD_URL: http://knowledge-directory:8282 runtime-2: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 environment: KE_RUNTIME_PORT: 8081 KE_RUNTIME_EXPOSED_URL: http://runtime-2:8081 KD_URL: http://knowledge-directory:8282 runtime-3: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 environment: KE_RUNTIME_PORT: 8081 KE_RUNTIME_EXPOSED_URL: http://runtime-3:8081 @@ -83,7 +83,7 @@ services: } ] broken-1: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 environment: KE_RUNTIME_PORT: 8081 # The port that the KE uses to listen for inter-KE-runtime communication. KE_RUNTIME_EXPOSED_URL: runtime-3:8081 # The 'http://' part of the URL with a port is missing. @@ -102,7 +102,7 @@ services: KB_DATA: | [] broken-2: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 environment: KE_RUNTIME_PORT: 8081 # The port that the KE uses to listen for inter-KE-runtime communication. KE_RUNTIME_EXPOSED_URL: 3test.runtime.nl:8081 # The 'http://' part of the URL with a port is missing and the first character is numeric. @@ -121,7 +121,7 @@ services: KB_DATA: | [] broken-3: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 environment: KE_RUNTIME_PORT: 8081 # The port that the KE uses to listen for inter-KE-runtime communication. KE_RUNTIME_EXPOSED_URL: 3test.runtime.nl # The 'http://' part of the URL without a port is missing and the first character is numeric. @@ -140,7 +140,7 @@ services: KB_DATA: | [] broken-4: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 environment: KE_RUNTIME_PORT: 8081 # The port that the KE uses to listen for inter-KE-runtime communication. KE_RUNTIME_EXPOSED_URL: http://runtime-3:8081/ # The URL ends with a '/' @@ -159,7 +159,7 @@ services: KB_DATA: | [] broken-5: - image: ghcr.io/tno/knowledge-engine/smart-connector:1.2.5 + image: ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 environment: KE_RUNTIME_PORT: 8081 # The port that the KE uses to listen for inter-KE-runtime communication. KE_RUNTIME_EXPOSED_URL: runtime-3 # The 'http://' part of the URL without a port is missing. diff --git a/examples/unreachable-runtimes/Dockerfile b/examples/unreachable-runtimes/Dockerfile index d1360f48..0d87a94a 100644 --- a/examples/unreachable-runtimes/Dockerfile +++ b/examples/unreachable-runtimes/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/tno/knowledge-engine/smart-connector:1.2.4 +FROM ghcr.io/tno/knowledge-engine/smart-connector:1.3.0 USER root diff --git a/examples/unreachable-runtimes/docker-compose.yml b/examples/unreachable-runtimes/docker-compose.yml index 35350aae..e10187b3 100644 --- a/examples/unreachable-runtimes/docker-compose.yml +++ b/examples/unreachable-runtimes/docker-compose.yml @@ -2,7 +2,7 @@ services: # This is the knowledge directory, facilitating discovery between different # runtimes. It exposes its service over port 8282. knowledge-directory: - image: ghcr.io/tno/knowledge-engine/knowledge-directory:1.2.5 + image: ghcr.io/tno/knowledge-engine/knowledge-directory:1.3.0 ports: - "8282:8282" diff --git a/knowledge-directory/pom.xml b/knowledge-directory/pom.xml index 4f72a41c..f4393163 100644 --- a/knowledge-directory/pom.xml +++ b/knowledge-directory/pom.xml @@ -14,13 +14,8 @@ - 2.2.22 + 2.2.27 3.1.0 - 11.0.15 - 3.1.9 - 2.18.1 - 4.13.1 - 1.4.14 UTF-8 @@ -36,54 +31,40 @@ swagger-jaxrs2-servlet-initializer-v2-jakarta ${swagger-core-version} - - ch.qos.logback - logback-core - ${logback-version} - compile - org.junit.jupiter junit-jupiter-api - 5.11.3 test org.junit.jupiter junit-jupiter-engine - 5.11.3 test org.junit.jupiter junit-jupiter - 5.11.3 test org.glassfish.jersey.containers jersey-container-servlet-core - ${jersey3-version} org.glassfish.jersey.inject jersey-hk2 - ${jersey3-version} org.glassfish.jersey.media jersey-media-multipart - ${jersey3-version} com.fasterxml.jackson.datatype jackson-datatype-joda - ${jackson-version} com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider - ${jackson-version} com.fasterxml.jackson.core @@ -124,32 +105,29 @@ org.slf4j slf4j-simple - 2.0.13 + org.eclipse.jetty jetty-util - ${jetty-version} org.eclipse.jetty jetty-servlet - ${jetty-version} org.eclipse.jetty jetty-server - ${jetty-version} jakarta.xml.bind jakarta.xml.bind-api - 4.0.1 + 4.0.2 @@ -159,7 +137,7 @@ org.openapitools openapi-generator-maven-plugin - 7.9.0 + 7.10.0 generate-sources diff --git a/pom.xml b/pom.xml index a14ab417..04f0cdf6 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,8 @@ ke-parent pom ${revision} - This artifact is a suite of tools for orchestrating semantic data exchange through a knowledge network. + This artifact is a suite of tools for orchestrating semantic + data exchange through a knowledge network. smart-connector @@ -71,20 +72,98 @@ + 11.0.24 17 + 3.1.9 + 2.18.2 17 17 - 1.2.6-SNAPSHOT + 1.3.1-SNAPSHOT + + + org.eclipse.jetty + jetty-util + ${jetty-version} + + + + org.eclipse.jetty + jetty-servlet + ${jetty-version} + + + + org.eclipse.jetty + jetty-server + ${jetty-version} + + + + org.glassfish.jersey.containers + jersey-container-servlet-core + ${jersey3-version} + + + org.glassfish.jersey.inject + jersey-hk2 + ${jersey3-version} + + + org.glassfish.jersey.media + jersey-media-multipart + ${jersey3-version} + + + org.glassfish.jersey.containers + jersey-container-servlet + ${jersey3-version} + + + + com.fasterxml.jackson.datatype + jackson-datatype-joda + ${jackson-version} + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + ${jackson-version} + + + + + + org.junit.jupiter + junit-jupiter-api + 5.11.4 + + + org.junit.jupiter + junit-jupiter-engine + 5.11.3 + + + org.junit.jupiter + junit-jupiter + 5.11.3 + org.apache.jena apache-jena-libs 5.2.0 pom + + + + org.slf4j + slf4j-simple + 2.0.16 + diff --git a/reasoner/pom.xml b/reasoner/pom.xml index 355254c5..1cfbddf1 100644 --- a/reasoner/pom.xml +++ b/reasoner/pom.xml @@ -19,27 +19,23 @@ org.junit.jupiter junit-jupiter-api - 5.11.3 test org.junit.jupiter junit-jupiter-engine - 5.11.3 test org.junit.jupiter junit-jupiter - 5.11.3 test - + org.slf4j slf4j-simple - 2.0.13 diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/RuleNode.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/RuleNode.java index 5d34df42..c4282860 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/RuleNode.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/RuleNode.java @@ -81,7 +81,7 @@ public Map> findAntecedentCoverage(Map> entry : someAntecedentNeighbors.entrySet()) { for (Match m : entry.getValue()) { - if (m.getMatchingPatterns().keySet().contains(tp)) { + if (m.getMatchingPatterns().values().contains(tp)) { coveringNodes.add(entry.getKey()); break; // where does this break from? The inner loop. } diff --git a/smart-connector-api/pom.xml b/smart-connector-api/pom.xml index 545faa38..365a8cb2 100644 --- a/smart-connector-api/pom.xml +++ b/smart-connector-api/pom.xml @@ -15,7 +15,6 @@ org.slf4j slf4j-simple - 2.0.13 eu.knowledge.engine diff --git a/smart-connector-rest-dist/pom.xml b/smart-connector-rest-dist/pom.xml index 6de1e16e..d5b4f2cf 100644 --- a/smart-connector-rest-dist/pom.xml +++ b/smart-connector-rest-dist/pom.xml @@ -25,19 +25,16 @@ org.junit.jupiter junit-jupiter-api - 5.11.3 test org.junit.jupiter junit-jupiter-engine - 5.11.3 test org.junit.jupiter junit-jupiter - 5.11.3 test @@ -47,7 +44,8 @@ org.apache.maven.plugins maven-surefire-plugin - false + false diff --git a/smart-connector-rest-server/pom.xml b/smart-connector-rest-server/pom.xml index 567c84d1..83f39540 100644 --- a/smart-connector-rest-server/pom.xml +++ b/smart-connector-rest-server/pom.xml @@ -11,10 +11,7 @@ - 2.2.22 - 11.0.15 - 3.1.9 - 2.18.1 + 2.2.27 UTF-8 @@ -34,30 +31,25 @@ com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider - ${jackson-version} org.slf4j slf4j-simple - 2.0.13 org.eclipse.jetty jetty-util - ${jetty-version} org.eclipse.jetty jetty-servlet - ${jetty-version} org.eclipse.jetty jetty-server - ${jetty-version} @@ -65,29 +57,25 @@ org.glassfish.jersey.containers jersey-container-servlet-core - ${jersey3-version} org.glassfish.jersey.inject jersey-hk2 - ${jersey3-version} org.glassfish.jersey.media jersey-media-multipart - ${jersey3-version} org.glassfish.jersey.containers jersey-container-servlet - ${jersey3-version} jakarta.xml.bind jakarta.xml.bind-api - 4.0.1 + 4.0.2 @@ -120,19 +108,16 @@ org.junit.jupiter junit-jupiter-api - 5.11.3 test org.junit.jupiter junit-jupiter-engine - 5.11.3 test org.junit.jupiter junit-jupiter - 5.11.3 test @@ -140,7 +125,7 @@ org.apache.maven maven-model - 3.9.4 + 3.9.9 @@ -149,7 +134,7 @@ org.openapitools openapi-generator-maven-plugin - 7.9.0 + 7.10.0 generate-sources 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 60663eb9..6beffb1e 100644 --- a/smart-connector-rest-server/src/main/resources/openapi-sc.yaml +++ b/smart-connector-rest-server/src/main/resources/openapi-sc.yaml @@ -7,7 +7,7 @@ info: Interactions are (un)registered and data is exchanged. Each Smart Connector is coupled with a Knowledge Base Id, so every rest call uses this Knowledge Base Id to identify yourself. - version: 1.2.6-SNAPSHOT + version: 1.3.1-SNAPSHOT paths: /sc: diff --git a/smart-connector/pom.xml b/smart-connector/pom.xml index ad9af2e0..48f67341 100644 --- a/smart-connector/pom.xml +++ b/smart-connector/pom.xml @@ -25,28 +25,43 @@ org.slf4j slf4j-simple - 2.0.13 org.junit.jupiter junit-jupiter-api - 5.11.3 test org.junit.jupiter junit-jupiter-engine - 5.11.3 test org.junit.jupiter junit-jupiter - 5.11.3 test + + org.mockito + mockito-core + 5.14.2 + test + + + org.mockito + mockito-junit-jupiter + 5.14.2 + test + + + org.wiremock + wiremock + 3.10.0 + test + + @@ -95,12 +110,10 @@ com.fasterxml.jackson.datatype jackson-datatype-joda - ${jackson-version} com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider - ${jackson-version} jakarta.xml.bind @@ -128,31 +141,26 @@ org.glassfish.jersey.containers jersey-container-servlet-core - ${jersey3-version} org.glassfish.jersey.inject jersey-hk2 - ${jersey3-version} org.eclipse.jetty jetty-util - ${jetty-version} org.eclipse.jetty jetty-servlet - ${jetty-version} org.eclipse.jetty jetty-server - ${jetty-version} @@ -189,14 +197,22 @@ + + org.eclipse.microprofile.config + microprofile-config-api + ${version.eclipse.microprofile.config} + + + io.smallrye.config + smallrye-config + 3.10.1 + - 2.2.22 - 11.0.15 - 3.1.9 - 2.18.1 + 2.2.27 6.1.0 + 3.1 @@ -204,7 +220,7 @@ org.openapitools openapi-generator-maven-plugin - 7.9.0 + 7.10.0 diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/InteractionProcessorImpl.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/InteractionProcessorImpl.java index d6ca242e..f48dde3a 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/InteractionProcessorImpl.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/InteractionProcessorImpl.java @@ -28,6 +28,7 @@ import org.apache.jena.sparql.syntax.ElementData; import org.apache.jena.sparql.syntax.ElementGroup; import org.apache.jena.vocabulary.RDF; +import org.eclipse.microprofile.config.ConfigProvider; import org.slf4j.Logger; import eu.knowledge.engine.reasoner.Rule; @@ -76,8 +77,6 @@ public class InteractionProcessorImpl implements InteractionProcessor { */ private boolean reasonerEnabled = false; - private static boolean VALIDATE_OUTGOING_BINDINGS_WRT_INCOMING_BINDINGS_DEFAULT = true; - private static final Query query = QueryFactory.create( "ASK WHERE { ?req ?someClass . FILTER NOT EXISTS {?sat ?someClass .} VALUES (?req ?sat) {} }"); @@ -388,9 +387,8 @@ public CompletableFuture processPostFromMessageRouter(PostMessage } private boolean shouldValidateInputOutputBindings() { - return SmartConnectorConfig.getBoolean( - SmartConnectorConfig.CONF_KEY_VALIDATE_OUTGOING_BINDINGS_WRT_INCOMING_BINDINGS, - VALIDATE_OUTGOING_BINDINGS_WRT_INCOMING_BINDINGS_DEFAULT); + return ConfigProvider.getConfig().getValue( + SmartConnectorConfig.CONF_KEY_VALIDATE_OUTGOING_BINDINGS_WRT_INCOMING_BINDINGS, Boolean.class); } @Override diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/MessageRouterImpl.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/MessageRouterImpl.java index e4114a92..5422e5b0 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/MessageRouterImpl.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/MessageRouterImpl.java @@ -10,6 +10,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.eclipse.microprofile.config.ConfigProvider; import org.slf4j.Logger; import eu.knowledge.engine.smartconnector.messaging.AnswerMessage; @@ -30,14 +31,6 @@ public class MessageRouterImpl implements MessageRouter, SmartConnectorEndpoint */ private static final int MAX_ENTRIES = 5000; - /** - * How many seconds should the MessageRouter wait for ANSWER/REACT Message when - * sending a ASK/POST Message? 0 means wait forever (useful when working with a - * human KB) - */ - private static final String CONF_KEY_WAIT_TIMEOUT = "KE_KB_WAIT_TIMEOUT"; - private static final int DEFAULT_WAIT_TIMEOUT = 10; - private final SmartConnectorImpl smartConnector; private final Map> openAskMessages = Collections .synchronizedMap(new LinkedHashMap>() { @@ -87,7 +80,7 @@ public MessageRouterImpl(SmartConnectorImpl smartConnector) { } private int getWaitTimeout() { - return Integer.parseInt(this.getConfigProperty(CONF_KEY_WAIT_TIMEOUT, Integer.toString(DEFAULT_WAIT_TIMEOUT))); + return ConfigProvider.getConfig().getValue(SmartConnectorConfig.CONF_KEY_KE_KB_WAIT_TIMEOUT, Integer.class); } @Override @@ -286,21 +279,6 @@ public URI getKnowledgeBaseId() { return this.smartConnector.getKnowledgeBaseId(); } - public String getConfigProperty(String key, String defaultValue) { - // We might replace this with something a bit more fancy in the future... - String value = System.getenv(key); - if (value == null) { - value = defaultValue; - LOG.trace("No value for the configuration parameter '{}' was provided, using the default value '{}'", key, - defaultValue); - } - return value; - } - - public boolean hasConfigProperty(String key) { - return System.getenv(key) != null; - } - @Override public void setMessageDispatcher(MessageDispatcherEndpoint messageDispatcherEndpoint) { assert this.messageDispatcherEndpoint == null; 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 5640c5e5..b7ee805c 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,15 +2,13 @@ import java.io.IOException; import java.time.Instant; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import eu.knowledge.engine.reasoner.api.TripleNode; import org.apache.jena.graph.Node; import org.apache.jena.sparql.core.TriplePath; import org.apache.jena.sparql.core.Var; @@ -711,40 +709,83 @@ public Set getKnowledgeGaps(RuleNode plan) { } 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 (foundNeighborWithoutGap) continue; - if (collectedOrGaps.isEmpty()) { - KnowledgeGap kg = new KnowledgeGap(); - kg.add(entry.getKey()); - collectedOrGaps.add(kg); - } - LOG.debug("CollectedOrGaps is {}", collectedOrGaps); - - Set newExistingOrGaps = new HashSet(); - if (existingOrGaps.isEmpty()) { - existingOrGaps.addAll(collectedOrGaps); - LOG.debug("Added collectedOrGaps to existingOrGaps"); - } else { - KnowledgeGap newGap; - for (KnowledgeGap existingOrGap : existingOrGaps) { - for (KnowledgeGap collectedOrGap : collectedOrGaps) { - newGap = new KnowledgeGap(); - newGap.addAll(existingOrGap); - newGap.addAll(collectedOrGap); - LOG.debug("Found newGap {}", newGap); - newExistingOrGaps.add(newGap); - } - } - existingOrGaps = newExistingOrGaps; - } + // there is a gap here, either in the current node or in a neighbor. + if (collectedOrGaps.isEmpty()) { + KnowledgeGap kg = new KnowledgeGap(); + kg.add(entry.getKey()); + collectedOrGaps.add(kg); } + LOG.debug("CollectedOrGaps is {}", collectedOrGaps); + + existingOrGaps = mergeGaps(existingOrGaps, collectedOrGaps); } LOG.debug("Found existingOrGaps {}", existingOrGaps); return existingOrGaps; } + private static TripleMatchType getTripleMatchType(TriplePattern existingTriple, TriplePattern newTriple) { + Map matches = existingTriple.findMatches(newTriple); + if (matches == null) { + return TripleMatchType.ADD_TRIPLE; + } + + ArrayList matchType = new ArrayList<>(); + for (Entry match : matches.entrySet()) { + if (match.getKey().node.isVariable() && match.getValue().node.isConcrete()) { + matchType.add(TripleMatchType.REPLACE_TRIPLE); + } else if (match.getKey().node.isConcrete() && match.getValue().node.isVariable()) { + matchType.add(TripleMatchType.IGNORE_TRIPLE); + } + } + + if (matchType.isEmpty()) return TripleMatchType.IGNORE_TRIPLE; + + boolean equalMatchTypes = matchType.stream().allMatch(m -> m.equals(matchType.get(0))); + return equalMatchTypes ? matchType.get(0) : TripleMatchType.ADD_TRIPLE; + } + + private static KnowledgeGap mergeGap(KnowledgeGap gap, KnowledgeGap gapToAdd) { + KnowledgeGap result = new KnowledgeGap(gap); + for (TriplePattern tripleToAdd : gapToAdd) { + Map tripleActions = new HashMap<>(); + for (TriplePattern existingTriple : gap) { + TripleMatchType tripleAction = getTripleMatchType(existingTriple, tripleToAdd); + tripleActions.put(existingTriple, tripleAction); + } + Set> replaces = tripleActions.entrySet().stream().filter(t -> t.getValue() == TripleMatchType.REPLACE_TRIPLE).collect(Collectors.toSet()); + + if (replaces.size() == 1) { + TriplePattern toReplace = replaces.iterator().next().getKey(); + result.remove(toReplace); + result.add(tripleToAdd); + } else if (!tripleActions.values().contains(TripleMatchType.IGNORE_TRIPLE)) { + result.add(tripleToAdd); + } + } + return result; + } + + private static Set mergeGaps(Set listOfGaps, Set gapsToAdd) { + if (listOfGaps.isEmpty()) { + return gapsToAdd; + } else if (gapsToAdd.isEmpty()) { + return listOfGaps; + } + + Set knowledgeGaps = new HashSet<>(); + for (KnowledgeGap existingGap : listOfGaps) { + for (KnowledgeGap gapToAdd : gapsToAdd) { + KnowledgeGap g = mergeGap(existingGap, gapToAdd); + knowledgeGaps.add(g); + } + } + + return knowledgeGaps; + } + private boolean isMetaKI(RuleNode neighbor) { assert neighbor.getRule() instanceof Rule; diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorConfig.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorConfig.java index a261960c..7f18b2a2 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorConfig.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/SmartConnectorConfig.java @@ -1,14 +1,53 @@ package eu.knowledge.engine.smartconnector.impl; public class SmartConnectorConfig { - public static final String CONF_KEY_VALIDATE_OUTGOING_BINDINGS_WRT_INCOMING_BINDINGS = "SC_VALIDATE_OUTGOING_BINDINGS_WRT_INCOMING_BINDINGS"; - - public static boolean getBoolean(String key, boolean defaultValue) { - String valueString = System.getenv(key); - if (valueString == null) { - return defaultValue; - } else { - return Boolean.parseBoolean(valueString); - } - } + + /** + * Key to set whether this KER should strictly validate whether outgoing + * bindings are compatible with incoming bindings. + */ + public static final String CONF_KEY_VALIDATE_OUTGOING_BINDINGS_WRT_INCOMING_BINDINGS = "sc.validate.outgoing.bindings.wrt.incoming.bindings"; + + /** + * Key to configure the hostname of the machine this Knowledge Engine Runtime + * (KER) runs on. + * + * @deprecated Replaced by + * {@link SmartConnectorConfig#CONF_KEY_KE_RUNTIME_EXPOSED_URL} + */ + @Deprecated + public static final String CONF_KEY_KE_RUNTIME_HOSTNAME = "ke.runtime.hostname"; + + /** + * Key to configure the URL of the Knowledge Directory where this KER can find + * other KERs in the network. Note that overriding this configuration property + * will run this KER in distributed mode. + */ + public static final String CONF_KEY_KD_URL = "kd.url"; + + /** + * Key to configure the time in seconds the SCs in this KER wait for a HTTP + * connection response from another KER. Only used in distributed mode. + */ + public static final String CONF_KEY_KE_HTTP_TIMEOUT = "ke.http.timeout"; + + /** + * Key to configure the how many seconds the MessageRouter should wait for + * ANSWER/REACT Message when sending a ASK/POST Message? 0 means wait forever + * (useful when working with a human KB). + */ + public static final String CONF_KEY_KE_KB_WAIT_TIMEOUT = "ke.kb.wait.timeout"; + + /** + * Key to configure the URL that is advertised to other KERs. Other KERs can use + * this URL to reach this KER. Note that this configuration property is only + * used in distributed mode. + */ + public static final String CONF_KEY_KE_RUNTIME_EXPOSED_URL = "ke.runtime.exposed.url"; + + /** + * Key to configure the port at which the KER's peer-to-peer communication + * should happen. Only used in distributed mode. + */ + public static final String CONF_KEY_KE_RUNTIME_PORT = "ke.runtime.port"; } diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/TripleMatchType.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/TripleMatchType.java new file mode 100644 index 00000000..dcfced32 --- /dev/null +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/TripleMatchType.java @@ -0,0 +1,5 @@ +package eu.knowledge.engine.smartconnector.impl; + +public enum TripleMatchType { + ADD_TRIPLE, IGNORE_TRIPLE, REPLACE_TRIPLE, ADD_GAP +} diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/runtime/KeRuntime.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/runtime/KeRuntime.java index b308f4ec..1eec0ea6 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/runtime/KeRuntime.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/runtime/KeRuntime.java @@ -8,9 +8,13 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.ConfigValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import eu.knowledge.engine.smartconnector.impl.SmartConnectorConfig; import eu.knowledge.engine.smartconnector.runtime.messaging.MessageDispatcher; /** @@ -20,14 +24,6 @@ * component of the Knowledge Engine. */ public class KeRuntime { - - private static final String CONF_KEY_MY_HOSTNAME = "KE_RUNTIME_HOSTNAME"; - private static final String CONF_KEY_MY_PORT = "KE_RUNTIME_PORT"; - private static final String CONF_KEY_KD_URL = "KD_URL"; - private static final String CONF_KEY_MY_EXPOSED_URL = "KE_RUNTIME_EXPOSED_URL"; - - private static final String EXPOSED_URL_DEFAULT_PROTOCOL = "http"; - private static final Logger LOG = LoggerFactory.getLogger(KeRuntime.class); private static LocalSmartConnectorRegistry localSmartConnectorRegistry = new LocalSmartConnectorRegistryImpl(); @@ -35,31 +31,38 @@ public class KeRuntime { private static MessageDispatcher messageDispatcher = null; static { - if (hasConfigProperty(CONF_KEY_MY_EXPOSED_URL) && hasConfigProperty(CONF_KEY_MY_HOSTNAME)) { - LOG.error("KE runtime must be configured with {} or {}, not both.", CONF_KEY_MY_EXPOSED_URL, - CONF_KEY_MY_HOSTNAME); + + Config config = ConfigProvider.getConfig(); + ConfigValue exposedUrl = config.getConfigValue(SmartConnectorConfig.CONF_KEY_KE_RUNTIME_EXPOSED_URL); + ConfigValue hostname = config.getConfigValue(SmartConnectorConfig.CONF_KEY_KE_RUNTIME_HOSTNAME); + + // Using MicroProfile Config's source ordinal to determine if default + // configuration got overridden? + if (exposedUrl.getSourceOrdinal() > 100 && hostname.getSourceOrdinal() > 100) { + LOG.error("KE runtime must be configured with {} or {}, not both.", + SmartConnectorConfig.CONF_KEY_KE_RUNTIME_EXPOSED_URL, + SmartConnectorConfig.CONF_KEY_KE_RUNTIME_HOSTNAME); LOG.info("Using {} allows the use of a reverse proxy for TLS connections, which is recommended.", - CONF_KEY_MY_EXPOSED_URL); + SmartConnectorConfig.CONF_KEY_KE_RUNTIME_EXPOSED_URL); System.exit(1); } // execute some validation on the EXPOSED URL, because it can have severe // consequences - String url = getConfigProperty(CONF_KEY_MY_EXPOSED_URL, null); - if (url != null) { + if (exposedUrl.getSourceOrdinal() > 100) { + String url = exposedUrl.getValue(); if (url.endsWith("/")) { LOG.error( "The '{}' environment variable's value '{}' should be a valid URL without a slash ('/') as the last character.", - CONF_KEY_MY_EXPOSED_URL, url); + SmartConnectorConfig.CONF_KEY_KE_RUNTIME_EXPOSED_URL, url); System.exit(1); } try { - URL exposedUrl = new URL(url); + new URL(url); } catch (MalformedURLException e) { - LOG.error( - "The '{}' environment variable with value '{}' contains a malformed URL '{}'.", - CONF_KEY_MY_EXPOSED_URL, url, e.getMessage()); + LOG.error("The '{}' environment variable with value '{}' contains a malformed URL '{}'.", + SmartConnectorConfig.CONF_KEY_KE_RUNTIME_EXPOSED_URL, url, e.getMessage()); System.exit(1); } } @@ -96,27 +99,25 @@ public static ScheduledExecutorService executorService() { } public static MessageDispatcher getMessageDispatcher() { + + Config config = ConfigProvider.getConfig(); + if (messageDispatcher == null) { try { - if (!hasConfigProperty(CONF_KEY_KD_URL)) { + ConfigValue kdUrl = config.getConfigValue(SmartConnectorConfig.CONF_KEY_KD_URL); + if (kdUrl.getSourceOrdinal() == 100) { LOG.warn( "No configuration provided for Knowledge Directory, starting Knowledge Engine in local mode"); messageDispatcher = new MessageDispatcher(); } else { - var myHostname = getConfigProperty(CONF_KEY_MY_HOSTNAME, "localhost"); - var myPort = Integer.parseInt(getConfigProperty(CONF_KEY_MY_PORT, "8081")); - - URI myExposedUrl; - if (hasConfigProperty(CONF_KEY_MY_EXPOSED_URL)) { - myExposedUrl = new URI(getConfigProperty(CONF_KEY_MY_EXPOSED_URL, null)); - } else { - // If no exposed URL config is given we build one based on the - // configured host and port. - myExposedUrl = new URI(EXPOSED_URL_DEFAULT_PROTOCOL + "://" + myHostname + ":" + myPort); - } - - messageDispatcher = new MessageDispatcher(myPort, myExposedUrl, - new URI(getConfigProperty(CONF_KEY_KD_URL, "http://localhost:8080"))); + ConfigValue port = config.getConfigValue(SmartConnectorConfig.CONF_KEY_KE_RUNTIME_PORT); + var myPort = Integer.parseInt(port.getValue()); + + ConfigValue exposedUrl = config + .getConfigValue(SmartConnectorConfig.CONF_KEY_KE_RUNTIME_EXPOSED_URL); + URI myExposedUrl = new URI(exposedUrl.getValue()); + + messageDispatcher = new MessageDispatcher(myPort, myExposedUrl, new URI(kdUrl.getValue())); } } catch (NumberFormatException | URISyntaxException e) { LOG.error("Could not parse configuration properties, cannot start Knowledge Engine", e); @@ -131,20 +132,4 @@ public static MessageDispatcher getMessageDispatcher() { } return messageDispatcher; } - - public static String getConfigProperty(String key, String defaultValue) { - // We might replace this with something a bit more fancy in the future... - String value = System.getenv(key); - if (value == null) { - value = defaultValue; - LOG.info("No value for the configuration parameter '" + key + "' was provided, using the default value '" - + defaultValue + "'"); - } - return value; - } - - public static boolean hasConfigProperty(String key) { - return System.getenv(key) != null; - } - } diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/runtime/messaging/RemoteKerConnection.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/runtime/messaging/RemoteKerConnection.java index eb2c0b39..a8da339b 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/runtime/messaging/RemoteKerConnection.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/runtime/messaging/RemoteKerConnection.java @@ -3,7 +3,11 @@ import static eu.knowledge.engine.smartconnector.runtime.messaging.Utils.stripUserInfoFromURI; import java.io.IOException; -import java.net.*; +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublishers; @@ -15,6 +19,7 @@ import java.util.ArrayList; import java.util.List; +import org.eclipse.microprofile.config.ConfigProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,6 +27,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import eu.knowledge.engine.smartconnector.impl.SmartConnectorConfig; import eu.knowledge.engine.smartconnector.messaging.AnswerMessage; import eu.knowledge.engine.smartconnector.messaging.AskMessage; import eu.knowledge.engine.smartconnector.messaging.ErrorMessage; @@ -39,13 +45,6 @@ */ public class RemoteKerConnection { - /** - * How many seconds the HttpClient waits for a HTTP response when sending a HTTP - * request. Default 5 seconds. - */ - private static final String CONF_KEY_HTTP_TIMEOUT = "KE_HTTP_TIMEOUT"; - private static final int DEFAULT_HTTP_TIMEOUT = 5; - public static final Logger LOG = LoggerFactory.getLogger(RemoteKerConnection.class); private final KnowledgeEngineRuntimeConnectionDetails remoteKerConnectionDetails; @@ -98,7 +97,7 @@ protected PasswordAuthentication getPasswordAuthentication() { } private int getHttpTimeout() { - return Integer.parseInt(this.getConfigProperty(CONF_KEY_HTTP_TIMEOUT, Integer.toString(DEFAULT_HTTP_TIMEOUT))); + return ConfigProvider.getConfig().getValue(SmartConnectorConfig.CONF_KEY_KE_HTTP_TIMEOUT, Integer.class); } public URI getRemoteKerUri() { @@ -230,9 +229,9 @@ public void start() { public void stop() { if (this.isAvailable()) { try { - String ker_id = URLEncoder.encode(dispatcher.getMyKnowledgeEngineRuntimeDetails().getRuntimeId(), StandardCharsets.UTF_8); - HttpRequest request = HttpRequest - .newBuilder(new URI(this.remoteKerUri + "/runtimedetails/" + ker_id)) + String ker_id = URLEncoder.encode(dispatcher.getMyKnowledgeEngineRuntimeDetails().getRuntimeId(), + StandardCharsets.UTF_8); + HttpRequest request = HttpRequest.newBuilder(new URI(this.remoteKerUri + "/runtimedetails/" + ker_id)) .header("Content-Type", "application/json").DELETE().build(); HttpResponse response = this.httpClient.send(request, BodyHandlers.ofString()); @@ -357,20 +356,4 @@ private String getPathForMessageType(KnowledgeMessage message) { return null; } } - - public String getConfigProperty(String key, String defaultValue) { - // We might replace this with something a bit more fancy in the future... - String value = System.getenv(key); - if (value == null) { - value = defaultValue; - LOG.trace("No value for the configuration parameter '{}' was provided, using the default value '{}'", key, - defaultValue); - } - return value; - } - - public boolean hasConfigProperty(String key) { - return System.getenv(key) != null; - } - } diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/util/MockedKnowledgeBase.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/util/KnowledgeBaseImpl.java similarity index 95% rename from smart-connector/src/main/java/eu/knowledge/engine/smartconnector/util/MockedKnowledgeBase.java rename to smart-connector/src/main/java/eu/knowledge/engine/smartconnector/util/KnowledgeBaseImpl.java index eb96b473..91501f03 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/util/MockedKnowledgeBase.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/util/KnowledgeBaseImpl.java @@ -46,9 +46,9 @@ import eu.knowledge.engine.smartconnector.impl.SmartConnectorBuilder; import eu.knowledge.engine.smartconnector.impl.Util; -public class MockedKnowledgeBase implements KnowledgeBase { +public class KnowledgeBaseImpl implements KnowledgeBase { - private static final Logger LOG = LoggerFactory.getLogger(MockedKnowledgeBase.class); + private static final Logger LOG = LoggerFactory.getLogger(KnowledgeBaseImpl.class); private final Set registeredAskKIs; private final Map registeredAnswerKIs; @@ -68,7 +68,10 @@ public class MockedKnowledgeBase implements KnowledgeBase { private Set domainKnowledge = new HashSet<>(); private SmartConnector sc; + + protected URI id; protected String name; + protected String description; private Phaser readyPhaser; private CompletableFuture stoppedFuture = new CompletableFuture(); @@ -83,7 +86,13 @@ public class MockedKnowledgeBase implements KnowledgeBase { */ private boolean isThreadSafe = false; - public MockedKnowledgeBase(String aName) { + public KnowledgeBaseImpl(String aName) { + this(null, aName, null); + } + + public KnowledgeBaseImpl(String anId, String aName, String aDescription) { + + assert aName != null; this.registeredAskKIs = ConcurrentHashMap.newKeySet(); this.registeredAnswerKIs = new ConcurrentHashMap<>(); @@ -100,7 +109,28 @@ public MockedKnowledgeBase(String aName) { this.unregisteredPostKIs = ConcurrentHashMap.newKeySet(); this.unregisteredReactKIs = ConcurrentHashMap.newKeySet(); + // name this.name = aName; + + // id + var someId = anId; + if (someId == null) + someId = "https://www.tno.nl/" + this.name; + + URI uri = null; + try { + uri = new URI(someId); + } catch (URISyntaxException e) { + LOG.error("Could not parse the uri.", e); + } + this.id = uri; + + // description + var someDescr = aDescription; + if (someDescr == null) + someDescr = "Description of " + this.name; + + this.description = someDescr; } /** @@ -119,18 +149,12 @@ public void setPhaser(Phaser aReadyPhaser) { @Override public URI getKnowledgeBaseId() { - URI uri = null; - try { - uri = new URI("https://www.tno.nl/" + this.name); - } catch (URISyntaxException e) { - LOG.error("Could not parse the uri.", e); - } - return uri; + return this.id; } @Override public String getKnowledgeBaseDescription() { - return "description of " + this.name; + return this.description; } @Override @@ -161,7 +185,7 @@ public String getKnowledgeBaseName() { return this.name; } - private SmartConnector getSC() { + protected SmartConnector getSC() { return this.sc; } @@ -267,7 +291,7 @@ public Map getReactKnowledgeInteraction } public boolean isUpToDate(AskKnowledgeInteraction askKnowledgeInteraction, - Set someKnowledgeBases) { + Set someKnowledgeBases) { boolean isUpToDate = true; @@ -282,7 +306,7 @@ public boolean isUpToDate(AskKnowledgeInteraction askKnowledgeInteraction, // m.write(System.out, "turtle"); // System.out.println("-----------------------"); - for (MockedKnowledgeBase aKnowledgeBase : someKnowledgeBases) { + for (KnowledgeBaseImpl aKnowledgeBase : someKnowledgeBases) { if (!this.getKnowledgeBaseId().toString().equals(aKnowledgeBase.getKnowledgeBaseId().toString())) { isUpToDate &= isKnowledgeBaseUpToDate(aKnowledgeBase, m); } @@ -296,7 +320,7 @@ public boolean isUpToDate(AskKnowledgeInteraction askKnowledgeInteraction, } - private boolean isKnowledgeBaseUpToDate(MockedKnowledgeBase aMockedKB, Model aModel) { + private boolean isKnowledgeBaseUpToDate(KnowledgeBaseImpl aMockedKB, Model aModel) { boolean isSame = true; Resource kb = ResourceFactory.createResource(aMockedKB.getKnowledgeBaseId().toString()); diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/util/KnowledgeNetwork.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/util/KnowledgeNetwork.java index 810dd1bd..699fbfdd 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/util/KnowledgeNetwork.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/util/KnowledgeNetwork.java @@ -30,8 +30,8 @@ public class KnowledgeNetwork { * ready, Phase 2 = everybody is aware of everybody. */ private Phaser readyPhaser; - private Set knowledgeBases; - private Map knowledgeInteractionMetadata; + private Set knowledgeBases; + private Map knowledgeInteractionMetadata; private PrefixMapping prefixMapping; public KnowledgeNetwork() { @@ -43,7 +43,7 @@ public KnowledgeNetwork() { this.prefixMapping.setNsPrefix("kb", Vocab.ONTO_URI); } - public void addKB(MockedKnowledgeBase aKB) { + public void addKB(KnowledgeBaseImpl aKB) { aKB.setPhaser(this.readyPhaser); knowledgeBases.add(aKB); } @@ -59,9 +59,9 @@ public void sync() { */ private void startAndWaitForReady() { - Set justStartedKBs = new HashSet<>(); + Set justStartedKBs = new HashSet<>(); - for (MockedKnowledgeBase kb : this.knowledgeBases) { + for (KnowledgeBaseImpl kb : this.knowledgeBases) { if (!kb.isStarted()) { kb.start(); justStartedKBs.add(kb); @@ -87,14 +87,14 @@ private void startAndWaitForReady() { // @formatter:on ); - for (MockedKnowledgeBase kb : justStartedKBs) { + for (KnowledgeBaseImpl kb : justStartedKBs) { AskKnowledgeInteraction anAskKI = new AskKnowledgeInteraction(new CommunicativeAct(), gp, null, false, true, false, MatchStrategy.ENTRY_LEVEL); this.knowledgeInteractionMetadata.put(kb, anAskKI); kb.register(anAskKI); } - for (MockedKnowledgeBase kb : this.knowledgeBases) { + for (KnowledgeBaseImpl kb : this.knowledgeBases) { kb.syncKIs(); } } @@ -110,9 +110,9 @@ private void waitForUpToDate() { count++; LOG.info("Checking up to date knowledge bases."); allUpToDate = true; - Map upToDate = new HashMap<>(); + Map upToDate = new HashMap<>(); boolean kbUpToDate; - for (MockedKnowledgeBase kb : this.knowledgeBases) { + for (KnowledgeBaseImpl kb : this.knowledgeBases) { kbUpToDate = kb.isUpToDate(this.knowledgeInteractionMetadata.get(kb), this.knowledgeBases); allUpToDate &= kbUpToDate; upToDate.put(kb, kbUpToDate); @@ -130,11 +130,11 @@ private void waitForUpToDate() { LOG.info("Everyone is up to date after {} rounds!", count); } - private String getUpToDateInfo(Map upToDate) { + private String getUpToDateInfo(Map upToDate) { StringBuilder sb = new StringBuilder(); sb.append("("); - for (Map.Entry entry : upToDate.entrySet()) { + for (Map.Entry entry : upToDate.entrySet()) { sb.append(entry.getKey().getKnowledgeBaseName()).append("=").append(entry.getValue()); sb.append(","); } diff --git a/smart-connector/src/main/resources/META-INF/microprofile-config.properties b/smart-connector/src/main/resources/META-INF/microprofile-config.properties new file mode 100644 index 00000000..ecb1cea5 --- /dev/null +++ b/smart-connector/src/main/resources/META-INF/microprofile-config.properties @@ -0,0 +1,8 @@ +# documentation about these configuration property can be found in the SmartConnectorConfig class +ke.runtime.port = 8081 +ke.runtime.exposed.url = http://${ke.runtime.hostname}:${ke.runtime.port} +ke.kb.wait.timeout = 10 +ke.http.timeout = 5 +kd.url = http://localhost:8080 +sc.validate.outgoing.bindings.wrt.incoming.bindings = true +ke.runtime.hostname = localhost \ No newline at end of file diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/AdditionForAudienceTest.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/AdditionForAudienceTest.java index ca0192c2..cccb7d2a 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/AdditionForAudienceTest.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/AdditionForAudienceTest.java @@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; @Tag("Long") public class AdditionForAudienceTest { @@ -72,12 +72,12 @@ public BindingSet react(ReactKnowledgeInteraction anRKI, ReactExchangeInfo aReac } }; - MockedKnowledgeBase kb1; - MockedKnowledgeBase kb10; - MockedKnowledgeBase kb100; - MockedKnowledgeBase kbSum; - MockedKnowledgeBase kbNum; - MockedKnowledgeBase kbRule; + KnowledgeBaseImpl kb1; + KnowledgeBaseImpl kb10; + KnowledgeBaseImpl kb100; + KnowledgeBaseImpl kbSum; + KnowledgeBaseImpl kbNum; + KnowledgeBaseImpl kbRule; private AskKnowledgeInteraction askKI; private static KnowledgeNetwork kn; @@ -86,7 +86,7 @@ public void beforeAll() { kn = new KnowledgeNetwork(); // kb1 - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kb1.setReasonerEnabled(true); kn.addKB(kb1); @@ -97,7 +97,7 @@ public void beforeAll() { kb1.register(reactKI, new MyReactHandler(1)); // kb10 - kb10 = new MockedKnowledgeBase("kb10"); + kb10 = new KnowledgeBaseImpl("kb10"); kb10.setReasonerEnabled(true); kn.addKB(kb10); reactKI = new ReactKnowledgeInteraction(new CommunicativeAct(), new GraphPattern( @@ -106,7 +106,7 @@ public void beforeAll() { "?e ?n3 . ?n3 . ?n3 ?d3 . ?d3 \"2\" . ?d3 ?ad3 . ?d3 ?o .")); kb10.register(reactKI, new MyReactHandler(2)); - kb100 = new MockedKnowledgeBase("kb100"); + kb100 = new KnowledgeBaseImpl("kb100"); kb100.setReasonerEnabled(true); kn.addKB(kb100); reactKI = new ReactKnowledgeInteraction(new CommunicativeAct(), new GraphPattern( @@ -115,7 +115,7 @@ public void beforeAll() { "?e ?n3 . ?n3 . ?n3 ?d3 . ?d3 \"3\" . ?d3 ?ad3 . ?d3 ?o .")); kb100.register(reactKI, new MyReactHandler(3)); - kbSum = new MockedKnowledgeBase("kbSum"); + kbSum = new KnowledgeBaseImpl("kbSum"); kbSum.setReasonerEnabled(true); kn.addKB(kbSum); @@ -187,7 +187,7 @@ public void beforeAll() { return bs; }); - kbNum = new MockedKnowledgeBase("kbNum"); + kbNum = new KnowledgeBaseImpl("kbNum"); kbNum.setReasonerEnabled(true); kn.addKB(kbNum); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/GeneralizeIfNecessaryTest.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/GeneralizeIfNecessaryTest.java index a899e9e8..38ea10f8 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/GeneralizeIfNecessaryTest.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/GeneralizeIfNecessaryTest.java @@ -16,7 +16,7 @@ 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; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; /** * This test tries to illustrate generalize if necessary. We instantiate @@ -36,10 +36,10 @@ public class GeneralizeIfNecessaryTest { private static final Logger LOG = LoggerFactory.getLogger(GeneralizeIfNecessaryTest.class); private static KnowledgeNetwork kn = new KnowledgeNetwork(); - private MockedKnowledgeBase appKb = new MockedKnowledgeBase("AppKB"); - private MockedKnowledgeBase sensor1Kb = new MockedKnowledgeBase("Sensor1KB"); - private MockedKnowledgeBase sensor2Kb = new MockedKnowledgeBase("Sensor2KB"); - private MockedKnowledgeBase floorplanKb = new MockedKnowledgeBase("FloorplanKB"); + private KnowledgeBaseImpl appKb = new KnowledgeBaseImpl("AppKB"); + private KnowledgeBaseImpl sensor1Kb = new KnowledgeBaseImpl("Sensor1KB"); + private KnowledgeBaseImpl sensor2Kb = new KnowledgeBaseImpl("Sensor2KB"); + private KnowledgeBaseImpl floorplanKb = new KnowledgeBaseImpl("FloorplanKB"); private PrefixMapping prefixes = new PrefixMappingMem().setNsPrefix("ex", "http://example.org/").setNsPrefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/MultiThreadingPerformanceTest.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/MultiThreadingPerformanceTest.java index 338a9e76..474d4272 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/MultiThreadingPerformanceTest.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/MultiThreadingPerformanceTest.java @@ -14,7 +14,7 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; /** * This test tries to balance the number of threads posting to a single KB to @@ -82,8 +82,8 @@ public void run() { private Queue timings = new ConcurrentLinkedQueue(); private KnowledgeNetwork kn = null; - private MockedKnowledgeBase poster = null; - private MockedKnowledgeBase reacter = null; + private KnowledgeBaseImpl poster = null; + private KnowledgeBaseImpl reacter = null; private PostKnowledgeInteraction postKI = null; private ReactKnowledgeInteraction reactKI = null; @@ -151,10 +151,10 @@ public void run() { public void test() throws InterruptedException { kn = new KnowledgeNetwork(); - poster = new MockedKnowledgeBase("poster"); + poster = new KnowledgeBaseImpl("poster"); poster.setIsThreadSafe(true); kn.addKB(poster); - reacter = new MockedKnowledgeBase("reacter"); + reacter = new KnowledgeBaseImpl("reacter"); reacter.setIsThreadSafe(true); kn.addKB(reacter); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/NotDesignedToWorkTogetherTest.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/NotDesignedToWorkTogetherTest.java index c26a2738..b12bc52b 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/NotDesignedToWorkTogetherTest.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/NotDesignedToWorkTogetherTest.java @@ -19,7 +19,7 @@ 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; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; /** * This test tries to illustrate forward compatibility. We instantiate an @@ -37,9 +37,9 @@ public class NotDesignedToWorkTogetherTest { private static final Logger LOG = LoggerFactory.getLogger(NotDesignedToWorkTogetherTest.class); private static KnowledgeNetwork kn = new KnowledgeNetwork(); - private MockedKnowledgeBase appKb = new MockedKnowledgeBase("AppKB"); - private MockedKnowledgeBase lamp1Kb = new MockedKnowledgeBase("Lamp1KB"); - private MockedKnowledgeBase lamp2Kb = new MockedKnowledgeBase("Lamp2KB"); + private KnowledgeBaseImpl appKb = new KnowledgeBaseImpl("AppKB"); + private KnowledgeBaseImpl lamp1Kb = new KnowledgeBaseImpl("Lamp1KB"); + private KnowledgeBaseImpl lamp2Kb = new KnowledgeBaseImpl("Lamp2KB"); private PrefixMapping prefixes = new PrefixMappingMem().setNsPrefix("ex", "http://example.org/") .setNsPrefix("time", "https://www.w3.org/TR/owl-time/") .setNsPrefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/SingleLargeVsMultipleSmallGraphPatternTest.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/SingleLargeVsMultipleSmallGraphPatternTest.java index ee705c47..029e31c6 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/SingleLargeVsMultipleSmallGraphPatternTest.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/SingleLargeVsMultipleSmallGraphPatternTest.java @@ -10,7 +10,7 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; /** * What is faster: @@ -37,10 +37,10 @@ public class SingleLargeVsMultipleSmallGraphPatternTest { public void testSingleLargeGP() throws InterruptedException, ExecutionException { // creating SCs - MockedKnowledgeBase kb1 = new MockedKnowledgeBase("KB1"); + KnowledgeBaseImpl kb1 = new KnowledgeBaseImpl("KB1"); kn1.addKB(kb1); kb1.setReasonerEnabled(true); - MockedKnowledgeBase kb2 = new MockedKnowledgeBase("KB2"); + KnowledgeBaseImpl kb2 = new KnowledgeBaseImpl("KB2"); kn1.addKB(kb2); kb2.setReasonerEnabled(true); @@ -95,10 +95,10 @@ public void testSingleLargeGP() throws InterruptedException, ExecutionException public void testMultipleSmallGPs() throws InterruptedException, ExecutionException { // creating SCs - MockedKnowledgeBase kb1 = new MockedKnowledgeBase("KB1"); + KnowledgeBaseImpl kb1 = new KnowledgeBaseImpl("KB1"); kn2.addKB(kb1); kb1.setReasonerEnabled(true); - MockedKnowledgeBase kb2 = new MockedKnowledgeBase("KB2"); + KnowledgeBaseImpl kb2 = new KnowledgeBaseImpl("KB2"); kn2.addKB(kb2); kb2.setReasonerEnabled(true); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer.java index a04a1ffe..f36a4de2 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer.java @@ -24,14 +24,14 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskAnswer { private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswer.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; @BeforeAll public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { @@ -45,9 +45,9 @@ public void testAskAnswer() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); var kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); LOG.info("Waiting for ready..."); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer2.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer2.java index 810ae36f..ac16d322 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer2.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer2.java @@ -19,14 +19,14 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskAnswer2 { private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswer2.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; @BeforeAll public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { @@ -40,9 +40,9 @@ public void testAskAnswer() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); var kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); LOG.info("Waiting for ready..."); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer3.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer3.java index 0e31fc45..8bab526d 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer3.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer3.java @@ -31,16 +31,16 @@ import eu.knowledge.engine.smartconnector.api.CommunicativeAct; import eu.knowledge.engine.smartconnector.api.GraphPattern; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskAnswer3 { private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswer3.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; - private static MockedKnowledgeBase kb3; - private static MockedKnowledgeBase kb4; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; + private static KnowledgeBaseImpl kb3; + private static KnowledgeBaseImpl kb4; @BeforeAll public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { @@ -54,13 +54,13 @@ public void testAskAnswer() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); var kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); - kb3 = new MockedKnowledgeBase("kb3"); + kb3 = new KnowledgeBaseImpl("kb3"); kn.addKB(kb3); - kb4 = new MockedKnowledgeBase("kb4"); + kb4 = new KnowledgeBaseImpl("kb4"); kn.addKB(kb4); GraphPattern gp1 = new GraphPattern(prefixes, "?a ?c."); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer4.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer4.java index 36cdd3ca..1642d88b 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer4.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer4.java @@ -19,14 +19,14 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskAnswer4 { private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswer4.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; @BeforeAll public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { @@ -40,10 +40,10 @@ public void testAskAnswer() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); var kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kb1.setReasonerEnabled(true); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kb1.setReasonerEnabled(true); kn.addKB(kb2); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer5.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer5.java index 50fc0d9d..c091bee5 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer5.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer5.java @@ -22,7 +22,7 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskAnswer5 { @@ -42,16 +42,16 @@ public void testAskAnswer() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); this.kn = new KnowledgeNetwork(); - MockedKnowledgeBase kb1 = new MockedKnowledgeBase("kb1"); + KnowledgeBaseImpl kb1 = new KnowledgeBaseImpl("kb1"); kb1.setReasonerEnabled(true); kn.addKB(kb1); - MockedKnowledgeBase kb2 = new MockedKnowledgeBase("kb2"); + KnowledgeBaseImpl kb2 = new KnowledgeBaseImpl("kb2"); kb2.setReasonerEnabled(true); kn.addKB(kb2); - MockedKnowledgeBase kb3 = new MockedKnowledgeBase("kb3"); + KnowledgeBaseImpl kb3 = new KnowledgeBaseImpl("kb3"); kb3.setReasonerEnabled(true); kn.addKB(kb3); - MockedKnowledgeBase kb4 = new MockedKnowledgeBase("kb4"); + KnowledgeBaseImpl kb4 = new KnowledgeBaseImpl("kb4"); kb4.setReasonerEnabled(true); kn.addKB(kb4); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerBindingValidity.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerBindingValidity.java index f50ddc28..f2eb1e88 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerBindingValidity.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerBindingValidity.java @@ -18,14 +18,14 @@ import eu.knowledge.engine.smartconnector.api.ExchangeInfo.Status; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskAnswerBindingValidity { private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerBindingValidity.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; @Test public void testAskAnswerInvalidOutgoingBindings() throws InterruptedException { @@ -35,9 +35,9 @@ public void testAskAnswerInvalidOutgoingBindings() throws InterruptedException { prefixes.setNsPrefix("ex", "https://example.org/"); var kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); final AtomicBoolean wasInAnswerHandler = new AtomicBoolean(false); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerManyKIs.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerManyKIs.java index 669fe004..af221847 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerManyKIs.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerManyKIs.java @@ -24,17 +24,17 @@ import org.slf4j.helpers.MessageFormatter; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; @Tag("Long") public class TestAskAnswerManyKIs { private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerManyKIs.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; - private static MockedKnowledgeBase kb3; - private static MockedKnowledgeBase kb4; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; + private static KnowledgeBaseImpl kb3; + private static KnowledgeBaseImpl kb4; private String pattern = "?{} ?{}."; @@ -50,13 +50,13 @@ public void testAskAnswer() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); var kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); - kb3 = new MockedKnowledgeBase("kb3"); + kb3 = new KnowledgeBaseImpl("kb3"); kn.addKB(kb3); - kb4 = new MockedKnowledgeBase("kb4"); + kb4 = new KnowledgeBaseImpl("kb4"); kn.addKB(kb4); int count = 50; diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerRealistic.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerRealistic.java index 735fe386..33a9b958 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerRealistic.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerRealistic.java @@ -23,23 +23,23 @@ 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; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; @Disabled public class TestAskAnswerRealistic { private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerRealistic.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; - private static MockedKnowledgeBase kb3; - private static MockedKnowledgeBase kb4; - private static MockedKnowledgeBase kb5; - private static MockedKnowledgeBase kb6; - private static MockedKnowledgeBase kb7; - private static MockedKnowledgeBase kb8; - private static MockedKnowledgeBase kb9; - private static MockedKnowledgeBase kb10; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; + private static KnowledgeBaseImpl kb3; + private static KnowledgeBaseImpl kb4; + private static KnowledgeBaseImpl kb5; + private static KnowledgeBaseImpl kb6; + private static KnowledgeBaseImpl kb7; + private static KnowledgeBaseImpl kb8; + private static KnowledgeBaseImpl kb9; + private static KnowledgeBaseImpl kb10; @BeforeAll public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { @@ -51,25 +51,25 @@ public void testAskAnswer() throws InterruptedException { var kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kb1.setReasonerEnabled(true); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kb2.setReasonerEnabled(true); - kb3 = new MockedKnowledgeBase("kb3"); + kb3 = new KnowledgeBaseImpl("kb3"); kb3.setReasonerEnabled(true); - kb4 = new MockedKnowledgeBase("kb4"); + kb4 = new KnowledgeBaseImpl("kb4"); kb4.setReasonerEnabled(true); - kb5 = new MockedKnowledgeBase("kb5"); + kb5 = new KnowledgeBaseImpl("kb5"); kb5.setReasonerEnabled(true); - kb6 = new MockedKnowledgeBase("kb6"); + kb6 = new KnowledgeBaseImpl("kb6"); kb6.setReasonerEnabled(true); - kb7 = new MockedKnowledgeBase("kb7"); + kb7 = new KnowledgeBaseImpl("kb7"); kb7.setReasonerEnabled(true); - kb8 = new MockedKnowledgeBase("kb8"); + kb8 = new KnowledgeBaseImpl("kb8"); kb8.setReasonerEnabled(true); - kb9 = new MockedKnowledgeBase("kb9"); + kb9 = new KnowledgeBaseImpl("kb9"); kb9.setReasonerEnabled(true); - kb10 = new MockedKnowledgeBase("kb10"); + kb10 = new KnowledgeBaseImpl("kb10"); kb10.setReasonerEnabled(true); var rules = new HashSet(); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerSingleKBMultipleMatchingKIs.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerSingleKBMultipleMatchingKIs.java index 5c3ccae4..f6513d8b 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerSingleKBMultipleMatchingKIs.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerSingleKBMultipleMatchingKIs.java @@ -32,14 +32,14 @@ import eu.knowledge.engine.smartconnector.api.CommunicativeAct; import eu.knowledge.engine.smartconnector.api.GraphPattern; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskAnswerSingleKBMultipleMatchingKIs { private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerSingleKBMultipleMatchingKIs.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; @BeforeAll public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { @@ -53,10 +53,10 @@ public void testAskAnswer() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); var kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kb1.setReasonerEnabled(true); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kb2.setReasonerEnabled(true); kn.addKB(kb2); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerUnregister.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerUnregister.java index 6203a18b..769b25c9 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerUnregister.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerUnregister.java @@ -27,14 +27,14 @@ import eu.knowledge.engine.smartconnector.api.CommunicativeAct; import eu.knowledge.engine.smartconnector.api.GraphPattern; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskAnswerUnregister { private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerUnregister.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; @BeforeAll public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { @@ -48,9 +48,9 @@ public void testAskAnswer() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); var kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); GraphPattern gp1 = new GraphPattern(prefixes, "?a ?c."); 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 4d850aca..f49501bf 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 @@ -21,13 +21,13 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskAnswerWithGapsEnabled1 { private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerWithGapsEnabled1.class); - private static MockedKnowledgeBase kbRelationAsker; + private static KnowledgeBaseImpl kbRelationAsker; private static KnowledgeNetwork kn; @@ -89,7 +89,7 @@ private void setupNetwork() { public void instantiateAskRelationsKB() { // start a knowledge base with the behavior "I am interested in related people" - kbRelationAsker = new MockedKnowledgeBase("RelationAsker"); + kbRelationAsker = new KnowledgeBaseImpl("RelationAsker"); kbRelationAsker.setReasonerEnabled(true); // Register an Ask pattern for relations with knowledge gaps enabled 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 9321ee1b..56b18acf 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 @@ -24,14 +24,14 @@ import eu.knowledge.engine.reasoner.api.TriplePattern; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskAnswerWithGapsEnabled2 { private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerWithGapsEnabled2.class); - private static MockedKnowledgeBase kbRelationAsker; - private static MockedKnowledgeBase kbRelationProvider; + private static KnowledgeBaseImpl kbRelationAsker; + private static KnowledgeBaseImpl kbRelationProvider; private static KnowledgeNetwork kn; @@ -106,7 +106,7 @@ private void setupNetwork() { public void instantiateAskRelationsKB() { // start a knowledge base with the behavior "I am interested in related people" - kbRelationAsker = new MockedKnowledgeBase("RelationAsker"); + kbRelationAsker = new KnowledgeBaseImpl("RelationAsker"); kbRelationAsker.setReasonerEnabled(true); // Register an Ask pattern for relations with knowledge gaps enabled @@ -119,7 +119,7 @@ public void instantiateAskRelationsKB() { public void instantiateAnswerRelationsKB() { // start a knowledge base with the behavior "I can supply related people" - kbRelationProvider = new MockedKnowledgeBase("RelationProvider"); + kbRelationProvider = new KnowledgeBaseImpl("RelationProvider"); kbRelationProvider.setReasonerEnabled(true); // Patterns for the RelationProvider: an Answer pattern for relations 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 17bc5c98..c32e4d71 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 @@ -24,15 +24,15 @@ import eu.knowledge.engine.reasoner.api.TriplePattern; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; 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 KnowledgeBaseImpl kbRelationAsker; + private static KnowledgeBaseImpl kbRelationProvider; + private static KnowledgeBaseImpl kbRelationReactor; private static KnowledgeNetwork kn; @@ -105,7 +105,7 @@ private void setupNetwork() { public void instantiateAskRelationsKB() { // start a knowledge base with the behavior "I am interested in related people" - kbRelationAsker = new MockedKnowledgeBase("RelationAsker"); + kbRelationAsker = new KnowledgeBaseImpl("RelationAsker"); kbRelationAsker.setReasonerEnabled(true); // Register an Ask pattern for relations with knowledge gaps enabled @@ -117,7 +117,7 @@ public void instantiateAskRelationsKB() { public void instantiateAnswerRelationsKB() { // start a knowledge base with the behavior "I can supply related people" - kbRelationProvider = new MockedKnowledgeBase("RelationProvider"); + kbRelationProvider = new KnowledgeBaseImpl("RelationProvider"); kbRelationProvider.setReasonerEnabled(true); // Patterns for the RelationProvider: an Answer pattern for relations @@ -144,7 +144,7 @@ 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 = new KnowledgeBaseImpl("relationReactor"); kbRelationReactor.setReasonerEnabled(true); // Patterns for the relationReactor: an React pattern to supply relations 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 2e2a334f..ca3efae0 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 @@ -19,13 +19,13 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskAnswerWithGapsNotEnabled { private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerWithGapsNotEnabled.class); - private static MockedKnowledgeBase kbRelationAsker; + private static KnowledgeBaseImpl kbRelationAsker; private static KnowledgeNetwork kn; @@ -81,7 +81,7 @@ private void setupNetwork() { public void instantiateAskRelationsKB() { // start a knowledge base with the behavior "I am interested in related people" - kbRelationAsker = new MockedKnowledgeBase("RelationAsker"); + kbRelationAsker = new KnowledgeBaseImpl("RelationAsker"); kbRelationAsker.setReasonerEnabled(true); // Register an Ask pattern for relations without knowledge gaps enabled diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithPartialBindings.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithPartialBindings.java index b8b0478e..16386391 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithPartialBindings.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerWithPartialBindings.java @@ -24,14 +24,14 @@ import eu.knowledge.engine.smartconnector.api.CommunicativeAct; import eu.knowledge.engine.smartconnector.api.GraphPattern; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskAnswerWithPartialBindings { private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerWithPartialBindings.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; @Test public void testAskAnswer() throws InterruptedException { @@ -41,9 +41,9 @@ public void testAskAnswer() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); var kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); GraphPattern gp1 = new GraphPattern(prefixes, "?a ?c."); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskPartialMetadata.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskPartialMetadata.java index e9e10519..261fd288 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskPartialMetadata.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskPartialMetadata.java @@ -29,15 +29,15 @@ import eu.knowledge.engine.smartconnector.api.CommunicativeAct; import eu.knowledge.engine.smartconnector.api.GraphPattern; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskPartialMetadata { private static final Logger LOG = LoggerFactory.getLogger(TestAskPartialMetadata.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; - private static MockedKnowledgeBase kb3; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; + private static KnowledgeBaseImpl kb3; @BeforeAll public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { @@ -51,13 +51,13 @@ public void testAskAnswer() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); var kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kb1.setReasonerEnabled(true); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kb2.setReasonerEnabled(true); kn.addKB(kb2); - kb3 = new MockedKnowledgeBase("kb3"); + kb3 = new KnowledgeBaseImpl("kb3"); kb3.setReasonerEnabled(true); kn.addKB(kb3); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskRecipientSelector.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskRecipientSelector.java index 4961fa9c..36bd316f 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskRecipientSelector.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskRecipientSelector.java @@ -33,16 +33,16 @@ import eu.knowledge.engine.smartconnector.api.GraphPattern; import eu.knowledge.engine.smartconnector.api.RecipientSelector; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestAskRecipientSelector { private static final Logger LOG = LoggerFactory.getLogger(TestAskRecipientSelector.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; - private static MockedKnowledgeBase kb3; - private static MockedKnowledgeBase kb4; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; + private static KnowledgeBaseImpl kb3; + private static KnowledgeBaseImpl kb4; @BeforeAll public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { @@ -56,13 +56,13 @@ public void testAskAnswer() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); var kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); - kb3 = new MockedKnowledgeBase("kb3"); + kb3 = new KnowledgeBaseImpl("kb3"); kn.addKB(kb3); - kb4 = new MockedKnowledgeBase("kb4"); + kb4 = new KnowledgeBaseImpl("kb4"); kn.addKB(kb4); GraphPattern gp1 = new GraphPattern(prefixes, "?a ?c."); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestComplexGraphPatternMatching.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestComplexGraphPatternMatching.java index 0fc0bf69..6987e243 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestComplexGraphPatternMatching.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestComplexGraphPatternMatching.java @@ -35,15 +35,15 @@ import eu.knowledge.engine.reasoner.api.TripleVarBindingSet; import eu.knowledge.engine.smartconnector.impl.Util; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestComplexGraphPatternMatching { private static final Logger LOG = LoggerFactory.getLogger(TestComplexGraphPatternMatching.class); - private static MockedKnowledgeBase devicesKB; - private static MockedKnowledgeBase dashboardKB; - private static MockedKnowledgeBase observationsKB; + private static KnowledgeBaseImpl devicesKB; + private static KnowledgeBaseImpl dashboardKB; + private static KnowledgeBaseImpl observationsKB; @BeforeAll public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { @@ -133,7 +133,7 @@ public void findMissingVars(Set gp, TripleVarBinding tvb) { private AskKnowledgeInteraction createDashboardKB(PrefixMappingMem prefixes, KnowledgeNetwork kn) { - dashboardKB = new MockedKnowledgeBase("dashboardKB"); + dashboardKB = new KnowledgeBaseImpl("dashboardKB"); dashboardKB.setReasonerEnabled(true); kn.addKB(dashboardKB); @@ -269,7 +269,7 @@ public float convert(float fahrenheit) { } private void createDevicesKB(PrefixMappingMem prefixes, KnowledgeNetwork kn) { - devicesKB = new MockedKnowledgeBase("devicesKB"); + devicesKB = new KnowledgeBaseImpl("devicesKB"); devicesKB.setReasonerEnabled(true); kn.addKB(devicesKB); GraphPattern gp1 = new GraphPattern(prefixes, TestUtils.convertGP(""" @@ -383,7 +383,7 @@ private void createDevicesKB(PrefixMappingMem prefixes, KnowledgeNetwork kn) { } private void createObservationsKB(PrefixMappingMem prefixes, KnowledgeNetwork kn) { - observationsKB = new MockedKnowledgeBase("observationsKB"); + observationsKB = new KnowledgeBaseImpl("observationsKB"); observationsKB.setReasonerEnabled(true); kn.addKB(observationsKB); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestDuplicateBindings.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestDuplicateBindings.java index 07f43169..8cf82aeb 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestDuplicateBindings.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestDuplicateBindings.java @@ -37,15 +37,15 @@ import eu.knowledge.engine.smartconnector.api.ReactHandler; import eu.knowledge.engine.smartconnector.api.ReactKnowledgeInteraction; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestDuplicateBindings { private static final Logger LOG = LoggerFactory.getLogger(TestDuplicateBindings.class); - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; - private static MockedKnowledgeBase kb3; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; + private static KnowledgeBaseImpl kb3; private static KnowledgeNetwork kn; private static AnswerKnowledgeInteraction answerKI1; @@ -61,11 +61,11 @@ public class TestDuplicateBindings { @BeforeAll public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); - kb3 = new MockedKnowledgeBase("kb3"); + kb3 = new KnowledgeBaseImpl("kb3"); kn.addKB(kb3); PrefixMappingMem prefixes = new PrefixMappingMem(); 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 40384a0b..6783f383 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 @@ -33,17 +33,17 @@ 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; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestDynamicSemanticComposition { private static final Logger LOG = LoggerFactory.getLogger(TestDynamicSemanticComposition.class); - private MockedKnowledgeBase kbTargetObserver; - private MockedKnowledgeBase kbHVTSearcher; - private MockedKnowledgeBase kbTargetCountrySupplier; - private MockedKnowledgeBase kbTargetLanguageSupplier; - private MockedKnowledgeBase kbTargetAgeSupplier; + private KnowledgeBaseImpl kbTargetObserver; + private KnowledgeBaseImpl kbHVTSearcher; + private KnowledgeBaseImpl kbTargetCountrySupplier; + private KnowledgeBaseImpl kbTargetLanguageSupplier; + private KnowledgeBaseImpl kbTargetAgeSupplier; private KnowledgeNetwork kn; @@ -186,13 +186,13 @@ private void updateNetwork(Set gaps) { instantiateTargetLanguageSupplierKB(); instantiateTargetAgeSupplierKB(); - List availableKBs = new ArrayList<>(); + List availableKBs = new ArrayList<>(); availableKBs.add(kbTargetAgeSupplier); availableKBs.add(kbTargetCountrySupplier); availableKBs.add(kbTargetLanguageSupplier); // add the first KB that fulfills the gap - kbs: for (MockedKnowledgeBase kb : availableKBs) { + kbs: for (KnowledgeBaseImpl kb : availableKBs) { for (ReactKnowledgeInteraction ki : kb.getReactKnowledgeInteractions().keySet()) { @@ -244,7 +244,7 @@ private Set translateGraphPatternTo(GraphPattern pattern) { public void instantiateHVTSearcherKB() { // start a knowledge base with the behaviour "I am interested in high-value // targets" - kbHVTSearcher = new MockedKnowledgeBase("HVTSearcher"); + kbHVTSearcher = new KnowledgeBaseImpl("HVTSearcher"); kbHVTSearcher.setReasonerEnabled(true); // Patterns for the HVTSearcher @@ -272,7 +272,7 @@ public void instantiateHVTSearcherKB() { public void instantiateObserverKB() { // start a knowledge base with the behaviour "I can supply observations of // targets" - kbTargetObserver = new MockedKnowledgeBase("TargetObserver"); + kbTargetObserver = new KnowledgeBaseImpl("TargetObserver"); kbTargetObserver.setReasonerEnabled(true); // Patterns for the TargetObserver @@ -306,7 +306,7 @@ public void instantiateObserverKB() { } public void instantiateTargetLanguageSupplierKB() { - kbTargetLanguageSupplier = new MockedKnowledgeBase("TargetLanguageSupplier"); + kbTargetLanguageSupplier = new KnowledgeBaseImpl("TargetLanguageSupplier"); kbTargetLanguageSupplier.setReasonerEnabled(true); // Patterns for the TargetLanguageSupplier @@ -348,14 +348,14 @@ public void instantiateTargetLanguageSupplierKB() { } public void instantiateTargetAgeSupplierKB() { - kbTargetAgeSupplier = new MockedKnowledgeBase("TargetAgeSupplier"); + kbTargetAgeSupplier = new KnowledgeBaseImpl("TargetAgeSupplier"); kbTargetAgeSupplier.setReasonerEnabled(true); } public void instantiateTargetCountrySupplierKB() { // start a knowledge base with the behaviour "Give me a target and I can supply // its basic attributes" - kbTargetCountrySupplier = new MockedKnowledgeBase("TargetCountrySupplier"); + kbTargetCountrySupplier = new KnowledgeBaseImpl("TargetCountrySupplier"); kbTargetCountrySupplier.setReasonerEnabled(true); // Patterns for the TargetCountrySupplier diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestKnowledgeGapDetection.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestKnowledgeGapDetection.java new file mode 100644 index 00000000..924a7583 --- /dev/null +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestKnowledgeGapDetection.java @@ -0,0 +1,199 @@ +package eu.knowledge.engine.smartconnector.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashSet; +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.graph.PrefixMappingMem; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +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.util.KnowledgeNetwork; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; + +public class TestKnowledgeGapDetection { + + private static final Logger LOG = LoggerFactory.getLogger(TestKnowledgeGapDetection.class); + + private KnowledgeBaseImpl kbEggObserver; + private KnowledgeBaseImpl kbImperialEggSearcher; + private KnowledgeNetwork kn; + private PrefixMappingMem prefixes; + private AskKnowledgeInteraction askKI; + private Set ruleSet; + + @BeforeEach + public void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { + prefixes = new PrefixMappingMem(); + prefixes.setNsPrefixes(PrefixMapping.Standard); + prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); + addDomainKnowledge(); + + instantiateImperialEggSearcherKB(); + instantiateObserverKB(); + + kn = new KnowledgeNetwork(); + kn.addKB(kbEggObserver); + kn.addKB(kbImperialEggSearcher); + kn.sync(); + } + + private void addDomainKnowledge() { + this.ruleSet = new HashSet<>(); + HashSet consequent1 = new HashSet<>(); + consequent1.add(new TriplePattern( + "?id ")); + HashSet antecedent1 = new HashSet<>(); + antecedent1.add(new TriplePattern( + "?id ")); + antecedent1.add(new TriplePattern("?id \"Alexander III\"")); + antecedent1.add(new TriplePattern("?id \"Russia\"")); + antecedent1.add(new TriplePattern("?id \"House of Fabergé\"")); + this.ruleSet.add(new Rule("Domain knowledge", antecedent1, consequent1, new Rule.AntecedentToConsequentBindingSetHandler(antecedent1))); + } + + @Test + public void testKnowledgeGap() throws InterruptedException, ExecutionException { + AskPlan plan = kbImperialEggSearcher.planAsk(askKI, new RecipientSelector()); + ReasonerPlan rn = plan.getReasonerPlan(); + rn.getStore().printGraphVizCode(rn); + AskResult result = plan.execute(new BindingSet()).get(); + Set gaps = result.getKnowledgeGaps(); + + LOG.info("Found gaps: " + gaps); + + assertEquals(1, gaps.size()); + + Set expectedGap = new HashSet<>(); + expectedGap.add(new TriplePattern(prefixes, "?id ex:commissionedBy \"Alexander III\"")); + expectedGap.add(new TriplePattern(prefixes, "?id ex:madeIn \"Russia\"")); + expectedGap.add(new TriplePattern(prefixes, "?id ex:madeBy \"House of Fabergé\"")); + + assertEquals(expectedGap, gaps.toArray()[0]); + } + + + @Test + public void testKnowledgeGapNoMatchingVars() throws InterruptedException, ExecutionException { + GraphPattern gp = new GraphPattern(prefixes, + "?iq rdf:type . ?iq ?company . ?iq ?country . ?iq ?image ."); + this.askKI = new AskKnowledgeInteraction(new CommunicativeAct(), gp, "askImperialEggsNonMatching", false, false, true, MatchStrategy.SUPREME_LEVEL); + kbImperialEggSearcher.register(this.askKI); + kn.sync(); + + AskPlan plan = kbImperialEggSearcher.planAsk(askKI, new RecipientSelector()); + ReasonerPlan rn = plan.getReasonerPlan(); + rn.getStore().printGraphVizCode(rn); + + AskResult result = plan.execute(new BindingSet()).get(); + + Set gaps = result.getKnowledgeGaps(); + LOG.info("Found gaps: " + gaps); + + assertEquals(1, gaps.size()); + + Set expectedGap = new HashSet<>(); + expectedGap.add(new TriplePattern(prefixes, "?id ex:madeBy \"House of Fabergé\"")); + expectedGap.add(new TriplePattern(prefixes, "?id ex:madeIn \"Russia\"")); + expectedGap.add(new TriplePattern(prefixes, "?id ex:commissionedBy \"Alexander III\"")); + + assertEquals(new KnowledgeGap(expectedGap), gaps.toArray()[0]); + } + + @Test + public void testNoKnowledgeGap() throws InterruptedException, ExecutionException { + GraphPattern gp = new GraphPattern(prefixes, + "?iq rdf:type . ?iq ?image ."); + this.askKI = new AskKnowledgeInteraction(new CommunicativeAct(), gp, "askImperialEggNoGap", false, false, true, MatchStrategy.SUPREME_LEVEL); + kbImperialEggSearcher.register(this.askKI); + kn.sync(); + + AskPlan plan = kbImperialEggSearcher.planAsk(askKI, new RecipientSelector()); + ReasonerPlan rn = plan.getReasonerPlan(); + rn.getStore().printGraphVizCode(rn); + + AskResult result = plan.execute(new BindingSet()).get(); + + Set gaps = result.getKnowledgeGaps(); + LOG.info("Found gaps: " + gaps); + + assertEquals(0, gaps.size()); + } + + @Test + public void testKnowledgeGapWithoutPrefixes() throws InterruptedException, ExecutionException { + GraphPattern gp = new GraphPattern("?id . ?id ?image ."); + this.askKI = new AskKnowledgeInteraction(new CommunicativeAct(), gp, "askImperialEggNoGap", false, false, true, MatchStrategy.SUPREME_LEVEL); + kbImperialEggSearcher.register(this.askKI); + kn.sync(); + + AskPlan plan = kbImperialEggSearcher.planAsk(askKI, new RecipientSelector()); + ReasonerPlan rn = plan.getReasonerPlan(); + rn.getStore().printGraphVizCode(rn); + + AskResult result = plan.execute(new BindingSet()).get(); + Set gaps = result.getKnowledgeGaps(); + LOG.info("Found gaps: " + gaps); + + assertEquals(1, gaps.size()); + Set expectedGap = new HashSet<>(); + expectedGap.add(new TriplePattern(prefixes, "?id ex:commissionedBy \"Alexander III\"")); + expectedGap.add(new TriplePattern(prefixes, "?id ex:madeIn \"Russia\"")); + expectedGap.add(new TriplePattern(prefixes, "?id ex:madeBy \"House of Fabergé\"")); + assertEquals(expectedGap, gaps.toArray()[0]); + } + + public void instantiateImperialEggSearcherKB() { + kbImperialEggSearcher = new KnowledgeBaseImpl("ImperialEggSearcher"); + kbImperialEggSearcher.setReasonerEnabled(true); + + GraphPattern gp2 = new GraphPattern(prefixes, + "?id rdf:type . ?id ?company . ?id ?country . ?id ?image ."); + this.askKI = new AskKnowledgeInteraction(new CommunicativeAct(), gp2, "askImperialEggs", false, false, true, MatchStrategy.SUPREME_LEVEL); + kbImperialEggSearcher.register(this.askKI); + kbImperialEggSearcher.setDomainKnowledge(this.ruleSet); + } + + public void instantiateObserverKB() { + kbEggObserver = new KnowledgeBaseImpl("EggObserver"); + kbEggObserver.setReasonerEnabled(true); + + GraphPattern gp1 = new GraphPattern(prefixes, "?id . ?id ?image ."); + AnswerKnowledgeInteraction aKI = new AnswerKnowledgeInteraction(new CommunicativeAct(), gp1, "answerEggs"); + kbEggObserver.register(aKI, (AnswerHandler) (anAKI, anAnswerExchangeInfo) -> { + assertTrue( + anAnswerExchangeInfo.getIncomingBindings().isEmpty() + || anAnswerExchangeInfo.getIncomingBindings().iterator().next().getVariables().isEmpty(), + "Should not have bindings in this binding set."); + BindingSet bindingSet = new BindingSet(); + Binding binding1 = new Binding(); + binding1.put("id", ""); + binding1.put("image", "\"Picture Of Hen Fabergé Egg\"^^"); + bindingSet.add(binding1); + Binding binding2 = new Binding(); + binding2.put("id", ""); + binding2.put("image", "\"Picture of Third Imperial Fabergé Egg\""); + bindingSet.add(binding2); + + return bindingSet; + }); + } + + @AfterEach + public void cleanup() throws InterruptedException, ExecutionException { + LOG.info("Clean up: {}", TestKnowledgeGapDetection.class.getSimpleName()); + kn.stop().get(); + } +} diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestMetadataFromNormalKnowledgeInteraction.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestMetadataFromNormalKnowledgeInteraction.java index de8ef325..cdfa4d2c 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestMetadataFromNormalKnowledgeInteraction.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestMetadataFromNormalKnowledgeInteraction.java @@ -18,12 +18,12 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; @Tag("Long") public class TestMetadataFromNormalKnowledgeInteraction { - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; private static final Logger LOG = LoggerFactory.getLogger(TestMetadataFromNormalKnowledgeInteraction.class); @@ -33,9 +33,9 @@ public class TestMetadataFromNormalKnowledgeInteraction { public void testPostReact() throws InterruptedException { KnowledgeNetwork kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); LOG.info("Before everyone is ready!"); kn.sync(); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestMetadataKnowledgeInteractionMatching.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestMetadataKnowledgeInteractionMatching.java index 05f3bab8..5193f118 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestMetadataKnowledgeInteractionMatching.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestMetadataKnowledgeInteractionMatching.java @@ -1,7 +1,7 @@ package eu.knowledge.engine.smartconnector.api; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; import org.apache.jena.shared.PrefixMapping; import org.apache.jena.sparql.graph.PrefixMappingMem; import org.junit.jupiter.api.AfterEach; @@ -27,14 +27,14 @@ public class TestMetadataKnowledgeInteractionMatching { private static final Logger LOG = LoggerFactory.getLogger(TestDynamicSemanticComposition.class); private static KnowledgeNetwork kn; - private static MockedKnowledgeBase kb1, kb2; + private static KnowledgeBaseImpl kb1, kb2; private static PrefixMappingMem prefixes; @BeforeEach public void setup() { kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("Kb1"); + kb1 = new KnowledgeBaseImpl("Kb1"); kb1.setReasonerEnabled(true); prefixes = new PrefixMappingMem(); @@ -57,7 +57,7 @@ public void testNewKB() { kn.addKB(kb1); kn.sync(); - kb2 = new MockedKnowledgeBase("Kb2"); + kb2 = new KnowledgeBaseImpl("Kb2"); kb2.setReasonerEnabled(true); kn.addKB(kb2); kn.sync(); @@ -78,7 +78,7 @@ public void testChangedKB() { kn.addKB(kb1); kn.sync(); - kb2 = new MockedKnowledgeBase("Kb2"); + kb2 = new KnowledgeBaseImpl("Kb2"); kb2.setReasonerEnabled(true); kn.addKB(kb2); kn.sync(); @@ -101,7 +101,7 @@ public void testRemovedKB() { return new BindingSet(); })); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("Kb2"); + kb2 = new KnowledgeBaseImpl("Kb2"); kb2.setReasonerEnabled(true); kn.addKB(kb2); kn.sync(); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReact.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReact.java index 502a940e..c6e13f05 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReact.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReact.java @@ -16,11 +16,11 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestPostReact { - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; public boolean kb2Received = false; @@ -33,9 +33,9 @@ public void testPostReact() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); KnowledgeNetwork kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); GraphPattern gp1 = new GraphPattern(prefixes, "?a ?c."); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReact2.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReact2.java index 3d6f881e..0922d71f 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReact2.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReact2.java @@ -24,12 +24,12 @@ import eu.knowledge.engine.smartconnector.api.PostResult; import eu.knowledge.engine.smartconnector.api.ReactKnowledgeInteraction; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestPostReact2 { - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; - private static MockedKnowledgeBase kb3; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; + private static KnowledgeBaseImpl kb3; private static final Logger LOG = LoggerFactory.getLogger(TestPostReact2.class); @@ -42,11 +42,11 @@ public void testPostReact() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); KnowledgeNetwork kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); - kb3 = new MockedKnowledgeBase("kb3"); + kb3 = new KnowledgeBaseImpl("kb3"); kn.addKB(kb3); // start registering diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReact3.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReact3.java index 17b6345f..f752257e 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReact3.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReact3.java @@ -17,12 +17,12 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestPostReact3 { - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; - private static MockedKnowledgeBase kb3; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; + private static KnowledgeBaseImpl kb3; private static final Logger LOG = LoggerFactory.getLogger(TestPostReact3.class); @@ -35,13 +35,13 @@ public void testPostReact() throws InterruptedException { prefixes.setNsPrefix("ex", "https://example.org/"); KnowledgeNetwork kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kb1.setReasonerEnabled(true); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kb2.setReasonerEnabled(true); kn.addKB(kb2); - kb3 = new MockedKnowledgeBase("kb3"); + kb3 = new KnowledgeBaseImpl("kb3"); kb3.setReasonerEnabled(true); kn.addKB(kb3); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReactPerformance.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReactPerformance.java index decf36c7..d33645b8 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReactPerformance.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostReactPerformance.java @@ -23,11 +23,11 @@ import eu.knowledge.engine.smartconnector.api.ReactHandler; import eu.knowledge.engine.smartconnector.api.ReactKnowledgeInteraction; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestPostReactPerformance { - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; private static final Logger LOG = LoggerFactory.getLogger(TestPostReactPerformance.class); @@ -39,9 +39,9 @@ public void testPostReact() throws InterruptedException { // create the network KnowledgeNetwork kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); // register capabilities diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostRecipientSelector.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostRecipientSelector.java index f9ce7e0b..ac59b3d0 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostRecipientSelector.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestPostRecipientSelector.java @@ -25,12 +25,12 @@ import eu.knowledge.engine.smartconnector.api.ReactKnowledgeInteraction; import eu.knowledge.engine.smartconnector.api.RecipientSelector; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestPostRecipientSelector { - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; - private static MockedKnowledgeBase kb3; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; + private static KnowledgeBaseImpl kb3; private static final Logger LOG = LoggerFactory.getLogger(TestPostRecipientSelector.class); @@ -41,11 +41,11 @@ public void testPostReact() throws InterruptedException { prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); KnowledgeNetwork kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); - kb3 = new MockedKnowledgeBase("kb3"); + kb3 = new KnowledgeBaseImpl("kb3"); kn.addKB(kb3); // start registering diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestReactMetadata.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestReactMetadata.java index b0a19ef3..8329cb63 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestReactMetadata.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestReactMetadata.java @@ -14,11 +14,11 @@ import eu.knowledge.engine.smartconnector.impl.Util; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestReactMetadata { - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; private static final Logger LOG = LoggerFactory.getLogger(TestReactMetadata.class); @@ -31,9 +31,9 @@ public void testRequestMetadata() throws InterruptedException { prefixes.setNsPrefix("saref", "https://saref.etsi.org/core/"); var kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); kn.sync(); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestRequestMetadata.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestRequestMetadata.java index 01fc5a2d..9aa46f33 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestRequestMetadata.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestRequestMetadata.java @@ -21,11 +21,11 @@ import eu.knowledge.engine.smartconnector.impl.Util; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestRequestMetadata { - private static MockedKnowledgeBase kb1; - private static MockedKnowledgeBase kb2; + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; private static final Logger LOG = LoggerFactory.getLogger(TestRequestMetadata.class); @@ -39,10 +39,10 @@ public void testRequestMetadata() throws InterruptedException, ExecutionExceptio KnowledgeNetwork kn = new KnowledgeNetwork(); - kb1 = new MockedKnowledgeBase("kb1"); + kb1 = new KnowledgeBaseImpl("kb1"); kn.addKB(kb1); - kb2 = new MockedKnowledgeBase("kb2"); + kb2 = new KnowledgeBaseImpl("kb2"); kn.addKB(kb2); GraphPattern gp = new GraphPattern(prefixes, "?obs rdf:type saref:Measurement .", "?obs saref:hasTemp ?temp ."); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/Thermostat.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/Thermostat.java index adbeb8d2..98051733 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/Thermostat.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/Thermostat.java @@ -15,15 +15,15 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class Thermostat { private static final Logger LOG = LoggerFactory.getLogger(Thermostat.class); - private MockedKnowledgeBase sensor; - private MockedKnowledgeBase thermostat; - private MockedKnowledgeBase heating; + private KnowledgeBaseImpl sensor; + private KnowledgeBaseImpl thermostat; + private KnowledgeBaseImpl heating; private Room r; ExecutorService es = Executors.newFixedThreadPool(4); @@ -60,11 +60,11 @@ public void run() { // first add the relevant knowledge bases var kn = new KnowledgeNetwork(); - sensor = new MockedKnowledgeBase("temperatureSensor"); + sensor = new KnowledgeBaseImpl("temperatureSensor"); kn.addKB(sensor); - thermostat = new MockedKnowledgeBase("thermostat"); + thermostat = new KnowledgeBaseImpl("thermostat"); kn.addKB(thermostat); - heating = new MockedKnowledgeBase("heatingSource"); + heating = new KnowledgeBaseImpl("heatingSource"); kn.addKB(heating); // then register the relevant knowledge interactions diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TimeOntologyTest.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TimeOntologyTest.java index 974486c2..4bbb4944 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TimeOntologyTest.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TimeOntologyTest.java @@ -17,7 +17,7 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TimeOntologyTest { @@ -29,9 +29,9 @@ public class TimeOntologyTest { private KnowledgeNetwork kn = new KnowledgeNetwork(); - private MockedKnowledgeBase calendar = new MockedKnowledgeBase("Calendar"); - private MockedKnowledgeBase app = new MockedKnowledgeBase("App"); - private MockedKnowledgeBase time = new MockedKnowledgeBase("Time"); + private KnowledgeBaseImpl calendar = new KnowledgeBaseImpl("Calendar"); + private KnowledgeBaseImpl app = new KnowledgeBaseImpl("App"); + private KnowledgeBaseImpl time = new KnowledgeBaseImpl("Time"); @Test public void test() throws InterruptedException, ExecutionException { diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/VariableBindingNameTest.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/VariableBindingNameTest.java index bce7d769..80fd311a 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/VariableBindingNameTest.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/VariableBindingNameTest.java @@ -17,14 +17,14 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; class VariableBindingNameTest { private static final Logger LOG = LoggerFactory.getLogger(VariableBindingNameTest.class); - private static MockedKnowledgeBase sensor; - private static MockedKnowledgeBase thermostat; + private static KnowledgeBaseImpl sensor; + private static KnowledgeBaseImpl thermostat; @Test void test() { @@ -36,9 +36,9 @@ void test() { // first add the relevant knowledge bases var kn = new KnowledgeNetwork(); - sensor = new MockedKnowledgeBase("temperatureSensor"); + sensor = new KnowledgeBaseImpl("temperatureSensor"); kn.addKB(sensor); - thermostat = new MockedKnowledgeBase("thermostat"); + thermostat = new KnowledgeBaseImpl("thermostat"); kn.addKB(thermostat); // then register the relevant knowledge interactions diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/ConfigurationTest.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/ConfigurationTest.java new file mode 100644 index 00000000..2735eb4d --- /dev/null +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/ConfigurationTest.java @@ -0,0 +1,212 @@ +package eu.knowledge.engine.smartconnector.misc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.concurrent.ExecutionException; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.knowledge.engine.smartconnector.api.AnswerExchangeInfo; +import eu.knowledge.engine.smartconnector.api.AnswerKnowledgeInteraction; +import eu.knowledge.engine.smartconnector.api.AskKnowledgeInteraction; +import eu.knowledge.engine.smartconnector.api.AskResult; +import eu.knowledge.engine.smartconnector.api.Binding; +import eu.knowledge.engine.smartconnector.api.BindingSet; +import eu.knowledge.engine.smartconnector.api.CommunicativeAct; +import eu.knowledge.engine.smartconnector.api.ExchangeInfo; +import eu.knowledge.engine.smartconnector.api.GraphPattern; +import eu.knowledge.engine.smartconnector.impl.SmartConnectorConfig; +import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; + +public class ConfigurationTest { + + private static final Logger LOG = LoggerFactory.getLogger(ConfigurationTest.class); + + private KnowledgeNetwork kn; + private KnowledgeBaseImpl kb1; + private AskKnowledgeInteraction askKI; + private KnowledgeBaseImpl kb2; + private AnswerKnowledgeInteraction answerKI; + private int waitTimeout = 0; + + @BeforeEach + public void beforeTest() { + this.kn = new KnowledgeNetwork(); + + intializeKB1(); + intializeKB2(); + kn.addKB(kb1); + kn.addKB(kb2); + kn.sync(); + } + + @Test + public void testConfigValidateTrue() { + System.setProperty(SmartConnectorConfig.CONF_KEY_VALIDATE_OUTGOING_BINDINGS_WRT_INCOMING_BINDINGS, "true"); + BindingSet bs = new BindingSet(); + + var bs1 = new Binding(); + bs1.put("s", ""); + bs.add(bs1); + + var future = kb1.ask(this.askKI, bs); + + AskResult askResult = null; + try { + askResult = future.get(); + assertTrue(askResult.getExchangeInfoPerKnowledgeBase().iterator().hasNext()); + var info = askResult.getExchangeInfoPerKnowledgeBase().iterator().next(); + + assertEquals(ExchangeInfo.Status.FAILED, info.getStatus()); + assertTrue(info.getFailedMessage().contains("java.lang.IllegalArgumentException")); + + } catch (InterruptedException | ExecutionException e) { + LOG.info("{}", e); + } + + LOG.info("Result: {}", askResult); + + System.clearProperty(SmartConnectorConfig.CONF_KEY_VALIDATE_OUTGOING_BINDINGS_WRT_INCOMING_BINDINGS); + } + + @Test + public void testConfigValidateFalse() { + System.setProperty(SmartConnectorConfig.CONF_KEY_VALIDATE_OUTGOING_BINDINGS_WRT_INCOMING_BINDINGS, "false"); + + BindingSet bs = new BindingSet(); + + var bs1 = new Binding(); + bs1.put("s", ""); + bs.add(bs1); + + var future = kb1.ask(this.askKI, bs); + + AskResult askResult = null; + try { + askResult = future.get(); + assertTrue(askResult.getExchangeInfoPerKnowledgeBase().iterator().hasNext()); + var info = askResult.getExchangeInfoPerKnowledgeBase().iterator().next(); + + assertEquals(ExchangeInfo.Status.SUCCEEDED, info.getStatus()); + assertEquals(null, info.getFailedMessage()); + + } catch (InterruptedException | ExecutionException e) { + LOG.info("{}", e); + } + + LOG.info("Result: {}", askResult); + System.clearProperty(SmartConnectorConfig.CONF_KEY_VALIDATE_OUTGOING_BINDINGS_WRT_INCOMING_BINDINGS); + } + + @Test + public void testConfigWaitForKnowledgeBaseNegative() { + System.setProperty(SmartConnectorConfig.CONF_KEY_KE_KB_WAIT_TIMEOUT, "1"); + waitTimeout = 2000; + + BindingSet bs = new BindingSet(); + + var bs1 = new Binding(); + bs1.put("s", ""); + bs.add(bs1); + + var future = kb1.ask(this.askKI, bs); + + AskResult askResult = null; + try { + askResult = future.get(); + assertTrue(askResult.getExchangeInfoPerKnowledgeBase().iterator().hasNext()); + var info = askResult.getExchangeInfoPerKnowledgeBase().iterator().next(); + + assertEquals(ExchangeInfo.Status.FAILED, info.getStatus()); + assertTrue(info.getFailedMessage() != null); + assertTrue(info.getFailedMessage().contains("TimeoutException")); + + } catch (InterruptedException | ExecutionException e) { + LOG.info("{}", e); + } + + LOG.info("Result: {}", askResult); + waitTimeout = 0; + System.clearProperty(SmartConnectorConfig.CONF_KEY_KE_KB_WAIT_TIMEOUT); + } + + @Test + public void testConfigWaitForKnowledgeBasePositive() { + System.setProperty(SmartConnectorConfig.CONF_KEY_KE_KB_WAIT_TIMEOUT, "2"); + waitTimeout = 0; + BindingSet bs = new BindingSet(); + + var bs1 = new Binding(); + bs1.put("s", ""); + bs.add(bs1); + + var future = kb1.ask(this.askKI, bs); + + AskResult askResult = null; + try { + askResult = future.get(); + assertTrue(askResult.getExchangeInfoPerKnowledgeBase().iterator().hasNext()); + var info = askResult.getExchangeInfoPerKnowledgeBase().iterator().next(); + + assertEquals(ExchangeInfo.Status.SUCCEEDED, info.getStatus()); + assertEquals(null, info.getFailedMessage()); + + } catch (InterruptedException | ExecutionException e) { + LOG.info("{}", e); + } + + LOG.info("Result: {}", askResult); + System.clearProperty(SmartConnectorConfig.CONF_KEY_KE_KB_WAIT_TIMEOUT); + } + + @AfterEach + public void afterTest() { + try { + kn.stop().get(); + } catch (InterruptedException | ExecutionException e) { + fail(); + } + } + + private void intializeKB1() { + this.kb1 = new KnowledgeBaseImpl("kb1"); + GraphPattern gp1 = new GraphPattern(""" + ?s a . + ?s ?n . + """); + this.askKI = new AskKnowledgeInteraction(new CommunicativeAct(), gp1); + this.kb1.register(askKI); + } + + private void intializeKB2() { + this.kb2 = new KnowledgeBaseImpl("kb2"); + GraphPattern gp1 = new GraphPattern(""" + ?p a . + ?p ?name . + """); + this.answerKI = new AnswerKnowledgeInteraction(new CommunicativeAct(), gp1); + this.kb2.register(answerKI, (AnswerKnowledgeInteraction ki, AnswerExchangeInfo exchangeInfo) -> { + var bs = new BindingSet(); + var b = new Binding(); + b.put("p", ""); + b.put("name", "\"Barry Nouwt\""); + bs.add(b); + + try { + Thread.sleep(waitTimeout); + } catch (InterruptedException e) { + fail(); + } + + return bs; + }); + } + +} diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/SmartConnectorRegistrationStressTest.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/SmartConnectorRegistrationStressTest.java index d4bcef6d..79a34c19 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/SmartConnectorRegistrationStressTest.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/SmartConnectorRegistrationStressTest.java @@ -21,7 +21,7 @@ import eu.knowledge.engine.smartconnector.api.SmartConnector; import eu.knowledge.engine.smartconnector.impl.SmartConnectorBuilder; import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; @Tag("Long") @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -38,7 +38,7 @@ void seedKnowledgeNetwork() { kn = new KnowledgeNetwork(); for (var i = 0; i < NUM_KBS; i++) { LOG.info("Starting KB{}", i); - kn.addKB(new MockedKnowledgeBase("INITIAL-KB" + i)); + kn.addKB(new KnowledgeBaseImpl("INITIAL-KB" + i)); } kn.sync(); } diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/WireMockFirstConfigurationTest.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/WireMockFirstConfigurationTest.java new file mode 100644 index 00000000..ceab06b2 --- /dev/null +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/WireMockFirstConfigurationTest.java @@ -0,0 +1,88 @@ +package eu.knowledge.engine.smartconnector.misc; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.status; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.net.URI; +import java.util.concurrent.TimeUnit; + +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.Timeout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; + +import eu.knowledge.engine.smartconnector.impl.SmartConnectorConfig; +import eu.knowledge.engine.smartconnector.runtime.KeRuntime; +import eu.knowledge.engine.smartconnector.runtime.messaging.MessageDispatcher; +import eu.knowledge.engine.smartconnector.runtime.messaging.RemoteKerConnection; +import eu.knowledge.engine.smartconnector.runtime.messaging.kd.model.KnowledgeEngineRuntimeConnectionDetails; + +@WireMockTest +public class WireMockFirstConfigurationTest { + + private static Logger LOG = LoggerFactory.getLogger(WireMockFirstConfigurationTest.class); + + @BeforeAll + public static void before(WireMockRuntimeInfo wmRuntimeInfo) { + + // set all system property for various tests below. + String host = wmRuntimeInfo.getHttpBaseUrl(); + System.setProperty(SmartConnectorConfig.CONF_KEY_KD_URL, host); + System.setProperty(SmartConnectorConfig.CONF_KEY_KE_RUNTIME_PORT, "1234"); + String value = "http://test${ke.runtime.hostname}:${ke.runtime.port}"; + System.setProperty(SmartConnectorConfig.CONF_KEY_KE_RUNTIME_EXPOSED_URL, value); + + LOG.info("Testing with exposed url: {}", value); + } + + /** + * Does a (very limited) test of the http timeout configuration option. We are + * setting it to 1 second and if this works, the test should succeed within 2 + * seconds. If setting the configuration property fails, the test would take 5 + * seconds (= default value for http timeout configuration property). + * + * @throws Exception + * + * @throws IOException + * @throws InterruptedException + */ + @Test + @Timeout(value = 3, unit = TimeUnit.SECONDS) + public void testConfigHttpConnectTimeout() throws Exception { + stubFor(post("/ker/").willReturn(status(201).withBody("{}"))); + System.setProperty(SmartConnectorConfig.CONF_KEY_KE_HTTP_TIMEOUT, "1"); + + MessageDispatcher messageDispatcher = mock(MessageDispatcher.class); + + var ker = new RemoteKerConnection(messageDispatcher, + new KnowledgeEngineRuntimeConnectionDetails().exposedUrl(URI.create("http://10.255.255.1/"))); + + ker.start(); + assertFalse(ker.isAvailable()); + System.clearProperty(SmartConnectorConfig.CONF_KEY_KE_HTTP_TIMEOUT); + } + + @AfterAll + public static void after() { + System.clearProperty(SmartConnectorConfig.CONF_KEY_KD_URL); + System.clearProperty(SmartConnectorConfig.CONF_KEY_KE_RUNTIME_PORT); + System.clearProperty(SmartConnectorConfig.CONF_KEY_KE_RUNTIME_EXPOSED_URL); + } + +} diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/runtime/messaging/TestRegisterSmartConnectorWithSameId.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/runtime/messaging/TestRegisterSmartConnectorWithSameId.java index 3d5f336a..02c2799c 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/runtime/messaging/TestRegisterSmartConnectorWithSameId.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/runtime/messaging/TestRegisterSmartConnectorWithSameId.java @@ -11,18 +11,18 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.knowledgedirectory.KnowledgeDirectory; -import eu.knowledge.engine.smartconnector.util.MockedKnowledgeBase; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; public class TestRegisterSmartConnectorWithSameId { private static final Logger LOG = LoggerFactory.getLogger(TestRegisterSmartConnectorWithSameId.class); private Phaser readyPhaser = new Phaser(1); @Test public void testRegisterSmartConnectorWithSameIdInSameRuntimeThrows() { - var kb1 = new MockedKnowledgeBase("http://example.org/kb1"); + var kb1 = new KnowledgeBaseImpl("http://example.org/kb1"); kb1.setPhaser(this.readyPhaser); kb1.start(); - var kb1AsWell = new MockedKnowledgeBase("http://example.org/kb1"); + var kb1AsWell = new KnowledgeBaseImpl("http://example.org/kb1"); assertThrows(IllegalArgumentException.class, () -> { kb1AsWell.start();