aboutsummaryrefslogtreecommitdiffstats
path: root/language-web
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-13 19:25:15 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-31 19:26:10 +0100
commit86cb47fa2c86c27c2f82a70d77c3181e1ba43715 (patch)
tree258ca861c2c35fbfc2ab93215ff39cd38af8df0b /language-web
parentfeat(web): better websocket logging (diff)
downloadrefinery-86cb47fa2c86c27c2f82a70d77c3181e1ba43715.tar.gz
refinery-86cb47fa2c86c27c2f82a70d77c3181e1ba43715.tar.zst
refinery-86cb47fa2c86c27c2f82a70d77c3181e1ba43715.zip
feat(web): batch operations for websocket protocol
Diffstat (limited to 'language-web')
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/SimpleServiceContext.java41
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocket.java110
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketErrorKind.java14
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketErrorResponse.java53
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketOkResponse.java41
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketRequest.java50
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketResponse.java6
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketServlet.java9
-rw-r--r--language-web/src/main/js/logging.tsx2
9 files changed, 273 insertions, 53 deletions
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/SimpleServiceContext.java b/language-web/src/main/java/tools/refinery/language/web/xtext/SimpleServiceContext.java
index 1ec5b235..8036b749 100644
--- a/language-web/src/main/java/tools/refinery/language/web/xtext/SimpleServiceContext.java
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/SimpleServiceContext.java
@@ -5,14 +5,25 @@ import java.util.Set;
5 5
6import org.eclipse.xtext.web.server.IServiceContext; 6import org.eclipse.xtext.web.server.IServiceContext;
7import org.eclipse.xtext.web.server.ISession; 7import org.eclipse.xtext.web.server.ISession;
8import org.eclipse.xtext.web.server.InvalidRequestException;
8 9
9import com.google.common.collect.ImmutableSet; 10import com.google.common.collect.ImmutableMap;
10 11
11record SimpleServiceContext(ISession session, Map<String, String> parameters) implements IServiceContext { 12record SimpleServiceContext(ISession session, Map<String, String> parameters) implements IServiceContext {
12 13
14 public static final String RESOURCE_NAME_PARAMETER = "resource";
15
16 public static final String CONTENT_TYPE_PARAMETER = "contentType";
17
18 public static final String STATE_ID_PARAMETER = "requiredStateId";
19
20 public SimpleServiceContext(ISession session, XtextWebSocketRequest request, String stateId, int index) {
21 this(session, addPerTransactionData(request, stateId, request.getRequestData().get(index)));
22 }
23
13 @Override 24 @Override
14 public Set<String> getParameterKeys() { 25 public Set<String> getParameterKeys() {
15 return ImmutableSet.copyOf(parameters.keySet()); 26 return parameters.keySet();
16 } 27 }
17 28
18 @Override 29 @Override
@@ -24,4 +35,30 @@ record SimpleServiceContext(ISession session, Map<String, String> parameters) im
24 public ISession getSession() { 35 public ISession getSession() {
25 return session; 36 return session;
26 } 37 }
38
39 private static Map<String, String> addPerTransactionData(XtextWebSocketRequest request, String stateId,
40 Map<String, String> parameters) {
41 checkParameters(parameters, RESOURCE_NAME_PARAMETER);
42 checkParameters(parameters, CONTENT_TYPE_PARAMETER);
43 checkParameters(parameters, STATE_ID_PARAMETER);
44 var builder = ImmutableMap.<String, String>builder();
45 builder.putAll(parameters);
46 if (request.getResourceName() != null) {
47 builder.put(RESOURCE_NAME_PARAMETER, request.getResourceName());
48 }
49 if (request.getContentType() != null) {
50 builder.put(CONTENT_TYPE_PARAMETER, request.getContentType());
51 }
52 if (stateId != null) {
53 builder.put(STATE_ID_PARAMETER, stateId);
54 }
55 return builder.build();
56 }
57
58 private static void checkParameters(Map<String, String> parameters, String perTransactionParameter) {
59 if (parameters.containsKey(perTransactionParameter)) {
60 throw new InvalidRequestException.InvalidParametersException(
61 "Parameters map must not contain '" + perTransactionParameter + "' parameter.");
62 }
63 }
27} 64}
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocket.java b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocket.java
index 4ad98b6e..0849ccb7 100644
--- a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocket.java
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocket.java
@@ -13,13 +13,18 @@ import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
13import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; 13import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
14import org.eclipse.jetty.websocket.api.annotations.WebSocket; 14import org.eclipse.jetty.websocket.api.annotations.WebSocket;
15import org.eclipse.xtext.resource.IResourceServiceProvider; 15import org.eclipse.xtext.resource.IResourceServiceProvider;
16import org.eclipse.xtext.web.server.IServiceContext;
17import org.eclipse.xtext.web.server.IServiceResult; 16import org.eclipse.xtext.web.server.IServiceResult;
18import org.eclipse.xtext.web.server.ISession; 17import org.eclipse.xtext.web.server.ISession;
19import org.eclipse.xtext.web.server.IUnwrappableServiceResult;
20import org.eclipse.xtext.web.server.InvalidRequestException; 18import org.eclipse.xtext.web.server.InvalidRequestException;
21import org.eclipse.xtext.web.server.InvalidRequestException.UnknownLanguageException; 19import org.eclipse.xtext.web.server.InvalidRequestException.UnknownLanguageException;
20import org.eclipse.xtext.web.server.ServiceConflictResult;
22import org.eclipse.xtext.web.server.XtextServiceDispatcher; 21import org.eclipse.xtext.web.server.XtextServiceDispatcher;
22import org.eclipse.xtext.web.server.contentassist.ContentAssistResult;
23import org.eclipse.xtext.web.server.formatting.FormattingResult;
24import org.eclipse.xtext.web.server.hover.HoverResult;
25import org.eclipse.xtext.web.server.model.DocumentStateResult;
26import org.eclipse.xtext.web.server.occurrences.OccurrencesResult;
27import org.eclipse.xtext.web.server.persistence.ResourceContentResult;
23import org.slf4j.Logger; 28import org.slf4j.Logger;
24import org.slf4j.LoggerFactory; 29import org.slf4j.LoggerFactory;
25 30
@@ -80,17 +85,18 @@ public class XtextWebSocket {
80 webSocketSession.close(XtextStatusCode.INVALID_JSON, "Invalid JSON payload"); 85 webSocketSession.close(XtextStatusCode.INVALID_JSON, "Invalid JSON payload");
81 return; 86 return;
82 } 87 }
83 var serviceContext = new SimpleServiceContext(session, request.getRequestData()); 88 var requestData = request.getRequestData();
84 var response = handleMessage(webSocketSession, serviceContext); 89 if (requestData == null || requestData.isEmpty()) {
85 response.setId(request.getId()); 90 // Nothing to do.
86 var responseString = gson.toJson(response); 91 return;
92 }
93 int nCalls = requestData.size();
87 try { 94 try {
88 webSocketSession.getRemote().sendPartialString(responseString, true, new WriteCallback() { 95 int lastCall = handleTransaction(webSocketSession, request);
89 @Override 96 for (int index = lastCall + 1; index < nCalls; index++) {
90 public void writeFailed(Throwable x) { 97 sendReply(webSocketSession,
91 LOG.warn("Cannot complete async write to websocket " + webSocketSession.getRemoteAddress(), x); 98 new XtextWebSocketErrorResponse(request, index, XtextWebSocketErrorKind.TRANSACTION_CANCELLED));
92 } 99 }
93 });
94 } catch (IOException e) { 100 } catch (IOException e) {
95 LOG.warn("Cannot initiaite async write to websocket " + webSocketSession.getRemoteAddress(), e); 101 LOG.warn("Cannot initiaite async write to websocket " + webSocketSession.getRemoteAddress(), e);
96 if (webSocketSession.isOpen()) { 102 if (webSocketSession.isOpen()) {
@@ -99,27 +105,45 @@ public class XtextWebSocket {
99 } 105 }
100 } 106 }
101 107
102 protected XtextWebSocketResponse handleMessage(Session webSocketSession, IServiceContext serviceContext) { 108 protected int handleTransaction(Session webSocketSession, XtextWebSocketRequest request) throws IOException {
103 IServiceResult serviceResult; 109 var requestData = request.getRequestData();
110 var stateId = request.getRequiredStateId();
111 int index = 0;
104 try { 112 try {
105 var injector = getInjector(serviceContext); 113 var injector = getInjector(request);
106 var serviceDispatcher = injector.getInstance(XtextServiceDispatcher.class); 114 var serviceDispatcher = injector.getInstance(XtextServiceDispatcher.class);
107 var service = serviceDispatcher.getService(serviceContext); 115 int nCalls = requestData.size();
108 serviceResult = service.getService().apply(); 116 for (; index < nCalls; index++) {
117 var serviceContext = new SimpleServiceContext(session, request, stateId, index);
118 var service = serviceDispatcher.getService(serviceContext);
119 var serviceResult = service.getService().apply();
120 sendReply(webSocketSession, new XtextWebSocketOkResponse(request, index, serviceResult));
121 if (serviceResult instanceof ServiceConflictResult) {
122 break;
123 }
124 var nextStateId = getNextStateId(serviceResult);
125 if (nextStateId != null) {
126 stateId = nextStateId;
127 }
128 }
109 } catch (InvalidRequestException e) { 129 } catch (InvalidRequestException e) {
110 LOG.warn("Invalid request from websocket " + webSocketSession.getRemoteAddress(), e); 130 sendReply(webSocketSession,
111 var error = new XtextWebSocketErrorResponse(); 131 new XtextWebSocketErrorResponse(request, index, XtextWebSocketErrorKind.REQUEST_ERROR, e));
112 error.setErrorMessage(e.getMessage()); 132 } catch (RuntimeException e) {
113 return error; 133 sendReply(webSocketSession,
134 new XtextWebSocketErrorResponse(request, index, XtextWebSocketErrorKind.SERVER_ERROR, e));
114 } 135 }
115 var response = new XtextWebSocketOkResponse(); 136 return index;
116 if (serviceResult instanceof IUnwrappableServiceResult unwrappableServiceResult 137 }
117 && unwrappableServiceResult.getContent() != null) { 138
118 response.setResponseData(unwrappableServiceResult.getContent()); 139 protected void sendReply(Session webSocketSession, XtextWebSocketResponse response) throws IOException {
119 } else { 140 var responseString = gson.toJson(response);
120 response.setResponseData(serviceResult); 141 webSocketSession.getRemote().sendPartialString(responseString, true, new WriteCallback() {
121 } 142 @Override
122 return response; 143 public void writeFailed(Throwable x) {
144 LOG.warn("Cannot complete async write to websocket " + webSocketSession.getRemoteAddress(), x);
145 }
146 });
123 } 147 }
124 148
125 /** 149 /**
@@ -131,14 +155,14 @@ public class XtextWebSocket {
131 * @return the injector for the Xtext language in the request 155 * @return the injector for the Xtext language in the request
132 * @throws UnknownLanguageException if the Xtext language cannot be determined 156 * @throws UnknownLanguageException if the Xtext language cannot be determined
133 */ 157 */
134 protected Injector getInjector(IServiceContext serviceContext) { 158 protected Injector getInjector(XtextWebSocketRequest request) {
135 IResourceServiceProvider resourceServiceProvider = null; 159 IResourceServiceProvider resourceServiceProvider = null;
136 var resourceName = serviceContext.getParameter("resource"); 160 var resourceName = request.getResourceName();
137 if (resourceName == null) { 161 if (resourceName == null) {
138 resourceName = ""; 162 resourceName = "";
139 } 163 }
140 var emfURI = URI.createURI(resourceName); 164 var emfURI = URI.createURI(resourceName);
141 var contentType = serviceContext.getParameter("contentType"); 165 var contentType = request.getContentType();
142 if (Strings.isNullOrEmpty(contentType)) { 166 if (Strings.isNullOrEmpty(contentType)) {
143 resourceServiceProvider = resourceServiceProviderRegistry.getResourceServiceProvider(emfURI); 167 resourceServiceProvider = resourceServiceProviderRegistry.getResourceServiceProvider(emfURI);
144 if (resourceServiceProvider == null) { 168 if (resourceServiceProvider == null) {
@@ -159,4 +183,26 @@ public class XtextWebSocket {
159 } 183 }
160 return resourceServiceProvider.get(Injector.class); 184 return resourceServiceProvider.get(Injector.class);
161 } 185 }
186
187 protected String getNextStateId(IServiceResult serviceResult) {
188 if (serviceResult instanceof ContentAssistResult contentAssistResult) {
189 return contentAssistResult.getStateId();
190 }
191 if (serviceResult instanceof DocumentStateResult documentStateResult) {
192 return documentStateResult.getStateId();
193 }
194 if (serviceResult instanceof FormattingResult formattingResult) {
195 return formattingResult.getStateId();
196 }
197 if (serviceResult instanceof HoverResult hoverResult) {
198 return hoverResult.getStateId();
199 }
200 if (serviceResult instanceof OccurrencesResult occurrencesResult) {
201 return occurrencesResult.getStateId();
202 }
203 if (serviceResult instanceof ResourceContentResult resourceContentResult) {
204 return resourceContentResult.getStateId();
205 }
206 return null;
207 }
162} 208}
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketErrorKind.java b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketErrorKind.java
new file mode 100644
index 00000000..5759f39e
--- /dev/null
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketErrorKind.java
@@ -0,0 +1,14 @@
1package tools.refinery.language.web.xtext;
2
3import com.google.gson.annotations.SerializedName;
4
5public enum XtextWebSocketErrorKind {
6 @SerializedName("request")
7 REQUEST_ERROR,
8
9 @SerializedName("server")
10 SERVER_ERROR,
11
12 @SerializedName("transaction")
13 TRANSACTION_CANCELLED,
14}
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketErrorResponse.java b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketErrorResponse.java
index 87b300b4..1d2cf08a 100644
--- a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketErrorResponse.java
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketErrorResponse.java
@@ -7,19 +7,60 @@ import com.google.gson.annotations.SerializedName;
7public final class XtextWebSocketErrorResponse implements XtextWebSocketResponse { 7public final class XtextWebSocketErrorResponse implements XtextWebSocketResponse {
8 private String id; 8 private String id;
9 9
10 private int index;
11
10 @SerializedName("error") 12 @SerializedName("error")
13 private XtextWebSocketErrorKind errorKind;
14
15 @SerializedName("message")
11 private String errorMessage; 16 private String errorMessage;
12 17
13 @Override 18 public XtextWebSocketErrorResponse(String id, int index, XtextWebSocketErrorKind errorKind, String errorMessage) {
19 super();
20 this.id = id;
21 this.index = index;
22 this.errorKind = errorKind;
23 this.errorMessage = errorMessage;
24 }
25
26 public XtextWebSocketErrorResponse(XtextWebSocketRequest request, int index, XtextWebSocketErrorKind errorKind,
27 String errorMessage) {
28 this(request.getId(), index, errorKind, errorMessage);
29 }
30
31 public XtextWebSocketErrorResponse(XtextWebSocketRequest request, int index, XtextWebSocketErrorKind errorKind) {
32 this(request, index, errorKind, (String) null);
33 }
34
35 public XtextWebSocketErrorResponse(XtextWebSocketRequest request, int index, XtextWebSocketErrorKind errorKind,
36 Throwable t) {
37 this(request, index, errorKind, t.getMessage());
38 }
39
14 public String getId() { 40 public String getId() {
15 return id; 41 return id;
16 } 42 }
17 43
18 @Override
19 public void setId(String id) { 44 public void setId(String id) {
20 this.id = id; 45 this.id = id;
21 } 46 }
22 47
48 public int getIndex() {
49 return index;
50 }
51
52 public void setIndex(int index) {
53 this.index = index;
54 }
55
56 public XtextWebSocketErrorKind getErrorKind() {
57 return errorKind;
58 }
59
60 public void setErrorKind(XtextWebSocketErrorKind errorKind) {
61 this.errorKind = errorKind;
62 }
63
23 public String getErrorMessage() { 64 public String getErrorMessage() {
24 return errorMessage; 65 return errorMessage;
25 } 66 }
@@ -30,7 +71,7 @@ public final class XtextWebSocketErrorResponse implements XtextWebSocketResponse
30 71
31 @Override 72 @Override
32 public int hashCode() { 73 public int hashCode() {
33 return Objects.hash(errorMessage, id); 74 return Objects.hash(errorKind, errorMessage, id, index);
34 } 75 }
35 76
36 @Override 77 @Override
@@ -42,11 +83,13 @@ public final class XtextWebSocketErrorResponse implements XtextWebSocketResponse
42 if (getClass() != obj.getClass()) 83 if (getClass() != obj.getClass())
43 return false; 84 return false;
44 XtextWebSocketErrorResponse other = (XtextWebSocketErrorResponse) obj; 85 XtextWebSocketErrorResponse other = (XtextWebSocketErrorResponse) obj;
45 return Objects.equals(errorMessage, other.errorMessage) && Objects.equals(id, other.id); 86 return errorKind == other.errorKind && Objects.equals(errorMessage, other.errorMessage)
87 && Objects.equals(id, other.id) && index == other.index;
46 } 88 }
47 89
48 @Override 90 @Override
49 public String toString() { 91 public String toString() {
50 return "XtextWebSocketError [id=" + id + ", errorMessage=" + errorMessage + "]"; 92 return "XtextWebSocketErrorResponse [id=" + id + ", index=" + index + ", errorKind=" + errorKind
93 + ", errorMessage=" + errorMessage + "]";
51 } 94 }
52} 95}
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketOkResponse.java b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketOkResponse.java
index 4ef1768b..aa453544 100644
--- a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketOkResponse.java
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketOkResponse.java
@@ -2,24 +2,46 @@ package tools.refinery.language.web.xtext;
2 2
3import java.util.Objects; 3import java.util.Objects;
4 4
5import org.eclipse.xtext.web.server.IServiceResult;
6import org.eclipse.xtext.web.server.IUnwrappableServiceResult;
7
5import com.google.gson.annotations.SerializedName; 8import com.google.gson.annotations.SerializedName;
6 9
7public final class XtextWebSocketOkResponse implements XtextWebSocketResponse { 10public final class XtextWebSocketOkResponse implements XtextWebSocketResponse {
8 private String id; 11 private String id;
9 12
13 private int index;
14
10 @SerializedName("response") 15 @SerializedName("response")
11 private Object responseData; 16 private Object responseData;
12 17
13 @Override 18 public XtextWebSocketOkResponse(String id, int index, Object responseData) {
19 super();
20 this.id = id;
21 this.index = index;
22 this.responseData = responseData;
23 }
24
25 public XtextWebSocketOkResponse(XtextWebSocketRequest request, int index, IServiceResult result) {
26 this(request.getId(), index, maybeUnwrap(result));
27 }
28
14 public String getId() { 29 public String getId() {
15 return id; 30 return id;
16 } 31 }
17 32
18 @Override
19 public void setId(String id) { 33 public void setId(String id) {
20 this.id = id; 34 this.id = id;
21 } 35 }
22 36
37 public int getIndex() {
38 return index;
39 }
40
41 public void setIndex(int index) {
42 this.index = index;
43 }
44
23 public Object getResponseData() { 45 public Object getResponseData() {
24 return responseData; 46 return responseData;
25 } 47 }
@@ -30,7 +52,7 @@ public final class XtextWebSocketOkResponse implements XtextWebSocketResponse {
30 52
31 @Override 53 @Override
32 public int hashCode() { 54 public int hashCode() {
33 return Objects.hash(id, responseData); 55 return Objects.hash(id, index, responseData);
34 } 56 }
35 57
36 @Override 58 @Override
@@ -42,11 +64,20 @@ public final class XtextWebSocketOkResponse implements XtextWebSocketResponse {
42 if (getClass() != obj.getClass()) 64 if (getClass() != obj.getClass())
43 return false; 65 return false;
44 XtextWebSocketOkResponse other = (XtextWebSocketOkResponse) obj; 66 XtextWebSocketOkResponse other = (XtextWebSocketOkResponse) obj;
45 return Objects.equals(id, other.id) && Objects.equals(responseData, other.responseData); 67 return Objects.equals(id, other.id) && index == other.index && Objects.equals(responseData, other.responseData);
46 } 68 }
47 69
48 @Override 70 @Override
49 public String toString() { 71 public String toString() {
50 return "XtextWebSocketResponse [id=" + id + ", responseData=" + responseData + "]"; 72 return "XtextWebSocketOkResponse [id=" + id + ", index=" + index + ", responseData=" + responseData + "]";
73 }
74
75 private static Object maybeUnwrap(IServiceResult result) {
76 if (result instanceof IUnwrappableServiceResult unwrappableServiceResult
77 && unwrappableServiceResult.getContent() != null) {
78 return unwrappableServiceResult.getContent();
79 } else {
80 return result;
81 }
51 } 82 }
52} 83}
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketRequest.java b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketRequest.java
index e34bf73a..8aee70a1 100644
--- a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketRequest.java
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketRequest.java
@@ -1,5 +1,6 @@
1package tools.refinery.language.web.xtext; 1package tools.refinery.language.web.xtext;
2 2
3import java.util.List;
3import java.util.Map; 4import java.util.Map;
4import java.util.Objects; 5import java.util.Objects;
5 6
@@ -8,8 +9,15 @@ import com.google.gson.annotations.SerializedName;
8public class XtextWebSocketRequest { 9public class XtextWebSocketRequest {
9 private String id; 10 private String id;
10 11
12 @SerializedName("resource")
13 private String resourceName;
14
15 private String contentType;
16
17 private String requiredStateId;
18
11 @SerializedName("request") 19 @SerializedName("request")
12 private Map<String, String> requestData; 20 private List<Map<String, String>> requestData;
13 21
14 public String getId() { 22 public String getId() {
15 return id; 23 return id;
@@ -19,17 +27,41 @@ public class XtextWebSocketRequest {
19 this.id = id; 27 this.id = id;
20 } 28 }
21 29
22 public Map<String, String> getRequestData() { 30 public String getResourceName() {
31 return resourceName;
32 }
33
34 public void setResourceName(String resourceName) {
35 this.resourceName = resourceName;
36 }
37
38 public String getContentType() {
39 return contentType;
40 }
41
42 public void setContentType(String contentType) {
43 this.contentType = contentType;
44 }
45
46 public String getRequiredStateId() {
47 return requiredStateId;
48 }
49
50 public void setRequiredStateId(String requiredStateId) {
51 this.requiredStateId = requiredStateId;
52 }
53
54 public List<Map<String, String>> getRequestData() {
23 return requestData; 55 return requestData;
24 } 56 }
25 57
26 public void setRequestData(Map<String, String> request) { 58 public void setRequestData(List<Map<String, String>> requestData) {
27 this.requestData = request; 59 this.requestData = requestData;
28 } 60 }
29 61
30 @Override 62 @Override
31 public int hashCode() { 63 public int hashCode() {
32 return Objects.hash(id, requestData); 64 return Objects.hash(contentType, id, requestData, requiredStateId, resourceName);
33 } 65 }
34 66
35 @Override 67 @Override
@@ -41,11 +73,15 @@ public class XtextWebSocketRequest {
41 if (getClass() != obj.getClass()) 73 if (getClass() != obj.getClass())
42 return false; 74 return false;
43 XtextWebSocketRequest other = (XtextWebSocketRequest) obj; 75 XtextWebSocketRequest other = (XtextWebSocketRequest) obj;
44 return Objects.equals(id, other.id) && Objects.equals(requestData, other.requestData); 76 return Objects.equals(contentType, other.contentType) && Objects.equals(id, other.id)
77 && Objects.equals(requestData, other.requestData)
78 && Objects.equals(requiredStateId, other.requiredStateId)
79 && Objects.equals(resourceName, other.resourceName);
45 } 80 }
46 81
47 @Override 82 @Override
48 public String toString() { 83 public String toString() {
49 return "XtextWebSocketRequest [id=" + id + ", requestData=" + requestData + "]"; 84 return "XtextWebSocketRequest [id=" + id + ", resourceName=" + resourceName + ", contentType=" + contentType
85 + ", requiredStateId=" + requiredStateId + ", requestData=" + requestData + "]";
50 } 86 }
51} 87}
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketResponse.java b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketResponse.java
index df0c228e..9e15aa69 100644
--- a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketResponse.java
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketResponse.java
@@ -1,7 +1,11 @@
1package tools.refinery.language.web.xtext; 1package tools.refinery.language.web.xtext;
2 2
3public sealed interface XtextWebSocketResponse permits XtextWebSocketOkResponse, XtextWebSocketErrorResponse { 3public sealed interface XtextWebSocketResponse permits XtextWebSocketOkResponse,XtextWebSocketErrorResponse {
4 public String getId(); 4 public String getId();
5 5
6 public void setId(String id); 6 public void setId(String id);
7
8 public int getIndex();
9
10 public void setIndex(int index);
7} 11}
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketServlet.java b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketServlet.java
index 2db11325..5769e9e7 100644
--- a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketServlet.java
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketServlet.java
@@ -24,6 +24,8 @@ public abstract class XtextWebSocketServlet extends JettyWebSocketServlet implem
24 24
25 public static final String ALLOWED_ORIGINS_INIT_PARAM = "tools.refinery.language.web.xtext.XtextWebSocketServlet.allowedOrigin"; 25 public static final String ALLOWED_ORIGINS_INIT_PARAM = "tools.refinery.language.web.xtext.XtextWebSocketServlet.allowedOrigin";
26 26
27 public static final String XTEXT_SUBPROTOCOL_V1 = "tools.refinery.language.web.xtext.v1";
28
27 /** 29 /**
28 * Maximum message size should be large enough to upload a full model file. 30 * Maximum message size should be large enough to upload a full model file.
29 */ 31 */
@@ -68,6 +70,13 @@ public abstract class XtextWebSocketServlet extends JettyWebSocketServlet implem
68 return null; 70 return null;
69 } 71 }
70 } 72 }
73 if (req.getSubProtocols().contains(XTEXT_SUBPROTOCOL_V1)) {
74 resp.setAcceptedSubProtocol(XTEXT_SUBPROTOCOL_V1);
75 } else {
76 log.error("None of the subprotocols {} offered by {} are supported", req.getSubProtocols(),
77 req.getRemoteSocketAddress());
78 resp.setAcceptedSubProtocol(null);
79 }
71 var session = new SimpleSession(); 80 var session = new SimpleSession();
72 return new XtextWebSocket(session, IResourceServiceProvider.Registry.INSTANCE); 81 return new XtextWebSocket(session, IResourceServiceProvider.Registry.INSTANCE);
73 } 82 }
diff --git a/language-web/src/main/js/logging.tsx b/language-web/src/main/js/logging.tsx
index 25f50f19..306d122c 100644
--- a/language-web/src/main/js/logging.tsx
+++ b/language-web/src/main/js/logging.tsx
@@ -2,7 +2,7 @@ import styles, { CSPair } from 'ansi-styles';
2import log from 'loglevel'; 2import log from 'loglevel';
3import * as prefix from 'loglevel-plugin-prefix'; 3import * as prefix from 'loglevel-plugin-prefix';
4 4
5const colors: Record<string, CSPair> = { 5const colors: Partial<Record<string, CSPair>> = {
6 TRACE: styles.magenta, 6 TRACE: styles.magenta,
7 DEBUG: styles.cyan, 7 DEBUG: styles.cyan,
8 INFO: styles.blue, 8 INFO: styles.blue,