diff --git a/inception-recommendation/pom.xml b/inception-recommendation/pom.xml
index bbebcff13be..b47e372af2f 100644
--- a/inception-recommendation/pom.xml
+++ b/inception-recommendation/pom.xml
@@ -251,6 +251,10 @@
dkpro-core-api-ner-asl
test
+
+ org.apache.wicket
+ wicket-native-websocket-core
+
diff --git a/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java b/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java
index 537e0aeab19..2bfb9862e61 100644
--- a/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java
+++ b/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java
@@ -829,7 +829,7 @@ public Predictions computePredictions(User aUser, Project aProject,
// Perform the actual prediction
recommendationEngine.predict(ctx, predictionCas);
-
+
// Extract the suggestions from the data which the recommender has written
// into the CAS
List suggestions = extractSuggestions(aUser,
diff --git a/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/RecommenderInfoPanel.java b/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/RecommenderInfoPanel.java
index 6d40e54eabc..33f2add4b8e 100644
--- a/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/RecommenderInfoPanel.java
+++ b/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/RecommenderInfoPanel.java
@@ -35,6 +35,9 @@
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.protocol.ws.api.WebSocketBehavior;
+import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler;
+import org.apache.wicket.protocol.ws.api.message.IWebSocketPushMessage;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.wicketstuff.event.annotation.OnEvent;
@@ -111,6 +114,19 @@ protected void populateItem(ListItem item)
searchResultGroups.setModel(LoadableDetachableModel.of(() -> recommendationService
.listEnabledRecommenders(aModel.getObject().getProject())));
add(searchResultGroups);
+
+ add(new WebSocketBehavior() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void onPush(WebSocketRequestHandler aHandler, IWebSocketPushMessage aMessage)
+ {
+ // TODO: message specific stuff
+ aHandler.add(searchResultGroups);
+ }
+
+ });
}
public AnnotatorState getModelObject()
diff --git a/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/SelectionTask.java b/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/SelectionTask.java
index 49951219c48..edc14b61b37 100644
--- a/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/SelectionTask.java
+++ b/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/SelectionTask.java
@@ -48,6 +48,8 @@
import de.tudarmstadt.ukp.inception.recommendation.event.RecommenderEvaluationResultEvent;
import de.tudarmstadt.ukp.inception.scheduling.SchedulingService;
import de.tudarmstadt.ukp.inception.scheduling.Task;
+import de.tudarmstadt.ukp.inception.scheduling.TaskState;
+import de.tudarmstadt.ukp.inception.scheduling.TaskUpdateEvent;
/**
* This task evaluates all available classification tools for all annotation layers of the current
@@ -178,9 +180,8 @@ protected List initialize()
threshold);
}
- appEventPublisher.publishEvent(new RecommenderEvaluationResultEvent(this,
- recommender, user.getUsername(), result,
- System.currentTimeMillis() - start, activated));
+ publishFinishedEvents(user, recommender, start, result,
+ activated);
}
catch (Throwable e) {
log.error("[{}][{}]: Failed", user.getUsername(), recommenderName, e);
@@ -194,6 +195,17 @@ protected List initialize()
"SelectionTask after activating recommenders"));
}
+ private void publishFinishedEvents(User user, Recommender recommender, long start,
+ EvaluationResult result, boolean activated)
+ {
+ String username = user.getUsername();
+ appEventPublisher.publishEvent(new RecommenderEvaluationResultEvent(this,
+ recommender, username, result,
+ System.currentTimeMillis() - start, activated));
+ getSchedulerCallback().accept(new TaskUpdateEvent(username, TaskState.SELECTION_FINISHED,
+ result.getTrainDataRatio(), recommender.getId(), activated));
+ }
+
private List readCasses(Project aProject, String aUserName)
{
List casses = new ArrayList<>();
diff --git a/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/TrainingTask.java b/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/TrainingTask.java
index beba6e515e7..cd1c0ba3ddb 100644
--- a/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/TrainingTask.java
+++ b/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/TrainingTask.java
@@ -53,6 +53,8 @@
import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommenderContext;
import de.tudarmstadt.ukp.inception.scheduling.SchedulingService;
import de.tudarmstadt.ukp.inception.scheduling.Task;
+import de.tudarmstadt.ukp.inception.scheduling.TaskState;
+import de.tudarmstadt.ukp.inception.scheduling.TaskUpdateEvent;
/**
* This consumer trains a new classifier model, if a classification tool was selected before.
@@ -184,6 +186,9 @@ protected List initialize()
ctx.close();
recommendationService.putContext(user, recommender, ctx);
+
+ getSchedulerCallback().accept(new TaskUpdateEvent(user.getUsername(),
+ TaskState.TRAINING_FINISHED, recommender.getId()));
}
catch (Throwable e) {
log.info("[{}][{}]: Training failed ({} ms)", user.getUsername(),
diff --git a/inception-scheduling/pom.xml b/inception-scheduling/pom.xml
index 57aefbc3185..c90315d9d64 100644
--- a/inception-scheduling/pom.xml
+++ b/inception-scheduling/pom.xml
@@ -80,5 +80,9 @@
awaitility
test
+
+ org.apache.wicket
+ wicket-native-websocket-core
+
\ No newline at end of file
diff --git a/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingService.java b/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingService.java
index a9cec4e0ae5..9502b128681 100644
--- a/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingService.java
+++ b/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingService.java
@@ -22,12 +22,18 @@
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
+import org.apache.wicket.Application;
+import org.apache.wicket.protocol.ws.WebSocketSettings;
+import org.apache.wicket.protocol.ws.api.IWebSocketConnection;
+import org.apache.wicket.protocol.ws.api.registry.IWebSocketConnectionRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
+import org.springframework.security.core.session.SessionInformation;
+import org.springframework.security.core.session.SessionRegistry;
import org.springframework.stereotype.Component;
import de.tudarmstadt.ukp.inception.scheduling.config.SchedulingProperties;
@@ -40,6 +46,7 @@ public class SchedulingService
private final ApplicationContext applicationContext;
private final ThreadPoolExecutor executor;
+ private @Autowired SessionRegistry sessionRegistry;
private final List runningTasks;
@@ -94,6 +101,9 @@ public synchronized void enqueue(Task aTask)
log.debug("Enqueuing task [{}]", aTask);
+ // set callback to websocket distribution
+ aTask.setSchedulerCallback(this::distributeWebSocketMessage);
+
// This autowires the task fields manually.
AutowireCapableBeanFactory factory = applicationContext.getAutowireCapableBeanFactory();
factory.autowireBean(aTask);
@@ -122,5 +132,25 @@ public void destroy()
executor.shutdownNow();
}
-
+ public void distributeWebSocketMessage(TaskUpdateEvent aTaskUpdate)
+ {
+ Application application = Application.get();
+ WebSocketSettings webSocketSettings = WebSocketSettings.Holder.get(application);
+ IWebSocketConnectionRegistry webSocketConnectionRegistry = webSocketSettings
+ .getConnectionRegistry();
+
+ // get all connections for the user
+ List userConnections = new ArrayList<>();
+ for (SessionInformation sessionInfo : sessionRegistry
+ .getAllSessions(aTaskUpdate.getUsername(), false)) {
+ userConnections.addAll(webSocketConnectionRegistry.getConnections(application,
+ sessionInfo.getSessionId()));
+ }
+
+ // send message to all connections
+ for (IWebSocketConnection connection : userConnections) {
+ connection.sendMessage(new TaskWebSocketPushMessage(aTaskUpdate.getProgress(),
+ aTaskUpdate.getState(), aTaskUpdate.getRecommenderId(), aTaskUpdate.isActive()));
+ }
+ }
}
diff --git a/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/Task.java b/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/Task.java
index 7ee2cb481d5..34572d9ae94 100644
--- a/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/Task.java
+++ b/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/Task.java
@@ -20,6 +20,7 @@
import static org.apache.commons.lang3.Validate.notNull;
import java.util.Objects;
+import java.util.function.Consumer;
import de.tudarmstadt.ukp.clarin.webanno.model.Project;
import de.tudarmstadt.ukp.clarin.webanno.security.model.User;
@@ -30,6 +31,7 @@ public abstract class Task
private final User user;
private final Project project;
private final String trigger;
+ private Consumer schedulerCallback;
public Task(User aUser, Project aProject, String aTrigger)
{
@@ -85,4 +87,14 @@ public boolean equals(Object o) {
public int hashCode() {
return Objects.hash(user, project);
}
+
+ protected void setSchedulerCallback(Consumer aCallback)
+ {
+ schedulerCallback = aCallback;
+ }
+
+ protected Consumer getSchedulerCallback()
+ {
+ return schedulerCallback;
+ }
}
diff --git a/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskState.java b/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskState.java
new file mode 100644
index 00000000000..0f8a928dede
--- /dev/null
+++ b/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskState.java
@@ -0,0 +1,6 @@
+package de.tudarmstadt.ukp.inception.scheduling;
+
+public enum TaskState
+{
+ TRAINING_STARTED, TRAINING_FINISHED, SELECTION_STARTED, SELECTION_FINISHED
+}
diff --git a/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskUpdateEvent.java b/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskUpdateEvent.java
new file mode 100644
index 00000000000..5651bc6c642
--- /dev/null
+++ b/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskUpdateEvent.java
@@ -0,0 +1,53 @@
+package de.tudarmstadt.ukp.inception.scheduling;
+
+
+public class TaskUpdateEvent
+{
+ private final String username;
+ private final double progress;
+ private final TaskState state;
+ private final long recommenderId;
+ private final boolean active;
+
+ //TODO: add evaluationResult?
+
+ public TaskUpdateEvent(String aName, TaskState aState, double aProgress, long aRecommenderId,
+ boolean aActive)
+ {
+ username = aName;
+ state = aState;
+ progress = aProgress;
+ recommenderId = aRecommenderId;
+ active = aActive;
+ }
+
+ public TaskUpdateEvent(String aUsername, TaskState aState, long aRecommenderId)
+ {
+ this(aUsername, aState, 1, aRecommenderId, true);
+ }
+
+ public String getUsername()
+ {
+ return username;
+ }
+
+ public double getProgress()
+ {
+ return progress;
+ }
+
+ public TaskState getState()
+ {
+ return state;
+ }
+
+ public long getRecommenderId()
+ {
+ return recommenderId;
+ }
+
+ public boolean isActive()
+ {
+ return active;
+ }
+}
diff --git a/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskWebSocketPushMessage.java b/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskWebSocketPushMessage.java
new file mode 100644
index 00000000000..526349557e4
--- /dev/null
+++ b/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskWebSocketPushMessage.java
@@ -0,0 +1,16 @@
+package de.tudarmstadt.ukp.inception.scheduling;
+
+import org.apache.wicket.protocol.ws.api.message.IWebSocketPushMessage;
+
+public class TaskWebSocketPushMessage
+ implements IWebSocketPushMessage
+{
+ private static final long serialVersionUID = 1L;
+
+ public TaskWebSocketPushMessage(double aProgress, TaskState aState, long aRecommenderId,
+ boolean aActive)
+ {
+ // TODO Auto-generated constructor stub
+ }
+
+}