aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language-web
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-09-12 21:59:50 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-09-12 21:59:50 +0200
commita2a4696fdbd6440269d576aeba7b25b2ea40d9bf (patch)
tree5cbdf981a51a09fbe162e7748555d213ca518ff4 /subprojects/language-web
parentfix: avoid GLOP error message on stderr (diff)
downloadrefinery-a2a4696fdbd6440269d576aeba7b25b2ea40d9bf.tar.gz
refinery-a2a4696fdbd6440269d576aeba7b25b2ea40d9bf.tar.zst
refinery-a2a4696fdbd6440269d576aeba7b25b2ea40d9bf.zip
feat: connect model generator to UI
Diffstat (limited to 'subprojects/language-web')
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationCancelledResult.java11
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationErrorResult.java11
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationManager.java41
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationResult.java15
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationService.java60
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationStartedResult.java13
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationStatusResult.java11
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationSuccessResult.java17
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationWorker.java228
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/PartialInterpretation2Json.java81
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java2
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java63
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java32
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java2
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java39
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java20
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java4
17 files changed, 580 insertions, 70 deletions
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationCancelledResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationCancelledResult.java
new file mode 100644
index 00000000..fc06fd2e
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationCancelledResult.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.generator;
7
8import org.eclipse.xtext.web.server.IServiceResult;
9
10public record ModelGenerationCancelledResult() implements IServiceResult {
11}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationErrorResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationErrorResult.java
new file mode 100644
index 00000000..bedaeb35
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationErrorResult.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.generator;
7
8import java.util.UUID;
9
10public record ModelGenerationErrorResult(UUID uuid, String error) implements ModelGenerationResult {
11}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationManager.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationManager.java
new file mode 100644
index 00000000..b0a1912c
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationManager.java
@@ -0,0 +1,41 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.generator;
7
8import org.eclipse.xtext.util.CancelIndicator;
9
10public class ModelGenerationManager {
11 private final Object lockObject = new Object();
12 private ModelGenerationWorker worker;
13 private boolean disposed;
14
15 boolean setActiveModelGenerationWorker(ModelGenerationWorker worker, CancelIndicator cancelIndicator) {
16 synchronized (lockObject) {
17 cancel();
18 if (disposed || cancelIndicator.isCanceled()) {
19 return true;
20 }
21 this.worker = worker;
22 }
23 return false;
24 }
25
26 public void cancel() {
27 synchronized (lockObject) {
28 if (worker != null) {
29 worker.cancel();
30 worker = null;
31 }
32 }
33 }
34
35 public void dispose() {
36 synchronized (lockObject) {
37 disposed = true;
38 cancel();
39 }
40 }
41}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationResult.java
new file mode 100644
index 00000000..cf06f447
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationResult.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.generator;
7
8import org.eclipse.xtext.web.server.IServiceResult;
9
10import java.util.UUID;
11
12public sealed interface ModelGenerationResult extends IServiceResult permits ModelGenerationSuccessResult,
13 ModelGenerationErrorResult, ModelGenerationStatusResult {
14 UUID uuid();
15}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationService.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationService.java
new file mode 100644
index 00000000..5a60007f
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationService.java
@@ -0,0 +1,60 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.generator;
7
8import com.google.inject.Inject;
9import com.google.inject.Provider;
10import com.google.inject.Singleton;
11import org.eclipse.xtext.service.OperationCanceledManager;
12import org.eclipse.xtext.util.CancelIndicator;
13import org.eclipse.xtext.util.concurrent.CancelableUnitOfWork;
14import org.eclipse.xtext.web.server.model.IXtextWebDocument;
15import tools.refinery.language.web.semantics.SemanticsService;
16import tools.refinery.language.web.xtext.server.push.PushWebDocument;
17import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess;
18
19@Singleton
20public class ModelGenerationService {
21 public static final String SERVICE_NAME = "modelGeneration";
22 public static final String MODEL_GENERATION_EXECUTOR = "modelGeneration";
23 public static final String MODEL_GENERATION_TIMEOUT_EXECUTOR = "modelGenerationTimeout";
24
25 @Inject
26 private OperationCanceledManager operationCanceledManager;
27
28 @Inject
29 private Provider<ModelGenerationWorker> workerProvider;
30
31 private final long timeoutSec;
32
33 public ModelGenerationService() {
34 timeoutSec = SemanticsService.getTimeout("REFINERY_MODEL_GENERATION_TIMEOUT_SEC").orElse(600L);
35 }
36
37 public ModelGenerationStartedResult generateModel(PushWebDocumentAccess document){
38 return document.modify(new CancelableUnitOfWork<>() {
39 @Override
40 public ModelGenerationStartedResult exec(IXtextWebDocument state, CancelIndicator cancelIndicator) {
41 var pushState = (PushWebDocument) state;
42 var worker = workerProvider.get();
43 worker.setState(pushState, timeoutSec);
44 var manager = pushState.getModelGenerationManager();
45 worker.start();
46 boolean canceled = manager.setActiveModelGenerationWorker(worker, cancelIndicator);
47 if (canceled) {
48 worker.cancel();
49 operationCanceledManager.throwOperationCanceledException();
50 }
51 return new ModelGenerationStartedResult(worker.getUuid());
52 }
53 });
54 }
55
56 public ModelGenerationCancelledResult cancelModelGeneration(PushWebDocumentAccess document) {
57 document.cancelModelGeneration();
58 return new ModelGenerationCancelledResult();
59 }
60}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationStartedResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationStartedResult.java
new file mode 100644
index 00000000..8c0e73c7
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationStartedResult.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.generator;
7
8import org.eclipse.xtext.web.server.IServiceResult;
9
10import java.util.UUID;
11
12public record ModelGenerationStartedResult(UUID uuid) implements IServiceResult {
13}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationStatusResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationStatusResult.java
new file mode 100644
index 00000000..a6589870
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationStatusResult.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.generator;
7
8import java.util.UUID;
9
10public record ModelGenerationStatusResult(UUID uuid, String status) implements ModelGenerationResult {
11}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationSuccessResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationSuccessResult.java
new file mode 100644
index 00000000..21be4e08
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationSuccessResult.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.generator;
7
8import com.google.gson.JsonObject;
9import tools.refinery.language.semantics.metadata.NodeMetadata;
10import tools.refinery.language.semantics.metadata.RelationMetadata;
11
12import java.util.List;
13import java.util.UUID;
14
15public record ModelGenerationSuccessResult(UUID uuid, List<NodeMetadata> nodes, List<RelationMetadata> relations,
16 JsonObject partialInterpretation) implements ModelGenerationResult {
17}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationWorker.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationWorker.java
new file mode 100644
index 00000000..1f430da6
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationWorker.java
@@ -0,0 +1,228 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.generator;
7
8import com.google.inject.Inject;
9import com.google.inject.Provider;
10import org.eclipse.emf.common.util.URI;
11import org.eclipse.xtext.diagnostics.Severity;
12import org.eclipse.xtext.resource.IResourceFactory;
13import org.eclipse.xtext.resource.XtextResourceSet;
14import org.eclipse.xtext.service.OperationCanceledManager;
15import org.eclipse.xtext.util.LazyStringInputStream;
16import org.eclipse.xtext.validation.CheckMode;
17import org.eclipse.xtext.validation.IResourceValidator;
18import org.slf4j.Logger;
19import org.slf4j.LoggerFactory;
20import tools.refinery.language.model.problem.Problem;
21import tools.refinery.language.semantics.metadata.MetadataCreator;
22import tools.refinery.language.semantics.model.ModelInitializer;
23import tools.refinery.language.web.semantics.PartialInterpretation2Json;
24import tools.refinery.language.web.xtext.server.ThreadPoolExecutorServiceProvider;
25import tools.refinery.language.web.xtext.server.push.PushWebDocument;
26import tools.refinery.store.dse.propagation.PropagationAdapter;
27import tools.refinery.store.dse.strategy.BestFirstStoreManager;
28import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter;
29import tools.refinery.store.model.ModelStore;
30import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
31import tools.refinery.store.reasoning.ReasoningAdapter;
32import tools.refinery.store.reasoning.ReasoningStoreAdapter;
33import tools.refinery.store.reasoning.literal.Concreteness;
34import tools.refinery.store.statecoding.StateCoderAdapter;
35import tools.refinery.store.util.CancellationToken;
36
37import java.io.IOException;
38import java.util.Map;
39import java.util.UUID;
40import java.util.concurrent.*;
41
42public class ModelGenerationWorker implements Runnable {
43 private static final Logger LOG = LoggerFactory.getLogger(ModelGenerationWorker.class);
44
45 private final UUID uuid = UUID.randomUUID();
46
47 private PushWebDocument state;
48
49 private String text;
50
51 private volatile boolean timedOut;
52
53 private volatile boolean cancelled;
54
55 @Inject
56 private OperationCanceledManager operationCanceledManager;
57
58 @Inject
59 private Provider<XtextResourceSet> resourceSetProvider;
60
61 @Inject
62 private IResourceFactory resourceFactory;
63
64 @Inject
65 private IResourceValidator resourceValidator;
66
67 @Inject
68 private ModelInitializer initializer;
69
70 @Inject
71 private MetadataCreator metadataCreator;
72
73 @Inject
74 private PartialInterpretation2Json partialInterpretation2Json;
75
76 private final Object lockObject = new Object();
77
78 private ExecutorService executorService;
79
80 private ScheduledExecutorService scheduledExecutorService;
81
82 private long timeoutSec;
83
84 private Future<?> future;
85
86 private ScheduledFuture<?> timeoutFuture;
87
88 private final CancellationToken cancellationToken = () -> {
89 if (cancelled || Thread.interrupted()) {
90 operationCanceledManager.throwOperationCanceledException();
91 }
92 };
93
94 @Inject
95 public void setExecutorServiceProvider(ThreadPoolExecutorServiceProvider provider) {
96 executorService = provider.get(ModelGenerationService.MODEL_GENERATION_EXECUTOR);
97 scheduledExecutorService = provider.getScheduled(ModelGenerationService.MODEL_GENERATION_TIMEOUT_EXECUTOR);
98 }
99
100 public void setState(PushWebDocument state, long timeoutSec) {
101 this.state = state;
102 this.timeoutSec = timeoutSec;
103 text = state.getText();
104 }
105
106 public UUID getUuid() {
107 return uuid;
108 }
109
110 public void start() {
111 synchronized (lockObject) {
112 LOG.debug("Enqueueing model generation: {}", uuid);
113 future = executorService.submit(this);
114 }
115 }
116
117 public void startTimeout() {
118 synchronized (lockObject) {
119 LOG.debug("Starting model generation: {}", uuid);
120 cancellationToken.checkCancelled();
121 timeoutFuture = scheduledExecutorService.schedule(() -> cancel(true), timeoutSec, TimeUnit.SECONDS);
122 }
123 }
124
125 // We catch {@code Throwable} to handle {@code OperationCancelledError}, but we rethrow fatal JVM errors.
126 @SuppressWarnings("squid:S1181")
127 @Override
128 public void run() {
129 startTimeout();
130 notifyResult(new ModelGenerationStatusResult(uuid, "Initializing model generator"));
131 ModelGenerationResult result;
132 try {
133 result = doRun();
134 } catch (Throwable e) {
135 if (operationCanceledManager.isOperationCanceledException(e)) {
136 var message = timedOut ? "Model generation timed out" : "Model generation cancelled";
137 LOG.debug("{}: {}", message, uuid);
138 notifyResult(new ModelGenerationErrorResult(uuid, message));
139 } else if (e instanceof Error error) {
140 // Make sure we don't try to recover from any fatal JVM errors.
141 throw error;
142 } else {
143 LOG.debug("Model generation error", e);
144 notifyResult(new ModelGenerationErrorResult(uuid, e.toString()));
145 }
146 return;
147 }
148 notifyResult(result);
149 }
150
151 private void notifyResult(ModelGenerationResult result) {
152 state.notifyPrecomputationListeners(ModelGenerationService.SERVICE_NAME, result);
153 }
154
155 public ModelGenerationResult doRun() throws IOException {
156 cancellationToken.checkCancelled();
157 var resourceSet = resourceSetProvider.get();
158 var uri = URI.createURI("__synthetic_" + uuid + ".problem");
159 var resource = resourceFactory.createResource(uri);
160 resourceSet.getResources().add(resource);
161 var inputStream = new LazyStringInputStream(text);
162 resource.load(inputStream, Map.of());
163 cancellationToken.checkCancelled();
164 var issues = resourceValidator.validate(resource, CheckMode.ALL, () -> cancelled || Thread.interrupted());
165 cancellationToken.checkCancelled();
166 for (var issue : issues) {
167 if (issue.getSeverity() == Severity.ERROR) {
168 return new ModelGenerationErrorResult(uuid, "Validation error: " + issue.getMessage());
169 }
170 }
171 if (resource.getContents().isEmpty() || !(resource.getContents().get(0) instanceof Problem problem)) {
172 return new ModelGenerationErrorResult(uuid, "Model generation problem not found");
173 }
174 cancellationToken.checkCancelled();
175 var storeBuilder = ModelStore.builder()
176 .cancellationToken(cancellationToken)
177 .with(ViatraModelQueryAdapter.builder())
178 .with(PropagationAdapter.builder())
179 .with(StateCoderAdapter.builder())
180 .with(DesignSpaceExplorationAdapter.builder())
181 .with(ReasoningAdapter.builder());
182 var modelSeed = initializer.createModel(problem, storeBuilder);
183 var store = storeBuilder.build();
184 cancellationToken.checkCancelled();
185 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed);
186 var initialVersion = model.commit();
187 cancellationToken.checkCancelled();
188 notifyResult(new ModelGenerationStatusResult(uuid, "Generating model"));
189 var bestFirst = new BestFirstStoreManager(store, 1);
190 bestFirst.startExploration(initialVersion);
191 cancellationToken.checkCancelled();
192 var solutionStore = bestFirst.getSolutionStore();
193 if (solutionStore.getSolutions().isEmpty()) {
194 return new ModelGenerationErrorResult(uuid, "Problem is unsatisfiable");
195 }
196 notifyResult(new ModelGenerationStatusResult(uuid, "Saving generated model"));
197 model.restore(solutionStore.getSolutions().get(0).version());
198 cancellationToken.checkCancelled();
199 metadataCreator.setInitializer(initializer);
200 var nodesMetadata = metadataCreator.getNodesMetadata(model.getAdapter(ReasoningAdapter.class).getNodeCount());
201 cancellationToken.checkCancelled();
202 var relationsMetadata = metadataCreator.getRelationsMetadata();
203 cancellationToken.checkCancelled();
204 var partialInterpretation = partialInterpretation2Json.getPartialInterpretation(initializer, model,
205 Concreteness.CANDIDATE, cancellationToken);
206 return new ModelGenerationSuccessResult(uuid, nodesMetadata, relationsMetadata, partialInterpretation);
207 }
208
209 public void cancel() {
210 cancel(false);
211 }
212
213 public void cancel(boolean timedOut) {
214 synchronized (lockObject) {
215 LOG.trace("Cancelling model generation: {}", uuid);
216 this.timedOut = timedOut;
217 cancelled = true;
218 if (future != null) {
219 future.cancel(true);
220 future = null;
221 }
222 if (timeoutFuture != null) {
223 timeoutFuture.cancel(true);
224 timeoutFuture = null;
225 }
226 }
227 }
228}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/PartialInterpretation2Json.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/PartialInterpretation2Json.java
new file mode 100644
index 00000000..5d5da8fe
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/PartialInterpretation2Json.java
@@ -0,0 +1,81 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import com.google.gson.JsonArray;
9import com.google.gson.JsonObject;
10import com.google.inject.Inject;
11import com.google.inject.Singleton;
12import tools.refinery.language.semantics.model.ModelInitializer;
13import tools.refinery.language.semantics.model.SemanticsUtils;
14import tools.refinery.store.map.Cursor;
15import tools.refinery.store.model.Model;
16import tools.refinery.store.reasoning.ReasoningAdapter;
17import tools.refinery.store.reasoning.literal.Concreteness;
18import tools.refinery.store.reasoning.representation.PartialRelation;
19import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
20import tools.refinery.store.tuple.Tuple;
21import tools.refinery.store.util.CancellationToken;
22
23import java.util.TreeMap;
24
25@Singleton
26public class PartialInterpretation2Json {
27 @Inject
28 private SemanticsUtils semanticsUtils;
29
30 public JsonObject getPartialInterpretation(ModelInitializer initializer, Model model, Concreteness concreteness,
31 CancellationToken cancellationToken) {
32 var adapter = model.getAdapter(ReasoningAdapter.class);
33 var json = new JsonObject();
34 for (var entry : initializer.getRelationTrace().entrySet()) {
35 var relation = entry.getKey();
36 var partialSymbol = entry.getValue();
37 var tuples = getTuplesJson(adapter, concreteness, partialSymbol);
38 var name = semanticsUtils.getName(relation).orElse(partialSymbol.name());
39 json.add(name, tuples);
40 cancellationToken.checkCancelled();
41 }
42 json.add("builtin::count", getCountJson(model));
43 return json;
44 }
45
46 private static JsonArray getTuplesJson(ReasoningAdapter adapter, Concreteness concreteness,
47 PartialRelation partialSymbol) {
48 var interpretation = adapter.getPartialInterpretation(concreteness, partialSymbol);
49 var cursor = interpretation.getAll();
50 return getTuplesJson(cursor);
51 }
52
53 private static JsonArray getTuplesJson(Cursor<Tuple, ?> cursor) {
54 var map = new TreeMap<Tuple, Object>();
55 while (cursor.move()) {
56 map.put(cursor.getKey(), cursor.getValue());
57 }
58 var tuples = new JsonArray();
59 for (var entry : map.entrySet()) {
60 tuples.add(toArray(entry.getKey(), entry.getValue()));
61 }
62 return tuples;
63 }
64
65 private static JsonArray toArray(Tuple tuple, Object value) {
66 int arity = tuple.getSize();
67 var json = new JsonArray(arity + 1);
68 for (int i = 0; i < arity; i++) {
69 json.add(tuple.get(i));
70 }
71 json.add(value.toString());
72 return json;
73 }
74
75 private static JsonArray getCountJson(Model model) {
76 var interpretation = model.getInterpretation(MultiObjectTranslator.COUNT_STORAGE);
77 var cursor = interpretation.getAll();
78 return getTuplesJson(cursor);
79
80 }
81}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
index 26924f0a..331ef84b 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
@@ -54,7 +54,7 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> {
54 warmupTimeoutMs = getTimeout("REFINERY_SEMANTICS_WARMUP_TIMEOUT_MS").orElse(timeoutMs * 2); 54 warmupTimeoutMs = getTimeout("REFINERY_SEMANTICS_WARMUP_TIMEOUT_MS").orElse(timeoutMs * 2);
55 } 55 }
56 56
57 private static Optional<Long> getTimeout(String name) { 57 public static Optional<Long> getTimeout(String name) {
58 return Optional.ofNullable(System.getenv(name)).map(Long::parseUnsignedLong); 58 return Optional.ofNullable(System.getenv(name)).map(Long::parseUnsignedLong);
59 } 59 }
60 60
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
index 33b1c4fb..512c2778 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
@@ -5,8 +5,6 @@
5 */ 5 */
6package tools.refinery.language.web.semantics; 6package tools.refinery.language.web.semantics;
7 7
8import com.google.gson.JsonArray;
9import com.google.gson.JsonObject;
10import com.google.inject.Inject; 8import com.google.inject.Inject;
11import org.eclipse.emf.common.util.Diagnostic; 9import org.eclipse.emf.common.util.Diagnostic;
12import org.eclipse.emf.ecore.EObject; 10import org.eclipse.emf.ecore.EObject;
@@ -20,31 +18,24 @@ import org.eclipse.xtext.web.server.validation.ValidationResult;
20import tools.refinery.language.model.problem.Problem; 18import tools.refinery.language.model.problem.Problem;
21import tools.refinery.language.semantics.metadata.MetadataCreator; 19import tools.refinery.language.semantics.metadata.MetadataCreator;
22import tools.refinery.language.semantics.model.ModelInitializer; 20import tools.refinery.language.semantics.model.ModelInitializer;
23import tools.refinery.language.semantics.model.SemanticsUtils;
24import tools.refinery.language.semantics.model.TracedException; 21import tools.refinery.language.semantics.model.TracedException;
25import tools.refinery.store.dse.propagation.PropagationAdapter; 22import tools.refinery.store.dse.propagation.PropagationAdapter;
26import tools.refinery.store.map.Cursor;
27import tools.refinery.store.model.Model;
28import tools.refinery.store.model.ModelStore; 23import tools.refinery.store.model.ModelStore;
29import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; 24import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
30import tools.refinery.store.reasoning.ReasoningAdapter; 25import tools.refinery.store.reasoning.ReasoningAdapter;
31import tools.refinery.store.reasoning.ReasoningStoreAdapter; 26import tools.refinery.store.reasoning.ReasoningStoreAdapter;
32import tools.refinery.store.reasoning.literal.Concreteness; 27import tools.refinery.store.reasoning.literal.Concreteness;
33import tools.refinery.store.reasoning.representation.PartialRelation;
34import tools.refinery.store.reasoning.translator.TranslationException; 28import tools.refinery.store.reasoning.translator.TranslationException;
35import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
36import tools.refinery.store.tuple.Tuple;
37import tools.refinery.store.util.CancellationToken; 29import tools.refinery.store.util.CancellationToken;
38 30
39import java.util.ArrayList; 31import java.util.ArrayList;
40import java.util.TreeMap;
41import java.util.concurrent.Callable; 32import java.util.concurrent.Callable;
42 33
43class SemanticsWorker implements Callable<SemanticsResult> { 34class SemanticsWorker implements Callable<SemanticsResult> {
44 private static final String DIAGNOSTIC_ID = "tools.refinery.language.semantics.SemanticError"; 35 private static final String DIAGNOSTIC_ID = "tools.refinery.language.semantics.SemanticError";
45 36
46 @Inject 37 @Inject
47 private SemanticsUtils semanticsUtils; 38 private PartialInterpretation2Json partialInterpretation2Json;
48 39
49 @Inject 40 @Inject
50 private OperationCanceledManager operationCanceledManager; 41 private OperationCanceledManager operationCanceledManager;
@@ -93,7 +84,8 @@ class SemanticsWorker implements Callable<SemanticsResult> {
93 cancellationToken.checkCancelled(); 84 cancellationToken.checkCancelled();
94 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed); 85 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed);
95 cancellationToken.checkCancelled(); 86 cancellationToken.checkCancelled();
96 var partialInterpretation = getPartialInterpretation(initializer, model); 87 var partialInterpretation = partialInterpretation2Json.getPartialInterpretation(initializer, model,
88 Concreteness.PARTIAL, cancellationToken);
97 89
98 return new SemanticsSuccessResult(nodesMetadata, relationsMetadata, partialInterpretation); 90 return new SemanticsSuccessResult(nodesMetadata, relationsMetadata, partialInterpretation);
99 } catch (TracedException e) { 91 } catch (TracedException e) {
@@ -104,55 +96,6 @@ class SemanticsWorker implements Callable<SemanticsResult> {
104 } 96 }
105 } 97 }
106 98
107 private JsonObject getPartialInterpretation(ModelInitializer initializer, Model model) {
108 var adapter = model.getAdapter(ReasoningAdapter.class);
109 var json = new JsonObject();
110 for (var entry : initializer.getRelationTrace().entrySet()) {
111 var relation = entry.getKey();
112 var partialSymbol = entry.getValue();
113 var tuples = getTuplesJson(adapter, partialSymbol);
114 var name = semanticsUtils.getName(relation).orElse(partialSymbol.name());
115 json.add(name, tuples);
116 cancellationToken.checkCancelled();
117 }
118 json.add("builtin::count", getCountJson(model));
119 return json;
120 }
121
122 private static JsonArray getTuplesJson(ReasoningAdapter adapter, PartialRelation partialSymbol) {
123 var interpretation = adapter.getPartialInterpretation(Concreteness.PARTIAL, partialSymbol);
124 var cursor = interpretation.getAll();
125 return getTuplesJson(cursor);
126 }
127
128 private static JsonArray getTuplesJson(Cursor<Tuple, ?> cursor) {
129 var map = new TreeMap<Tuple, Object>();
130 while (cursor.move()) {
131 map.put(cursor.getKey(), cursor.getValue());
132 }
133 var tuples = new JsonArray();
134 for (var entry : map.entrySet()) {
135 tuples.add(toArray(entry.getKey(), entry.getValue()));
136 }
137 return tuples;
138 }
139
140 private static JsonArray toArray(Tuple tuple, Object value) {
141 int arity = tuple.getSize();
142 var json = new JsonArray(arity + 1);
143 for (int i = 0; i < arity; i++) {
144 json.add(tuple.get(i));
145 }
146 json.add(value.toString());
147 return json;
148 }
149
150 private static JsonArray getCountJson(Model model) {
151 var interpretation = model.getInterpretation(MultiObjectTranslator.COUNT_STORAGE);
152 var cursor = interpretation.getAll();
153 return getTuplesJson(cursor);
154 }
155
156 private SemanticsResult getTracedErrorResult(EObject sourceElement, String message) { 99 private SemanticsResult getTracedErrorResult(EObject sourceElement, String message) {
157 if (sourceElement == null || !problem.eResource().equals(sourceElement.eResource())) { 100 if (sourceElement == null || !problem.eResource().equals(sourceElement.eResource())) {
158 return new SemanticsInternalErrorResult(message); 101 return new SemanticsInternalErrorResult(message);
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java
index ba26ff58..625909b9 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java
@@ -13,9 +13,13 @@ import tools.refinery.language.web.semantics.SemanticsService;
13 13
14import java.lang.invoke.MethodHandle; 14import java.lang.invoke.MethodHandle;
15import java.lang.invoke.MethodHandles; 15import java.lang.invoke.MethodHandles;
16import java.util.Collections;
17import java.util.HashMap;
18import java.util.Map;
16import java.util.Optional; 19import java.util.Optional;
17import java.util.concurrent.ExecutorService; 20import java.util.concurrent.ExecutorService;
18import java.util.concurrent.Executors; 21import java.util.concurrent.Executors;
22import java.util.concurrent.ScheduledExecutorService;
19import java.util.concurrent.ThreadFactory; 23import java.util.concurrent.ThreadFactory;
20import java.util.concurrent.atomic.AtomicInteger; 24import java.util.concurrent.atomic.AtomicInteger;
21 25
@@ -24,6 +28,8 @@ public class ThreadPoolExecutorServiceProvider extends ExecutorServiceProvider {
24 private static final String DOCUMENT_LOCK_EXECUTOR; 28 private static final String DOCUMENT_LOCK_EXECUTOR;
25 private static final AtomicInteger POOL_ID = new AtomicInteger(1); 29 private static final AtomicInteger POOL_ID = new AtomicInteger(1);
26 30
31 private final Map<String, ScheduledExecutorService> scheduledInstanceCache =
32 Collections.synchronizedMap(new HashMap<>());
27 private final int executorThreadCount; 33 private final int executorThreadCount;
28 private final int lockExecutorThreadCount; 34 private final int lockExecutorThreadCount;
29 private final int semanticsExecutorThreadCount; 35 private final int semanticsExecutorThreadCount;
@@ -58,11 +64,15 @@ public class ThreadPoolExecutorServiceProvider extends ExecutorServiceProvider {
58 return Optional.ofNullable(System.getenv(name)).map(Integer::parseUnsignedInt); 64 return Optional.ofNullable(System.getenv(name)).map(Integer::parseUnsignedInt);
59 } 65 }
60 66
67 public ScheduledExecutorService getScheduled(String key) {
68 return scheduledInstanceCache.computeIfAbsent(key, this::createScheduledInstance);
69 }
70
61 @Override 71 @Override
62 protected ExecutorService createInstance(String key) { 72 protected ExecutorService createInstance(String key) {
63 String name = "xtext-" + POOL_ID.getAndIncrement(); 73 String name = "xtext-" + POOL_ID.getAndIncrement();
64 if (key != null) { 74 if (key != null) {
65 name = name + key + "-"; 75 name = name + "-" + key;
66 } 76 }
67 var threadFactory = new Factory(name, 5); 77 var threadFactory = new Factory(name, 5);
68 int size = getSize(key); 78 int size = getSize(key);
@@ -72,6 +82,15 @@ public class ThreadPoolExecutorServiceProvider extends ExecutorServiceProvider {
72 return Executors.newFixedThreadPool(size, threadFactory); 82 return Executors.newFixedThreadPool(size, threadFactory);
73 } 83 }
74 84
85 protected ScheduledExecutorService createScheduledInstance(String key) {
86 String name = "xtext-scheduled-" + POOL_ID.getAndIncrement();
87 if (key != null) {
88 name = name + "-" + key;
89 }
90 var threadFactory = new Factory(name, 5);
91 return Executors.newScheduledThreadPool(1, threadFactory);
92 }
93
75 private int getSize(String key) { 94 private int getSize(String key) {
76 if (SemanticsService.SEMANTICS_EXECUTOR.equals(key)) { 95 if (SemanticsService.SEMANTICS_EXECUTOR.equals(key)) {
77 return semanticsExecutorThreadCount; 96 return semanticsExecutorThreadCount;
@@ -82,6 +101,17 @@ public class ThreadPoolExecutorServiceProvider extends ExecutorServiceProvider {
82 } 101 }
83 } 102 }
84 103
104 @Override
105 public void dispose() {
106 super.dispose();
107 synchronized (scheduledInstanceCache) {
108 for (var instance : scheduledInstanceCache.values()) {
109 instance.shutdown();
110 }
111 scheduledInstanceCache.clear();
112 }
113 }
114
85 private static class Factory implements ThreadFactory { 115 private static class Factory implements ThreadFactory {
86 // We have to explicitly store the {@link ThreadGroup} to create a {@link ThreadFactory}. 116 // We have to explicitly store the {@link ThreadGroup} to create a {@link ThreadFactory}.
87 @SuppressWarnings("squid:S3014") 117 @SuppressWarnings("squid:S3014")
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java
index 74456604..a3792bac 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java
@@ -187,7 +187,7 @@ public class TransactionExecutor implements IDisposable, PrecomputationListener
187 var document = subscription.get(); 187 var document = subscription.get();
188 if (document != null) { 188 if (document != null) {
189 document.removePrecomputationListener(this); 189 document.removePrecomputationListener(this);
190 document.cancelBackgroundWork(); 190 document.dispose();
191 } 191 }
192 } 192 }
193 } 193 }
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java
index d4a8c433..a04ee226 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java
@@ -5,14 +5,17 @@
5 */ 5 */
6package tools.refinery.language.web.xtext.server.push; 6package tools.refinery.language.web.xtext.server.push;
7 7
8import com.google.common.base.Optional;
8import com.google.inject.Inject; 9import com.google.inject.Inject;
9import org.eclipse.xtext.web.server.IServiceContext; 10import org.eclipse.xtext.web.server.IServiceContext;
11import org.eclipse.xtext.web.server.InvalidRequestException;
10import org.eclipse.xtext.web.server.XtextServiceDispatcher; 12import org.eclipse.xtext.web.server.XtextServiceDispatcher;
11import org.eclipse.xtext.web.server.model.PrecomputedServiceRegistry; 13import org.eclipse.xtext.web.server.model.PrecomputedServiceRegistry;
12import org.eclipse.xtext.web.server.model.XtextWebDocument; 14import org.eclipse.xtext.web.server.model.XtextWebDocument;
13 15
14import com.google.inject.Singleton; 16import com.google.inject.Singleton;
15 17
18import tools.refinery.language.web.generator.ModelGenerationService;
16import tools.refinery.language.web.semantics.SemanticsService; 19import tools.refinery.language.web.semantics.SemanticsService;
17import tools.refinery.language.web.xtext.server.SubscribingServiceContext; 20import tools.refinery.language.web.xtext.server.SubscribingServiceContext;
18 21
@@ -21,6 +24,9 @@ public class PushServiceDispatcher extends XtextServiceDispatcher {
21 @Inject 24 @Inject
22 private SemanticsService semanticsService; 25 private SemanticsService semanticsService;
23 26
27 @Inject
28 private ModelGenerationService modelGenerationService;
29
24 @Override 30 @Override
25 @Inject 31 @Inject
26 protected void registerPreComputedServices(PrecomputedServiceRegistry registry) { 32 protected void registerPreComputedServices(PrecomputedServiceRegistry registry) {
@@ -37,4 +43,37 @@ public class PushServiceDispatcher extends XtextServiceDispatcher {
37 } 43 }
38 return document; 44 return document;
39 } 45 }
46
47 @Override
48 protected ServiceDescriptor createServiceDescriptor(String serviceType, IServiceContext context) {
49 if (ModelGenerationService.SERVICE_NAME.equals(serviceType)) {
50 return getModelGenerationService(context);
51 }
52 return super.createServiceDescriptor(serviceType, context);
53 }
54
55 protected ServiceDescriptor getModelGenerationService(IServiceContext context) throws InvalidRequestException {
56 var document = (PushWebDocumentAccess) getDocumentAccess(context);
57 // Using legacy Guava methods because of the Xtext dependency.
58 @SuppressWarnings({"Guava", "squid:S4738"})
59 boolean start = getBoolean(context, "start", Optional.of(false));
60 @SuppressWarnings({"Guava", "squid:S4738"})
61 boolean cancel = getBoolean(context, "cancel", Optional.of(false));
62 if (!start && !cancel) {
63 throw new InvalidRequestException("Either start of cancel must be specified");
64 }
65 var descriptor = new ServiceDescriptor();
66 descriptor.setService(() -> {
67 try {
68 if (start) {
69 return modelGenerationService.generateModel(document);
70 } else {
71 return modelGenerationService.cancelModelGeneration(document);
72 }
73 } catch (RuntimeException e) {
74 return handleError(descriptor, e);
75 }
76 });
77 return descriptor;
78 }
40} 79}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
index 2d43fb26..ca97147a 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
@@ -13,19 +13,18 @@ import org.eclipse.xtext.web.server.model.DocumentSynchronizer;
13import org.eclipse.xtext.web.server.model.XtextWebDocument; 13import org.eclipse.xtext.web.server.model.XtextWebDocument;
14import org.slf4j.Logger; 14import org.slf4j.Logger;
15import org.slf4j.LoggerFactory; 15import org.slf4j.LoggerFactory;
16import tools.refinery.language.web.generator.ModelGenerationManager;
16import tools.refinery.language.web.xtext.server.ResponseHandlerException; 17import tools.refinery.language.web.xtext.server.ResponseHandlerException;
17 18
18import java.util.ArrayList; 19import java.util.ArrayList;
19import java.util.HashMap;
20import java.util.List; 20import java.util.List;
21import java.util.Map;
22 21
23public class PushWebDocument extends XtextWebDocument { 22public class PushWebDocument extends XtextWebDocument {
24 private static final Logger LOG = LoggerFactory.getLogger(PushWebDocument.class); 23 private static final Logger LOG = LoggerFactory.getLogger(PushWebDocument.class);
25 24
26 private final List<PrecomputationListener> precomputationListeners = new ArrayList<>(); 25 private final List<PrecomputationListener> precomputationListeners = new ArrayList<>();
27 26
28 private final Map<Class<?>, IServiceResult> precomputedServices = new HashMap<>(); 27 private final ModelGenerationManager modelGenerationManager = new ModelGenerationManager();
29 28
30 private final DocumentSynchronizer synchronizer; 29 private final DocumentSynchronizer synchronizer;
31 30
@@ -34,6 +33,10 @@ public class PushWebDocument extends XtextWebDocument {
34 this.synchronizer = synchronizer; 33 this.synchronizer = synchronizer;
35 } 34 }
36 35
36 public ModelGenerationManager getModelGenerationManager() {
37 return modelGenerationManager;
38 }
39
37 public void addPrecomputationListener(PrecomputationListener listener) { 40 public void addPrecomputationListener(PrecomputationListener listener) {
38 synchronized (precomputationListeners) { 41 synchronized (precomputationListeners) {
39 if (precomputationListeners.contains(listener)) { 42 if (precomputationListeners.contains(listener)) {
@@ -52,15 +55,13 @@ public class PushWebDocument extends XtextWebDocument {
52 55
53 public <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, String serviceName, 56 public <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, String serviceName,
54 CancelIndicator cancelIndicator, boolean logCacheMiss) { 57 CancelIndicator cancelIndicator, boolean logCacheMiss) {
55 var serviceClass = service.getClass();
56 var result = getCachedServiceResult(service, cancelIndicator, logCacheMiss); 58 var result = getCachedServiceResult(service, cancelIndicator, logCacheMiss);
57 precomputedServices.put(serviceClass, result);
58 if (result != null) { 59 if (result != null) {
59 notifyPrecomputationListeners(serviceName, result); 60 notifyPrecomputationListeners(serviceName, result);
60 } 61 }
61 } 62 }
62 63
63 private <T extends IServiceResult> void notifyPrecomputationListeners(String serviceName, T result) { 64 public <T extends IServiceResult> void notifyPrecomputationListeners(String serviceName, T result) {
64 var resourceId = getResourceId(); 65 var resourceId = getResourceId();
65 if (resourceId == null) { 66 if (resourceId == null) {
66 return; 67 return;
@@ -86,7 +87,12 @@ public class PushWebDocument extends XtextWebDocument {
86 } 87 }
87 } 88 }
88 89
89 public void cancelBackgroundWork() { 90 public void cancelModelGeneration() {
91 modelGenerationManager.cancel();
92 }
93
94 public void dispose() {
90 synchronizer.setCanceled(true); 95 synchronizer.setCanceled(true);
96 modelGenerationManager.dispose();
91 } 97 }
92} 98}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java
index c72e8e67..1e68b244 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java
@@ -74,4 +74,8 @@ public class PushWebDocumentAccess extends XtextWebDocumentAccess {
74 } 74 }
75 throw new IllegalArgumentException("Unknown precomputed service: " + service); 75 throw new IllegalArgumentException("Unknown precomputed service: " + service);
76 } 76 }
77
78 public void cancelModelGeneration() {
79 pushDocument.cancelModelGeneration();
80 }
77} 81}