aboutsummaryrefslogtreecommitdiffstats
path: root/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java
diff options
context:
space:
mode:
Diffstat (limited to 'language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java')
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java180
1 files changed, 0 insertions, 180 deletions
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java b/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java
deleted file mode 100644
index 0b417b06..00000000
--- a/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java
+++ /dev/null
@@ -1,180 +0,0 @@
1package tools.refinery.language.web.xtext.server;
2
3import java.lang.ref.WeakReference;
4import java.util.ArrayList;
5import java.util.HashMap;
6import java.util.List;
7import java.util.Map;
8
9import org.eclipse.emf.common.util.URI;
10import org.eclipse.xtext.resource.IResourceServiceProvider;
11import org.eclipse.xtext.util.IDisposable;
12import org.eclipse.xtext.web.server.IServiceContext;
13import org.eclipse.xtext.web.server.IServiceResult;
14import org.eclipse.xtext.web.server.ISession;
15import org.eclipse.xtext.web.server.InvalidRequestException;
16import org.eclipse.xtext.web.server.InvalidRequestException.UnknownLanguageException;
17import org.eclipse.xtext.web.server.XtextServiceDispatcher;
18import org.slf4j.Logger;
19import org.slf4j.LoggerFactory;
20
21import com.google.common.base.Strings;
22import com.google.inject.Injector;
23
24import tools.refinery.language.web.xtext.server.message.XtextWebErrorKind;
25import tools.refinery.language.web.xtext.server.message.XtextWebErrorResponse;
26import tools.refinery.language.web.xtext.server.message.XtextWebOkResponse;
27import tools.refinery.language.web.xtext.server.message.XtextWebPushMessage;
28import tools.refinery.language.web.xtext.server.message.XtextWebRequest;
29import tools.refinery.language.web.xtext.server.push.PrecomputationListener;
30import tools.refinery.language.web.xtext.server.push.PushWebDocument;
31import tools.refinery.language.web.xtext.servlet.SimpleServiceContext;
32
33public class TransactionExecutor implements IDisposable, PrecomputationListener {
34 private static final Logger LOG = LoggerFactory.getLogger(TransactionExecutor.class);
35
36 private final ISession session;
37
38 private final IResourceServiceProvider.Registry resourceServiceProviderRegistry;
39
40 private final Map<String, WeakReference<PushWebDocument>> subscriptions = new HashMap<>();
41
42 private ResponseHandler responseHandler;
43
44 private Object callPendingLock = new Object();
45
46 private boolean callPending;
47
48 private List<XtextWebPushMessage> pendingPushMessages = new ArrayList<>();
49
50 public TransactionExecutor(ISession session, IResourceServiceProvider.Registry resourceServiceProviderRegistry) {
51 this.session = session;
52 this.resourceServiceProviderRegistry = resourceServiceProviderRegistry;
53 }
54
55 public void setResponseHandler(ResponseHandler responseHandler) {
56 this.responseHandler = responseHandler;
57 }
58
59 public void handleRequest(XtextWebRequest request) throws ResponseHandlerException {
60 var serviceContext = new SimpleServiceContext(session, request.getRequestData());
61 var ping = serviceContext.getParameter("ping");
62 if (ping != null) {
63 responseHandler.onResponse(new XtextWebOkResponse(request, new PongResult(ping)));
64 return;
65 }
66 synchronized (callPendingLock) {
67 if (callPending) {
68 LOG.error("Reentrant request detected");
69 }
70 if (!pendingPushMessages.isEmpty()) {
71 LOG.error("{} push messages got stuck without a pending request", pendingPushMessages.size());
72 }
73 callPending = true;
74 }
75 try {
76 var injector = getInjector(serviceContext);
77 var serviceDispatcher = injector.getInstance(XtextServiceDispatcher.class);
78 var service = serviceDispatcher.getService(new SubscribingServiceContext(serviceContext, this));
79 var serviceResult = service.getService().apply();
80 responseHandler.onResponse(new XtextWebOkResponse(request, serviceResult));
81 } catch (InvalidRequestException e) {
82 responseHandler.onResponse(new XtextWebErrorResponse(request, XtextWebErrorKind.REQUEST_ERROR, e));
83 } catch (RuntimeException e) {
84 responseHandler.onResponse(new XtextWebErrorResponse(request, XtextWebErrorKind.SERVER_ERROR, e));
85 } finally {
86 synchronized (callPendingLock) {
87 for (var message : pendingPushMessages) {
88 try {
89 responseHandler.onResponse(message);
90 } catch (ResponseHandlerException | RuntimeException e) {
91 LOG.error("Error while flushing push message", e);
92 }
93 }
94 pendingPushMessages.clear();
95 callPending = false;
96 }
97 }
98 }
99
100 @Override
101 public void onPrecomputedServiceResult(String resourceId, String stateId, String serviceName,
102 IServiceResult serviceResult) throws ResponseHandlerException {
103 var message = new XtextWebPushMessage(resourceId, stateId, serviceName, serviceResult);
104 synchronized (callPendingLock) {
105 // If we're currently responding to a call we must delay any push messages until
106 // the reply is sent, because push messages relating to the new state id must be
107 // sent after the response with the new state id so that the client knows about
108 // the new state when it receives the push message.
109 if (callPending) {
110 pendingPushMessages.add(message);
111 } else {
112 responseHandler.onResponse(message);
113 }
114 }
115 }
116
117 @Override
118 public void onSubscribeToPrecomputationEvents(String resourceId, PushWebDocument document) {
119 PushWebDocument previousDocument = null;
120 var previousSubscription = subscriptions.get(resourceId);
121 if (previousSubscription != null) {
122 previousDocument = previousSubscription.get();
123 }
124 if (previousDocument == document) {
125 return;
126 }
127 if (previousDocument != null) {
128 previousDocument.removePrecomputationListener(this);
129 }
130 subscriptions.put(resourceId, new WeakReference<>(document));
131 }
132
133 /**
134 * Get the injector to satisfy the request in the {@code serviceContext}.
135 *
136 * Based on {@link org.eclipse.xtext.web.servlet.XtextServlet#getInjector}.
137 *
138 * @param serviceContext the Xtext service context of the request
139 * @return the injector for the Xtext language in the request
140 * @throws UnknownLanguageException if the Xtext language cannot be determined
141 */
142 protected Injector getInjector(IServiceContext context) {
143 IResourceServiceProvider resourceServiceProvider = null;
144 var resourceName = context.getParameter("resource");
145 if (resourceName == null) {
146 resourceName = "";
147 }
148 var emfURI = URI.createURI(resourceName);
149 var contentType = context.getParameter("contentType");
150 if (Strings.isNullOrEmpty(contentType)) {
151 resourceServiceProvider = resourceServiceProviderRegistry.getResourceServiceProvider(emfURI);
152 if (resourceServiceProvider == null) {
153 if (emfURI.toString().isEmpty()) {
154 throw new UnknownLanguageException(
155 "Unable to identify the Xtext language: missing parameter 'resource' or 'contentType'.");
156 } else {
157 throw new UnknownLanguageException(
158 "Unable to identify the Xtext language for resource " + emfURI + ".");
159 }
160 }
161 } else {
162 resourceServiceProvider = resourceServiceProviderRegistry.getResourceServiceProvider(emfURI, contentType);
163 if (resourceServiceProvider == null) {
164 throw new UnknownLanguageException(
165 "Unable to identify the Xtext language for contentType " + contentType + ".");
166 }
167 }
168 return resourceServiceProvider.get(Injector.class);
169 }
170
171 @Override
172 public void dispose() {
173 for (var subscription : subscriptions.values()) {
174 var document = subscription.get();
175 if (document != null) {
176 document.removePrecomputationListener(this);
177 }
178 }
179 }
180}