Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
335 changes: 68 additions & 267 deletions admin-ui/src/main/java/eu/knowledge/engine/admin/AdminUI.java

Large diffs are not rendered by default.

233 changes: 233 additions & 0 deletions admin-ui/src/main/java/eu/knowledge/engine/admin/MetadataKB.java
Original file line number Diff line number Diff line change
@@ -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<Resource>(Arrays.asList(Vocab.NEW_KNOWLEDGE_PURPOSE)),
new HashSet<Resource>(Arrays.asList(Vocab.INFORM_PURPOSE))),
this.metaGraphPattern, null);
this.rKIChanged = new ReactKnowledgeInteraction(
new CommunicativeAct(new HashSet<Resource>(Arrays.asList(Vocab.CHANGED_KNOWLEDGE_PURPOSE)),
new HashSet<Resource>(Arrays.asList(Vocab.INFORM_PURPOSE))),
this.metaGraphPattern, null);
this.rKIRemoved = new ReactKnowledgeInteraction(
new CommunicativeAct(new HashSet<Resource>(Arrays.asList(Vocab.REMOVED_KNOWLEDGE_PURPOSE)),
new HashSet<Resource>(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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Resource> kbs = Util.getKnowledgeBaseURIs(model);
SmartConnector[] responses = findAndAddConnections(convertToModel(kbs, model, includeMeta));
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -294,7 +304,7 @@ public void stopKbs() {
stopKb(kb2);
}

public void stopKb(MockedKnowledgeBase aKb) {
public void stopKb(KnowledgeBaseImpl aKb) {
if (aKb != null) {
aKb.stop();
}
Expand Down
Loading