aboutsummaryrefslogtreecommitdiffstats
path: root/language-web/src/main/java/tools/refinery/language/web/xtext/server/push
diff options
context:
space:
mode:
Diffstat (limited to 'language-web/src/main/java/tools/refinery/language/web/xtext/server/push')
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PrecomputationListener.java15
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java23
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java89
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java68
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java33
5 files changed, 228 insertions, 0 deletions
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PrecomputationListener.java b/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PrecomputationListener.java
new file mode 100644
index 00000000..79a284db
--- /dev/null
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PrecomputationListener.java
@@ -0,0 +1,15 @@
1package tools.refinery.language.web.xtext.server.push;
2
3import org.eclipse.xtext.web.server.IServiceResult;
4
5import tools.refinery.language.web.xtext.server.ResponseHandlerException;
6
7@FunctionalInterface
8public interface PrecomputationListener {
9 void onPrecomputedServiceResult(String resourceId, String stateId, String serviceName, IServiceResult serviceResult)
10 throws ResponseHandlerException;
11
12 default void onSubscribeToPrecomputationEvents(String resourceId, PushWebDocument document) {
13 // Nothing to handle by default.
14 }
15}
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java b/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java
new file mode 100644
index 00000000..c7b8108d
--- /dev/null
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java
@@ -0,0 +1,23 @@
1package tools.refinery.language.web.xtext.server.push;
2
3import org.eclipse.xtext.web.server.IServiceContext;
4import org.eclipse.xtext.web.server.XtextServiceDispatcher;
5import org.eclipse.xtext.web.server.model.XtextWebDocument;
6
7import com.google.inject.Singleton;
8
9import tools.refinery.language.web.xtext.server.SubscribingServiceContext;
10
11@Singleton
12public class PushServiceDispatcher extends XtextServiceDispatcher {
13
14 @Override
15 protected XtextWebDocument getFullTextDocument(String fullText, String resourceId, IServiceContext context) {
16 var document = super.getFullTextDocument(fullText, resourceId, context);
17 if (document instanceof PushWebDocument pushWebDocument
18 && context instanceof SubscribingServiceContext subscribingContext) {
19 pushWebDocument.addPrecomputationListener(subscribingContext.subscriber());
20 }
21 return document;
22 }
23}
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java b/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
new file mode 100644
index 00000000..906b9e30
--- /dev/null
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
@@ -0,0 +1,89 @@
1package tools.refinery.language.web.xtext.server.push;
2
3import java.util.ArrayList;
4import java.util.HashMap;
5import java.util.List;
6import java.util.Map;
7
8import org.eclipse.xtext.util.CancelIndicator;
9import org.eclipse.xtext.web.server.IServiceResult;
10import org.eclipse.xtext.web.server.model.AbstractCachedService;
11import org.eclipse.xtext.web.server.model.DocumentSynchronizer;
12import org.eclipse.xtext.web.server.model.XtextWebDocument;
13import org.slf4j.Logger;
14import org.slf4j.LoggerFactory;
15
16import com.google.common.collect.ImmutableList;
17
18import tools.refinery.language.web.xtext.server.ResponseHandlerException;
19
20public class PushWebDocument extends XtextWebDocument {
21 private static final Logger LOG = LoggerFactory.getLogger(PushWebDocument.class);
22
23 private final List<PrecomputationListener> precomputationListeners = new ArrayList<>();
24
25 private final Map<Class<?>, IServiceResult> precomputedServices = new HashMap<>();
26
27 public PushWebDocument(String resourceId, DocumentSynchronizer synchronizer) {
28 super(resourceId, synchronizer);
29 if (resourceId == null) {
30 throw new IllegalArgumentException("resourceId must not be null");
31 }
32 }
33
34 public boolean addPrecomputationListener(PrecomputationListener listener) {
35 synchronized (precomputationListeners) {
36 if (precomputationListeners.contains(listener)) {
37 return false;
38 }
39 precomputationListeners.add(listener);
40 listener.onSubscribeToPrecomputationEvents(getResourceId(), this);
41 return true;
42 }
43 }
44
45 public boolean removePrecomputationListener(PrecomputationListener listener) {
46 synchronized (precomputationListeners) {
47 return precomputationListeners.remove(listener);
48 }
49 }
50
51 public <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, String serviceName,
52 CancelIndicator cancelIndicator, boolean logCacheMiss) {
53 var result = getCachedServiceResult(service, cancelIndicator, logCacheMiss);
54 if (result == null) {
55 LOG.error("{} service returned null result", serviceName);
56 return;
57 }
58 var serviceClass = service.getClass();
59 var previousResult = precomputedServices.get(serviceClass);
60 if (previousResult != null && previousResult.equals(result)) {
61 return;
62 }
63 precomputedServices.put(serviceClass, result);
64 notifyPrecomputationListeners(serviceName, result);
65 }
66
67 private <T extends IServiceResult> void notifyPrecomputationListeners(String serviceName, T result) {
68 var resourceId = getResourceId();
69 var stateId = getStateId();
70 List<PrecomputationListener> copyOfListeners;
71 synchronized (precomputationListeners) {
72 copyOfListeners = ImmutableList.copyOf(precomputationListeners);
73 }
74 var toRemove = new ArrayList<PrecomputationListener>();
75 for (var listener : copyOfListeners) {
76 try {
77 listener.onPrecomputedServiceResult(resourceId, stateId, serviceName, result);
78 } catch (ResponseHandlerException e) {
79 LOG.error("Delivering precomputation push message failed", e);
80 toRemove.add(listener);
81 }
82 }
83 if (!toRemove.isEmpty()) {
84 synchronized (precomputationListeners) {
85 precomputationListeners.removeAll(toRemove);
86 }
87 }
88 }
89}
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java b/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java
new file mode 100644
index 00000000..b3666a86
--- /dev/null
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java
@@ -0,0 +1,68 @@
1package tools.refinery.language.web.xtext.server.push;
2
3import org.eclipse.xtext.service.OperationCanceledManager;
4import org.eclipse.xtext.util.CancelIndicator;
5import org.eclipse.xtext.util.concurrent.CancelableUnitOfWork;
6import org.eclipse.xtext.web.server.IServiceResult;
7import org.eclipse.xtext.web.server.model.AbstractCachedService;
8import org.eclipse.xtext.web.server.model.IXtextWebDocument;
9import org.eclipse.xtext.web.server.model.PrecomputedServiceRegistry;
10import org.eclipse.xtext.web.server.model.XtextWebDocument;
11import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess;
12import org.eclipse.xtext.web.server.syntaxcoloring.HighlightingService;
13import org.eclipse.xtext.web.server.validation.ValidationService;
14
15import com.google.inject.Inject;
16
17public class PushWebDocumentAccess extends XtextWebDocumentAccess {
18
19 @Inject
20 private PrecomputedServiceRegistry preComputedServiceRegistry;
21
22 @Inject
23 private OperationCanceledManager operationCanceledManager;
24
25 private PushWebDocument pushDocument;
26
27 @Override
28 protected void init(XtextWebDocument document, String requiredStateId, boolean skipAsyncWork) {
29 super.init(document, requiredStateId, skipAsyncWork);
30 if (document instanceof PushWebDocument newPushDocument) {
31 pushDocument = newPushDocument;
32 }
33 }
34
35 @Override
36 protected void performPrecomputation(CancelIndicator cancelIndicator) {
37 if (pushDocument == null) {
38 super.performPrecomputation(cancelIndicator);
39 return;
40 }
41 for (AbstractCachedService<? extends IServiceResult> service : preComputedServiceRegistry
42 .getPrecomputedServices()) {
43 operationCanceledManager.checkCanceled(cancelIndicator);
44 precomputeServiceResult(service, false);
45 }
46 }
47
48 protected <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, boolean logCacheMiss) {
49 var serviceName = getPrecomputedServiceName(service);
50 readOnly(new CancelableUnitOfWork<Void, IXtextWebDocument>() {
51 @Override
52 public java.lang.Void exec(IXtextWebDocument d, CancelIndicator cancelIndicator) throws Exception {
53 pushDocument.precomputeServiceResult(service, serviceName, cancelIndicator, logCacheMiss);
54 return null;
55 }
56 });
57 }
58
59 protected String getPrecomputedServiceName(AbstractCachedService<? extends IServiceResult> service) {
60 if (service instanceof ValidationService) {
61 return "validate";
62 }
63 if (service instanceof HighlightingService) {
64 return "highlight";
65 }
66 throw new IllegalArgumentException("Unknown precomputed service: " + service);
67 }
68}
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java b/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java
new file mode 100644
index 00000000..fc45f74a
--- /dev/null
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java
@@ -0,0 +1,33 @@
1package tools.refinery.language.web.xtext.server.push;
2
3import org.eclipse.xtext.web.server.IServiceContext;
4import org.eclipse.xtext.web.server.model.DocumentSynchronizer;
5import org.eclipse.xtext.web.server.model.IWebDocumentProvider;
6import org.eclipse.xtext.web.server.model.XtextWebDocument;
7
8import com.google.inject.Inject;
9import com.google.inject.Provider;
10import com.google.inject.Singleton;
11
12/**
13 * Based on
14 * {@link org.eclipse.xtext.web.server.model.IWebDocumentProvider.DefaultImpl}.
15 *
16 * @author Kristóf Marussy
17 */
18@Singleton
19public class PushWebDocumentProvider implements IWebDocumentProvider {
20 @Inject
21 private Provider<DocumentSynchronizer> synchronizerProvider;
22
23 @Override
24 public XtextWebDocument get(String resourceId, IServiceContext serviceContext) {
25 if (resourceId == null) {
26 return new XtextWebDocument(resourceId, synchronizerProvider.get());
27 } else {
28 // We only need to send push messages if a resourceId is specified.
29 return new PushWebDocument(resourceId,
30 serviceContext.getSession().get(DocumentSynchronizer.class, () -> this.synchronizerProvider.get()));
31 }
32 }
33}