Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

581 update admin UI implementation to make meta functionality reusable #583

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;
@@ -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);
Original file line number Diff line number Diff line change
@@ -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<Resource> kbs = Util.getKnowledgeBaseURIs(model);
SmartConnector[] responses = findAndAddConnections(convertToModel(kbs, model, includeMeta));
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;
@@ -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();
}
Loading
Loading