diff options
author | Kristóf Marussy <kristof@marussy.com> | 2023-09-12 21:59:50 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2023-09-12 21:59:50 +0200 |
commit | a2a4696fdbd6440269d576aeba7b25b2ea40d9bf (patch) | |
tree | 5cbdf981a51a09fbe162e7748555d213ca518ff4 /subprojects/language-web | |
parent | fix: avoid GLOP error message on stderr (diff) | |
download | refinery-a2a4696fdbd6440269d576aeba7b25b2ea40d9bf.tar.gz refinery-a2a4696fdbd6440269d576aeba7b25b2ea40d9bf.tar.zst refinery-a2a4696fdbd6440269d576aeba7b25b2ea40d9bf.zip |
feat: connect model generator to UI
Diffstat (limited to 'subprojects/language-web')
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 | */ | ||
6 | package tools.refinery.language.web.generator; | ||
7 | |||
8 | import org.eclipse.xtext.web.server.IServiceResult; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.language.web.generator; | ||
7 | |||
8 | import java.util.UUID; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.language.web.generator; | ||
7 | |||
8 | import org.eclipse.xtext.util.CancelIndicator; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.language.web.generator; | ||
7 | |||
8 | import org.eclipse.xtext.web.server.IServiceResult; | ||
9 | |||
10 | import java.util.UUID; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.language.web.generator; | ||
7 | |||
8 | import com.google.inject.Inject; | ||
9 | import com.google.inject.Provider; | ||
10 | import com.google.inject.Singleton; | ||
11 | import org.eclipse.xtext.service.OperationCanceledManager; | ||
12 | import org.eclipse.xtext.util.CancelIndicator; | ||
13 | import org.eclipse.xtext.util.concurrent.CancelableUnitOfWork; | ||
14 | import org.eclipse.xtext.web.server.model.IXtextWebDocument; | ||
15 | import tools.refinery.language.web.semantics.SemanticsService; | ||
16 | import tools.refinery.language.web.xtext.server.push.PushWebDocument; | ||
17 | import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess; | ||
18 | |||
19 | @Singleton | ||
20 | public 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 | */ | ||
6 | package tools.refinery.language.web.generator; | ||
7 | |||
8 | import org.eclipse.xtext.web.server.IServiceResult; | ||
9 | |||
10 | import java.util.UUID; | ||
11 | |||
12 | public 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 | */ | ||
6 | package tools.refinery.language.web.generator; | ||
7 | |||
8 | import java.util.UUID; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.language.web.generator; | ||
7 | |||
8 | import com.google.gson.JsonObject; | ||
9 | import tools.refinery.language.semantics.metadata.NodeMetadata; | ||
10 | import tools.refinery.language.semantics.metadata.RelationMetadata; | ||
11 | |||
12 | import java.util.List; | ||
13 | import java.util.UUID; | ||
14 | |||
15 | public 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 | */ | ||
6 | package tools.refinery.language.web.generator; | ||
7 | |||
8 | import com.google.inject.Inject; | ||
9 | import com.google.inject.Provider; | ||
10 | import org.eclipse.emf.common.util.URI; | ||
11 | import org.eclipse.xtext.diagnostics.Severity; | ||
12 | import org.eclipse.xtext.resource.IResourceFactory; | ||
13 | import org.eclipse.xtext.resource.XtextResourceSet; | ||
14 | import org.eclipse.xtext.service.OperationCanceledManager; | ||
15 | import org.eclipse.xtext.util.LazyStringInputStream; | ||
16 | import org.eclipse.xtext.validation.CheckMode; | ||
17 | import org.eclipse.xtext.validation.IResourceValidator; | ||
18 | import org.slf4j.Logger; | ||
19 | import org.slf4j.LoggerFactory; | ||
20 | import tools.refinery.language.model.problem.Problem; | ||
21 | import tools.refinery.language.semantics.metadata.MetadataCreator; | ||
22 | import tools.refinery.language.semantics.model.ModelInitializer; | ||
23 | import tools.refinery.language.web.semantics.PartialInterpretation2Json; | ||
24 | import tools.refinery.language.web.xtext.server.ThreadPoolExecutorServiceProvider; | ||
25 | import tools.refinery.language.web.xtext.server.push.PushWebDocument; | ||
26 | import tools.refinery.store.dse.propagation.PropagationAdapter; | ||
27 | import tools.refinery.store.dse.strategy.BestFirstStoreManager; | ||
28 | import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter; | ||
29 | import tools.refinery.store.model.ModelStore; | ||
30 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; | ||
31 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
32 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; | ||
33 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
34 | import tools.refinery.store.statecoding.StateCoderAdapter; | ||
35 | import tools.refinery.store.util.CancellationToken; | ||
36 | |||
37 | import java.io.IOException; | ||
38 | import java.util.Map; | ||
39 | import java.util.UUID; | ||
40 | import java.util.concurrent.*; | ||
41 | |||
42 | public 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 | */ | ||
6 | package tools.refinery.language.web.semantics; | ||
7 | |||
8 | import com.google.gson.JsonArray; | ||
9 | import com.google.gson.JsonObject; | ||
10 | import com.google.inject.Inject; | ||
11 | import com.google.inject.Singleton; | ||
12 | import tools.refinery.language.semantics.model.ModelInitializer; | ||
13 | import tools.refinery.language.semantics.model.SemanticsUtils; | ||
14 | import tools.refinery.store.map.Cursor; | ||
15 | import tools.refinery.store.model.Model; | ||
16 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
17 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
18 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
19 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; | ||
20 | import tools.refinery.store.tuple.Tuple; | ||
21 | import tools.refinery.store.util.CancellationToken; | ||
22 | |||
23 | import java.util.TreeMap; | ||
24 | |||
25 | @Singleton | ||
26 | public 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 | */ |
6 | package tools.refinery.language.web.semantics; | 6 | package tools.refinery.language.web.semantics; |
7 | 7 | ||
8 | import com.google.gson.JsonArray; | ||
9 | import com.google.gson.JsonObject; | ||
10 | import com.google.inject.Inject; | 8 | import com.google.inject.Inject; |
11 | import org.eclipse.emf.common.util.Diagnostic; | 9 | import org.eclipse.emf.common.util.Diagnostic; |
12 | import org.eclipse.emf.ecore.EObject; | 10 | import org.eclipse.emf.ecore.EObject; |
@@ -20,31 +18,24 @@ import org.eclipse.xtext.web.server.validation.ValidationResult; | |||
20 | import tools.refinery.language.model.problem.Problem; | 18 | import tools.refinery.language.model.problem.Problem; |
21 | import tools.refinery.language.semantics.metadata.MetadataCreator; | 19 | import tools.refinery.language.semantics.metadata.MetadataCreator; |
22 | import tools.refinery.language.semantics.model.ModelInitializer; | 20 | import tools.refinery.language.semantics.model.ModelInitializer; |
23 | import tools.refinery.language.semantics.model.SemanticsUtils; | ||
24 | import tools.refinery.language.semantics.model.TracedException; | 21 | import tools.refinery.language.semantics.model.TracedException; |
25 | import tools.refinery.store.dse.propagation.PropagationAdapter; | 22 | import tools.refinery.store.dse.propagation.PropagationAdapter; |
26 | import tools.refinery.store.map.Cursor; | ||
27 | import tools.refinery.store.model.Model; | ||
28 | import tools.refinery.store.model.ModelStore; | 23 | import tools.refinery.store.model.ModelStore; |
29 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; | 24 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; |
30 | import tools.refinery.store.reasoning.ReasoningAdapter; | 25 | import tools.refinery.store.reasoning.ReasoningAdapter; |
31 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; | 26 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; |
32 | import tools.refinery.store.reasoning.literal.Concreteness; | 27 | import tools.refinery.store.reasoning.literal.Concreteness; |
33 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
34 | import tools.refinery.store.reasoning.translator.TranslationException; | 28 | import tools.refinery.store.reasoning.translator.TranslationException; |
35 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; | ||
36 | import tools.refinery.store.tuple.Tuple; | ||
37 | import tools.refinery.store.util.CancellationToken; | 29 | import tools.refinery.store.util.CancellationToken; |
38 | 30 | ||
39 | import java.util.ArrayList; | 31 | import java.util.ArrayList; |
40 | import java.util.TreeMap; | ||
41 | import java.util.concurrent.Callable; | 32 | import java.util.concurrent.Callable; |
42 | 33 | ||
43 | class SemanticsWorker implements Callable<SemanticsResult> { | 34 | class 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 | ||
14 | import java.lang.invoke.MethodHandle; | 14 | import java.lang.invoke.MethodHandle; |
15 | import java.lang.invoke.MethodHandles; | 15 | import java.lang.invoke.MethodHandles; |
16 | import java.util.Collections; | ||
17 | import java.util.HashMap; | ||
18 | import java.util.Map; | ||
16 | import java.util.Optional; | 19 | import java.util.Optional; |
17 | import java.util.concurrent.ExecutorService; | 20 | import java.util.concurrent.ExecutorService; |
18 | import java.util.concurrent.Executors; | 21 | import java.util.concurrent.Executors; |
22 | import java.util.concurrent.ScheduledExecutorService; | ||
19 | import java.util.concurrent.ThreadFactory; | 23 | import java.util.concurrent.ThreadFactory; |
20 | import java.util.concurrent.atomic.AtomicInteger; | 24 | import 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 | */ |
6 | package tools.refinery.language.web.xtext.server.push; | 6 | package tools.refinery.language.web.xtext.server.push; |
7 | 7 | ||
8 | import com.google.common.base.Optional; | ||
8 | import com.google.inject.Inject; | 9 | import com.google.inject.Inject; |
9 | import org.eclipse.xtext.web.server.IServiceContext; | 10 | import org.eclipse.xtext.web.server.IServiceContext; |
11 | import org.eclipse.xtext.web.server.InvalidRequestException; | ||
10 | import org.eclipse.xtext.web.server.XtextServiceDispatcher; | 12 | import org.eclipse.xtext.web.server.XtextServiceDispatcher; |
11 | import org.eclipse.xtext.web.server.model.PrecomputedServiceRegistry; | 13 | import org.eclipse.xtext.web.server.model.PrecomputedServiceRegistry; |
12 | import org.eclipse.xtext.web.server.model.XtextWebDocument; | 14 | import org.eclipse.xtext.web.server.model.XtextWebDocument; |
13 | 15 | ||
14 | import com.google.inject.Singleton; | 16 | import com.google.inject.Singleton; |
15 | 17 | ||
18 | import tools.refinery.language.web.generator.ModelGenerationService; | ||
16 | import tools.refinery.language.web.semantics.SemanticsService; | 19 | import tools.refinery.language.web.semantics.SemanticsService; |
17 | import tools.refinery.language.web.xtext.server.SubscribingServiceContext; | 20 | import 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; | |||
13 | import org.eclipse.xtext.web.server.model.XtextWebDocument; | 13 | import org.eclipse.xtext.web.server.model.XtextWebDocument; |
14 | import org.slf4j.Logger; | 14 | import org.slf4j.Logger; |
15 | import org.slf4j.LoggerFactory; | 15 | import org.slf4j.LoggerFactory; |
16 | import tools.refinery.language.web.generator.ModelGenerationManager; | ||
16 | import tools.refinery.language.web.xtext.server.ResponseHandlerException; | 17 | import tools.refinery.language.web.xtext.server.ResponseHandlerException; |
17 | 18 | ||
18 | import java.util.ArrayList; | 19 | import java.util.ArrayList; |
19 | import java.util.HashMap; | ||
20 | import java.util.List; | 20 | import java.util.List; |
21 | import java.util.Map; | ||
22 | 21 | ||
23 | public class PushWebDocument extends XtextWebDocument { | 22 | public 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 | } |