diff options
Diffstat (limited to 'subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/InterpreterEngineImpl.java')
-rw-r--r-- | subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/InterpreterEngineImpl.java | 689 |
1 files changed, 689 insertions, 0 deletions
diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/InterpreterEngineImpl.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/InterpreterEngineImpl.java new file mode 100644 index 00000000..8a2e4c5e --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/InterpreterEngineImpl.java | |||
@@ -0,0 +1,689 @@ | |||
1 | /******************************************************************************* | ||
2 | * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro | ||
3 | * Copyright (c) 2023 The Refinery Authors <https://refinery.tools> | ||
4 | * This program and the accompanying materials are made available under the | ||
5 | * terms of the Eclipse Public License v. 2.0 which is available at | ||
6 | * http://www.eclipse.org/legal/epl-v20.html. | ||
7 | * | ||
8 | * SPDX-License-Identifier: EPL-2.0 | ||
9 | *******************************************************************************/ | ||
10 | |||
11 | package tools.refinery.interpreter.internal.apiimpl; | ||
12 | |||
13 | import org.apache.log4j.Logger; | ||
14 | import tools.refinery.interpreter.api.*; | ||
15 | import tools.refinery.interpreter.api.impl.BaseMatcher; | ||
16 | import tools.refinery.interpreter.api.scope.IBaseIndex; | ||
17 | import tools.refinery.interpreter.api.scope.IEngineContext; | ||
18 | import tools.refinery.interpreter.api.scope.IIndexingErrorListener; | ||
19 | import tools.refinery.interpreter.api.scope.QueryScope; | ||
20 | import tools.refinery.interpreter.exception.InterpreterException; | ||
21 | import tools.refinery.interpreter.internal.engine.LifecycleProvider; | ||
22 | import tools.refinery.interpreter.internal.engine.ModelUpdateProvider; | ||
23 | import tools.refinery.interpreter.matchers.InterpreterRuntimeException; | ||
24 | import tools.refinery.interpreter.matchers.backend.*; | ||
25 | import tools.refinery.interpreter.matchers.context.IQueryBackendContext; | ||
26 | import tools.refinery.interpreter.matchers.context.IQueryCacheContext; | ||
27 | import tools.refinery.interpreter.matchers.context.IQueryResultProviderAccess; | ||
28 | import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; | ||
29 | import tools.refinery.interpreter.matchers.planning.QueryProcessingException; | ||
30 | import tools.refinery.interpreter.matchers.psystem.analysis.QueryAnalyzer; | ||
31 | import tools.refinery.interpreter.matchers.psystem.queries.PQueries; | ||
32 | import tools.refinery.interpreter.matchers.psystem.queries.PQuery; | ||
33 | import tools.refinery.interpreter.matchers.util.CollectionsFactory; | ||
34 | import tools.refinery.interpreter.matchers.util.IMultiLookup; | ||
35 | import tools.refinery.interpreter.matchers.util.Preconditions; | ||
36 | import tools.refinery.interpreter.util.InterpreterLoggingUtil; | ||
37 | |||
38 | import java.lang.ref.WeakReference; | ||
39 | import java.lang.reflect.InvocationTargetException; | ||
40 | import java.util.*; | ||
41 | import java.util.concurrent.Callable; | ||
42 | import java.util.function.Supplier; | ||
43 | import java.util.stream.Collectors; | ||
44 | |||
45 | /** | ||
46 | * A Refinery Interpreter engine back-end (implementation) | ||
47 | * | ||
48 | * @author Bergmann Gábor | ||
49 | */ | ||
50 | public final class InterpreterEngineImpl extends AdvancedInterpreterEngine | ||
51 | implements IQueryBackendHintProvider, IQueryCacheContext, IQueryResultProviderAccess { | ||
52 | |||
53 | /** | ||
54 | * | ||
55 | */ | ||
56 | private static final String ERROR_ACCESSING_BACKEND = "Error while accessing query evaluator backend"; | ||
57 | /** | ||
58 | * | ||
59 | */ | ||
60 | private static final String QUERY_ON_DISPOSED_ENGINE_MESSAGE = "Cannot evaluate query on disposed engine!"; | ||
61 | |||
62 | /** | ||
63 | * The model to which the engine is attached. | ||
64 | */ | ||
65 | private final QueryScope scope; | ||
66 | |||
67 | /** | ||
68 | * The context of the engine, provided by the scope. | ||
69 | */ | ||
70 | private final IEngineContext engineContext; | ||
71 | |||
72 | /** | ||
73 | * Initialized matchers for each query | ||
74 | */ | ||
75 | private final IMultiLookup<IQuerySpecification<? extends InterpreterMatcher<?>>, InterpreterMatcher<?>> matchers = | ||
76 | CollectionsFactory.createMultiLookup(Object.class, CollectionsFactory.MemoryType.SETS, Object.class); | ||
77 | |||
78 | /** | ||
79 | * The RETE and other pattern matcher implementations of the Refinery Interpreter engine. | ||
80 | */ | ||
81 | private final Map<IQueryBackendFactory, IQueryBackend> queryBackends = Collections.synchronizedMap(new HashMap<>()); | ||
82 | |||
83 | /** | ||
84 | * The current engine default hints | ||
85 | */ | ||
86 | private final InterpreterEngineOptions engineOptions; | ||
87 | |||
88 | /** | ||
89 | * Common query analysis provided to backends | ||
90 | */ | ||
91 | private QueryAnalyzer queryAnalyzer; | ||
92 | |||
93 | /** | ||
94 | * true if message delivery is currently delayed, false otherwise | ||
95 | */ | ||
96 | private boolean delayMessageDelivery = true; | ||
97 | |||
98 | private final LifecycleProvider lifecycleProvider; | ||
99 | private final ModelUpdateProvider modelUpdateProvider; | ||
100 | private Logger logger; | ||
101 | private boolean disposed = false; | ||
102 | |||
103 | /** | ||
104 | * @param scope | ||
105 | * @param engineDefaultHint | ||
106 | * @since 1.4 | ||
107 | */ | ||
108 | public InterpreterEngineImpl(QueryScope scope, | ||
109 | InterpreterEngineOptions engineOptions) { | ||
110 | super(); | ||
111 | this.scope = scope; | ||
112 | this.lifecycleProvider = new LifecycleProvider(this, getLogger()); | ||
113 | this.modelUpdateProvider = new ModelUpdateProvider(this, getLogger()); | ||
114 | this.engineContext = scope.createEngineContext(this, taintListener, getLogger()); | ||
115 | |||
116 | if (engineOptions != null) { | ||
117 | this.engineOptions = engineOptions; | ||
118 | } else { | ||
119 | this.engineOptions = InterpreterEngineOptions.getDefault(); | ||
120 | } | ||
121 | |||
122 | } | ||
123 | |||
124 | /** | ||
125 | * @param manager | ||
126 | * null if unmanaged | ||
127 | * @param scope | ||
128 | * @param engineDefaultHint | ||
129 | */ | ||
130 | public InterpreterEngineImpl(QueryScope scope) { | ||
131 | this(scope, InterpreterEngineOptions.getDefault()); | ||
132 | } | ||
133 | |||
134 | @Override | ||
135 | public boolean isUpdatePropagationDelayed() { | ||
136 | return this.delayMessageDelivery; | ||
137 | } | ||
138 | |||
139 | @Override | ||
140 | public <V> V delayUpdatePropagation(Callable<V> callable) throws InvocationTargetException { | ||
141 | if (!delayMessageDelivery) { | ||
142 | throw new IllegalStateException("Trying to delay propagation while changes are being flushed"); | ||
143 | } | ||
144 | try { | ||
145 | return callable.call(); | ||
146 | } catch (Exception e) { | ||
147 | throw new InvocationTargetException(e); | ||
148 | } | ||
149 | } | ||
150 | |||
151 | @Override | ||
152 | public void flushChanges() { | ||
153 | if (!delayMessageDelivery) { | ||
154 | throw new IllegalStateException("Trying to flush changes while changes are already being flushed"); | ||
155 | } | ||
156 | delayMessageDelivery = false; | ||
157 | try { | ||
158 | flushAllBackends(); | ||
159 | } finally { | ||
160 | delayMessageDelivery = true; | ||
161 | } | ||
162 | } | ||
163 | |||
164 | private void flushAllBackends() { | ||
165 | for (IQueryBackend backend : this.queryBackends.values()) { | ||
166 | backend.flushUpdates(); | ||
167 | } | ||
168 | } | ||
169 | |||
170 | @Override | ||
171 | public <T> T withFlushingChanges(Supplier<T> callback) { | ||
172 | if (!delayMessageDelivery) { | ||
173 | return callback.get(); | ||
174 | } | ||
175 | delayMessageDelivery = false; | ||
176 | try { | ||
177 | flushAllBackends(); | ||
178 | return callback.get(); | ||
179 | } finally { | ||
180 | delayMessageDelivery = true; | ||
181 | } | ||
182 | } | ||
183 | |||
184 | @Override | ||
185 | public Set<? extends InterpreterMatcher<? extends IPatternMatch>> getCurrentMatchers() { | ||
186 | return matchers.distinctValuesStream().collect(Collectors.toSet()); | ||
187 | } | ||
188 | |||
189 | @Override | ||
190 | public <Matcher extends InterpreterMatcher<? extends IPatternMatch>> Matcher getMatcher( | ||
191 | IQuerySpecification<Matcher> querySpecification) { | ||
192 | return getMatcher(querySpecification, null); | ||
193 | } | ||
194 | |||
195 | @Override | ||
196 | public <Matcher extends InterpreterMatcher<? extends IPatternMatch>> Matcher getMatcher( | ||
197 | IQuerySpecification<Matcher> querySpecification, QueryEvaluationHint optionalEvaluationHints) { | ||
198 | return withFlushingChanges(() -> { | ||
199 | IMatcherCapability capability = getRequestedCapability(querySpecification, optionalEvaluationHints); | ||
200 | Matcher matcher = doGetExistingMatcher(querySpecification, capability); | ||
201 | if (matcher != null) { | ||
202 | return matcher; | ||
203 | } | ||
204 | matcher = querySpecification.instantiate(); | ||
205 | |||
206 | BaseMatcher<?> baseMatcher = (BaseMatcher<?>) matcher; | ||
207 | ((QueryResultWrapper) baseMatcher).setBackend(this, | ||
208 | getResultProvider(querySpecification, optionalEvaluationHints), capability); | ||
209 | internalRegisterMatcher(querySpecification, baseMatcher); | ||
210 | return matcher; | ||
211 | }); | ||
212 | } | ||
213 | |||
214 | @Override | ||
215 | public <Matcher extends InterpreterMatcher<? extends IPatternMatch>> Matcher getExistingMatcher( | ||
216 | IQuerySpecification<Matcher> querySpecification) { | ||
217 | return getExistingMatcher(querySpecification, null); | ||
218 | } | ||
219 | |||
220 | @Override | ||
221 | public <Matcher extends InterpreterMatcher<? extends IPatternMatch>> Matcher getExistingMatcher( | ||
222 | IQuerySpecification<Matcher> querySpecification, QueryEvaluationHint optionalOverrideHints) { | ||
223 | return doGetExistingMatcher(querySpecification, getRequestedCapability(querySpecification, optionalOverrideHints)); | ||
224 | } | ||
225 | |||
226 | @SuppressWarnings("unchecked") | ||
227 | private <Matcher extends InterpreterMatcher<? extends IPatternMatch>> Matcher doGetExistingMatcher( | ||
228 | IQuerySpecification<Matcher> querySpecification, IMatcherCapability requestedCapability) { | ||
229 | for (InterpreterMatcher<?> matcher : matchers.lookupOrEmpty(querySpecification)) { | ||
230 | BaseMatcher<?> baseMatcher = (BaseMatcher<?>) matcher; | ||
231 | if (baseMatcher.getCapabilities().canBeSubstitute(requestedCapability)) | ||
232 | return (Matcher) matcher; | ||
233 | } | ||
234 | return null; | ||
235 | } | ||
236 | |||
237 | @Override | ||
238 | public InterpreterMatcher<? extends IPatternMatch> getMatcher(String patternFQN) { | ||
239 | throw new UnsupportedOperationException("Query specification registry is not available"); | ||
240 | } | ||
241 | |||
242 | @Override | ||
243 | public IBaseIndex getBaseIndex() { | ||
244 | return engineContext.getBaseIndex(); | ||
245 | } | ||
246 | |||
247 | public final Logger getLogger() { | ||
248 | if (logger == null) { | ||
249 | final int hash = System.identityHashCode(this); | ||
250 | logger = Logger.getLogger(InterpreterLoggingUtil.getLogger(InterpreterEngine.class).getName() + "." + hash); | ||
251 | if (logger == null) | ||
252 | throw new AssertionError( | ||
253 | "Configuration error: unable to create Refinery Interpreter runtime logger for engine " + hash); | ||
254 | } | ||
255 | return logger; | ||
256 | } | ||
257 | |||
258 | ///////////////// internal stuff ////////////// | ||
259 | private void internalRegisterMatcher(IQuerySpecification<?> querySpecification, InterpreterMatcher<?> matcher) { | ||
260 | matchers.addPair(querySpecification, matcher); | ||
261 | lifecycleProvider.matcherInstantiated(matcher); | ||
262 | } | ||
263 | |||
264 | /** | ||
265 | * Provides access to the selected query backend component of the Refinery Interpreter engine. | ||
266 | */ | ||
267 | @Override | ||
268 | public IQueryBackend getQueryBackend(IQueryBackendFactory iQueryBackendFactory) { | ||
269 | IQueryBackend iQueryBackend = queryBackends.get(iQueryBackendFactory); | ||
270 | if (iQueryBackend == null) { | ||
271 | // do this first, to make sure the runtime context exists | ||
272 | final IQueryRuntimeContext queryRuntimeContext = engineContext.getQueryRuntimeContext(); | ||
273 | |||
274 | // maybe the backend has been created in the meantime when the indexer was initialized and queried for | ||
275 | // derived features | ||
276 | // no need to instantiate a new backend in that case | ||
277 | iQueryBackend = queryBackends.get(iQueryBackendFactory); | ||
278 | if (iQueryBackend == null) { | ||
279 | |||
280 | // need to instantiate the backend | ||
281 | iQueryBackend = iQueryBackendFactory.create(new IQueryBackendContext() { | ||
282 | |||
283 | @Override | ||
284 | public IQueryRuntimeContext getRuntimeContext() { | ||
285 | return queryRuntimeContext; | ||
286 | } | ||
287 | |||
288 | @Override | ||
289 | public IQueryCacheContext getQueryCacheContext() { | ||
290 | return InterpreterEngineImpl.this; | ||
291 | } | ||
292 | |||
293 | @Override | ||
294 | public Logger getLogger() { | ||
295 | return logger; | ||
296 | } | ||
297 | |||
298 | @Override | ||
299 | public IQueryBackendHintProvider getHintProvider() { | ||
300 | return InterpreterEngineImpl.this; | ||
301 | } | ||
302 | |||
303 | @Override | ||
304 | public IQueryResultProviderAccess getResultProviderAccess() { | ||
305 | return InterpreterEngineImpl.this; | ||
306 | } | ||
307 | |||
308 | @Override | ||
309 | public QueryAnalyzer getQueryAnalyzer() { | ||
310 | if (queryAnalyzer == null) | ||
311 | queryAnalyzer = new QueryAnalyzer(queryRuntimeContext.getMetaContext()); | ||
312 | return queryAnalyzer; | ||
313 | } | ||
314 | |||
315 | @Override | ||
316 | public boolean areUpdatesDelayed() { | ||
317 | return InterpreterEngineImpl.this.delayMessageDelivery; | ||
318 | } | ||
319 | |||
320 | @Override | ||
321 | public IMatcherCapability getRequiredMatcherCapability(PQuery query, | ||
322 | QueryEvaluationHint hint) { | ||
323 | return engineOptions.getQueryBackendFactory(hint).calculateRequiredCapability(query, hint); | ||
324 | } | ||
325 | |||
326 | |||
327 | |||
328 | }); | ||
329 | queryBackends.put(iQueryBackendFactory, iQueryBackend); | ||
330 | } | ||
331 | } | ||
332 | return iQueryBackend; | ||
333 | } | ||
334 | |||
335 | ///////////////// advanced stuff ///////////// | ||
336 | |||
337 | @Override | ||
338 | public void dispose() { | ||
339 | wipe(); | ||
340 | |||
341 | this.disposed = true; | ||
342 | |||
343 | // called before base index disposal to allow removal of base listeners | ||
344 | lifecycleProvider.engineDisposed(); | ||
345 | |||
346 | try { | ||
347 | engineContext.dispose(); | ||
348 | } catch (IllegalStateException ex) { | ||
349 | getLogger().warn( | ||
350 | "The base index could not be disposed along with the Refinery Interpreter engine, as there are " + | ||
351 | "still active listeners on it."); | ||
352 | } | ||
353 | } | ||
354 | |||
355 | @Override | ||
356 | public void wipe() { | ||
357 | for (IQueryBackend backend : queryBackends.values()) { | ||
358 | backend.dispose(); | ||
359 | } | ||
360 | queryBackends.clear(); | ||
361 | matchers.clear(); | ||
362 | queryAnalyzer = null; | ||
363 | lifecycleProvider.engineWiped(); | ||
364 | } | ||
365 | |||
366 | /** | ||
367 | * Indicates whether the engine is in a tainted, inconsistent state. | ||
368 | */ | ||
369 | private boolean tainted = false; | ||
370 | private IIndexingErrorListener taintListener = new SelfTaintListener(this); | ||
371 | |||
372 | private static class SelfTaintListener implements IIndexingErrorListener { | ||
373 | WeakReference<InterpreterEngineImpl> queryEngineRef; | ||
374 | |||
375 | public SelfTaintListener(InterpreterEngineImpl queryEngine) { | ||
376 | this.queryEngineRef = new WeakReference<InterpreterEngineImpl>(queryEngine); | ||
377 | } | ||
378 | |||
379 | public void engineBecameTainted(String description, Throwable t) { | ||
380 | final InterpreterEngineImpl queryEngine = queryEngineRef.get(); | ||
381 | if (queryEngine != null) { | ||
382 | queryEngine.tainted = true; | ||
383 | queryEngine.lifecycleProvider.engineBecameTainted(description, t); | ||
384 | } | ||
385 | } | ||
386 | |||
387 | private boolean noTaintDetectedYet = true; | ||
388 | |||
389 | protected void notifyTainted(String description, Throwable t) { | ||
390 | if (noTaintDetectedYet) { | ||
391 | noTaintDetectedYet = false; | ||
392 | engineBecameTainted(description, t); | ||
393 | } | ||
394 | } | ||
395 | |||
396 | @Override | ||
397 | public void error(String description, Throwable t) { | ||
398 | // Errors does not mean tainting | ||
399 | } | ||
400 | |||
401 | @Override | ||
402 | public void fatal(String description, Throwable t) { | ||
403 | notifyTainted(description, t); | ||
404 | } | ||
405 | } | ||
406 | |||
407 | @Override | ||
408 | public boolean isTainted() { | ||
409 | return tainted; | ||
410 | } | ||
411 | |||
412 | private <Match extends IPatternMatch> IQueryResultProvider getUnderlyingResultProvider( | ||
413 | final BaseMatcher<Match> matcher) { | ||
414 | // IQueryResultProvider resultProvider = reteEngine.accessMatcher(matcher.getSpecification()); | ||
415 | return matcher.backend; | ||
416 | } | ||
417 | |||
418 | @Override | ||
419 | public <Match extends IPatternMatch> void addMatchUpdateListener(final InterpreterMatcher<Match> matcher, | ||
420 | final IMatchUpdateListener<? super Match> listener, boolean fireNow) { | ||
421 | |||
422 | Preconditions.checkArgument(listener != null, "Cannot add null listener!"); | ||
423 | Preconditions.checkArgument(matcher.getEngine() == this, "Cannot register listener for matcher of different engine!"); | ||
424 | Preconditions.checkArgument(!disposed, "Cannot register listener on matcher of disposed engine!"); | ||
425 | |||
426 | final BaseMatcher<Match> bm = (BaseMatcher<Match>) matcher; | ||
427 | |||
428 | final IUpdateable updateDispatcher = (updateElement, isInsertion) -> { | ||
429 | Match match = null; | ||
430 | try { | ||
431 | match = bm.newMatch(updateElement.getElements()); | ||
432 | if (isInsertion) | ||
433 | listener.notifyAppearance(match); | ||
434 | else | ||
435 | listener.notifyDisappearance(match); | ||
436 | } catch (Throwable e) { // NOPMD | ||
437 | if (e instanceof Error) | ||
438 | throw (Error) e; | ||
439 | logger.warn( | ||
440 | String.format( | ||
441 | "The incremental pattern matcher encountered an error during %s a callback on %s of match %s of pattern %s. Error message: %s. (Developer note: %s in %s callback)", | ||
442 | match == null ? "preparing" : "invoking", isInsertion ? "insertion" : "removal", | ||
443 | match == null ? updateElement.toString() : match.prettyPrint(), | ||
444 | matcher.getPatternName(), e.getMessage(), e.getClass().getSimpleName(), listener), | ||
445 | e); | ||
446 | } | ||
447 | |||
448 | }; | ||
449 | |||
450 | IQueryResultProvider resultProvider = getUnderlyingResultProvider(bm); | ||
451 | resultProvider.addUpdateListener(updateDispatcher, listener, fireNow); | ||
452 | } | ||
453 | |||
454 | @Override | ||
455 | public <Match extends IPatternMatch> void removeMatchUpdateListener(InterpreterMatcher<Match> matcher, | ||
456 | IMatchUpdateListener<? super Match> listener) { | ||
457 | Preconditions.checkArgument(listener != null, "Cannot remove null listener!"); | ||
458 | Preconditions.checkArgument(matcher.getEngine() == this, "Cannot remove listener from matcher of different engine!"); | ||
459 | Preconditions.checkArgument(!disposed, "Cannot remove listener from matcher of disposed engine!"); | ||
460 | |||
461 | final BaseMatcher<Match> bm = (BaseMatcher<Match>) matcher; | ||
462 | |||
463 | try { | ||
464 | IQueryResultProvider resultProvider = getUnderlyingResultProvider(bm); | ||
465 | resultProvider.removeUpdateListener(listener); | ||
466 | } catch (Exception e) { | ||
467 | logger.error( | ||
468 | "Error while removing listener " + listener + " from the matcher of " + matcher.getPatternName(), | ||
469 | e); | ||
470 | } | ||
471 | } | ||
472 | |||
473 | @Override | ||
474 | public void addModelUpdateListener(InterpreterModelUpdateListener listener) { | ||
475 | modelUpdateProvider.addListener(listener); | ||
476 | } | ||
477 | |||
478 | @Override | ||
479 | public void removeModelUpdateListener(InterpreterModelUpdateListener listener) { | ||
480 | modelUpdateProvider.removeListener(listener); | ||
481 | } | ||
482 | |||
483 | @Override | ||
484 | public void addLifecycleListener(InterpreterEngineLifecycleListener listener) { | ||
485 | lifecycleProvider.addListener(listener); | ||
486 | } | ||
487 | |||
488 | @Override | ||
489 | public void removeLifecycleListener(InterpreterEngineLifecycleListener listener) { | ||
490 | lifecycleProvider.removeListener(listener); | ||
491 | } | ||
492 | |||
493 | /** | ||
494 | * Returns an internal interface towards the query backend to feed the matcher with results. | ||
495 | * | ||
496 | * @param query | ||
497 | * the pattern for which the result provider should be delivered | ||
498 | * | ||
499 | * @throws InterpreterRuntimeException | ||
500 | */ | ||
501 | public IQueryResultProvider getResultProvider(IQuerySpecification<?> query) { | ||
502 | Preconditions.checkState(!disposed, QUERY_ON_DISPOSED_ENGINE_MESSAGE); | ||
503 | |||
504 | return getResultProviderInternal(query, null); | ||
505 | } | ||
506 | |||
507 | /** | ||
508 | * Returns an internal interface towards the query backend to feed the matcher with results. | ||
509 | * | ||
510 | * @param query | ||
511 | * the pattern for which the result provider should be delivered | ||
512 | * | ||
513 | * @throws InterpreterRuntimeException | ||
514 | */ | ||
515 | public IQueryResultProvider getResultProvider(IQuerySpecification<?> query, QueryEvaluationHint hint) { | ||
516 | Preconditions.checkState(!disposed, QUERY_ON_DISPOSED_ENGINE_MESSAGE); | ||
517 | |||
518 | return getResultProviderInternal(query, hint); | ||
519 | } | ||
520 | |||
521 | /** | ||
522 | * This method returns the result provider exactly as described by the passed hint. Query cannot be null! Use | ||
523 | * {@link #getQueryEvaluationHint(IQuerySpecification, QueryEvaluationHint)} before passing a hint to this method to | ||
524 | * make sure engine and query specific hints are correctly applied. | ||
525 | * | ||
526 | * @throws InterpreterRuntimeException | ||
527 | */ | ||
528 | private IQueryResultProvider getResultProviderInternal(IQuerySpecification<?> query, QueryEvaluationHint hint) { | ||
529 | return getResultProviderInternal(query.getInternalQueryRepresentation(), hint); | ||
530 | } | ||
531 | |||
532 | /** | ||
533 | * This method returns the result provider exactly as described by the passed hint. Query cannot be null! Use | ||
534 | * {@link #getQueryEvaluationHint(IQuerySpecification, QueryEvaluationHint)} before passing a hint to this method to | ||
535 | * make sure engine and query specific hints are correctly applied. | ||
536 | * | ||
537 | * @throws InterpreterRuntimeException | ||
538 | */ | ||
539 | private IQueryResultProvider getResultProviderInternal(PQuery query, QueryEvaluationHint hint) { | ||
540 | Preconditions.checkArgument(query != null, "Query cannot be null!"); | ||
541 | Preconditions.checkArgument(query.getStatus() != PQuery.PQueryStatus.ERROR, "Cannot initialize a result provider for the erronoues query `%s`.", query.getSimpleName()); | ||
542 | final IQueryBackend backend = getQueryBackend(engineOptions.getQueryBackendFactory(getQueryEvaluationHint(query, hint))); | ||
543 | return backend.getResultProvider(query, hint); | ||
544 | } | ||
545 | |||
546 | /** | ||
547 | * Returns the query backend (influenced by the hint system), even if it is a non-caching backend. | ||
548 | * | ||
549 | * @throws InterpreterRuntimeException | ||
550 | */ | ||
551 | private IQueryBackend getQueryBackend(PQuery query) { | ||
552 | final IQueryBackendFactory factory = engineOptions.getQueryBackendFactory(getQueryEvaluationHint(query)); | ||
553 | return getQueryBackend(factory); | ||
554 | } | ||
555 | |||
556 | /** | ||
557 | * Returns a caching query backend (influenced by the hint system). | ||
558 | * | ||
559 | * @throws InterpreterRuntimeException | ||
560 | */ | ||
561 | private IQueryBackend getCachingQueryBackend(PQuery query) { | ||
562 | IQueryBackend regularBackend = getQueryBackend(query); | ||
563 | if (regularBackend.isCaching()) | ||
564 | return regularBackend; | ||
565 | else | ||
566 | return getQueryBackend(engineOptions.getDefaultCachingBackendFactory()); | ||
567 | } | ||
568 | |||
569 | @Override | ||
570 | public boolean isResultCached(PQuery query) { | ||
571 | try { | ||
572 | return null != getCachingQueryBackend(query).peekExistingResultProvider(query); | ||
573 | } catch (InterpreterException iqe) { | ||
574 | getLogger().error(ERROR_ACCESSING_BACKEND, iqe); | ||
575 | return false; | ||
576 | } | ||
577 | } | ||
578 | |||
579 | @Override | ||
580 | public IQueryResultProvider getCachingResultProvider(PQuery query) { | ||
581 | try { | ||
582 | return getCachingQueryBackend(query).getResultProvider(query); | ||
583 | } catch (InterpreterException iqe) { | ||
584 | getLogger().error(ERROR_ACCESSING_BACKEND, iqe); | ||
585 | throw iqe; | ||
586 | } | ||
587 | } | ||
588 | |||
589 | private QueryEvaluationHint getEngineDefaultHint() { | ||
590 | return engineOptions.getEngineDefaultHints(); | ||
591 | } | ||
592 | |||
593 | @Override | ||
594 | public QueryEvaluationHint getQueryEvaluationHint(PQuery query) { | ||
595 | return getEngineDefaultHint().overrideBy(query.getEvaluationHints()); | ||
596 | } | ||
597 | |||
598 | private QueryEvaluationHint getQueryEvaluationHint(IQuerySpecification<?> querySpecification, | ||
599 | QueryEvaluationHint optionalOverrideHints) { | ||
600 | return getQueryEvaluationHint(querySpecification.getInternalQueryRepresentation()) | ||
601 | .overrideBy(optionalOverrideHints); | ||
602 | } | ||
603 | |||
604 | private QueryEvaluationHint getQueryEvaluationHint(PQuery query, QueryEvaluationHint optionalOverrideHints) { | ||
605 | return getQueryEvaluationHint(query).overrideBy(optionalOverrideHints); | ||
606 | } | ||
607 | |||
608 | private IMatcherCapability getRequestedCapability(IQuerySpecification<?> querySpecification, | ||
609 | QueryEvaluationHint optionalOverrideHints) { | ||
610 | final QueryEvaluationHint hint = getQueryEvaluationHint(querySpecification, optionalOverrideHints); | ||
611 | return engineOptions.getQueryBackendFactory(hint) | ||
612 | .calculateRequiredCapability(querySpecification.getInternalQueryRepresentation(), hint); | ||
613 | } | ||
614 | |||
615 | @Override | ||
616 | public void prepareGroup(IQueryGroup queryGroup, final QueryEvaluationHint optionalEvaluationHints) { | ||
617 | try { | ||
618 | Preconditions.checkState(!disposed, QUERY_ON_DISPOSED_ENGINE_MESSAGE); | ||
619 | |||
620 | final Set<IQuerySpecification<?>> specifications = new HashSet<IQuerySpecification<?>>( | ||
621 | queryGroup.getSpecifications()); | ||
622 | final Collection<PQuery> patterns = specifications.stream().map( | ||
623 | IQuerySpecification::getInternalQueryRepresentation).collect(Collectors.toList()); | ||
624 | patterns.forEach(PQuery::ensureInitialized); | ||
625 | |||
626 | Collection<String> erroneousPatterns = patterns.stream(). | ||
627 | filter(PQueries.queryStatusPredicate(PQuery.PQueryStatus.ERROR)). | ||
628 | map(PQuery::getFullyQualifiedName). | ||
629 | collect(Collectors.toList()); | ||
630 | Preconditions.checkState(erroneousPatterns.isEmpty(), "Erroneous query(s) found: %s", | ||
631 | erroneousPatterns.stream().collect(Collectors.joining(", "))); | ||
632 | |||
633 | // TODO maybe do some smarter preparation per backend? | ||
634 | try { | ||
635 | engineContext.getBaseIndex().coalesceTraversals(new Callable<Void>() { | ||
636 | @Override | ||
637 | public Void call() throws Exception { | ||
638 | for (IQuerySpecification<?> query : specifications) { | ||
639 | getResultProviderInternal(query, optionalEvaluationHints); | ||
640 | } | ||
641 | return null; | ||
642 | } | ||
643 | }); | ||
644 | } catch (InvocationTargetException ex) { | ||
645 | final Throwable cause = ex.getCause(); | ||
646 | if (cause instanceof QueryProcessingException) | ||
647 | throw (QueryProcessingException) cause; | ||
648 | if (cause instanceof InterpreterException) | ||
649 | throw (InterpreterException) cause; | ||
650 | if (cause instanceof RuntimeException) | ||
651 | throw (RuntimeException) cause; | ||
652 | assert (false); | ||
653 | } | ||
654 | } catch (QueryProcessingException e) { | ||
655 | throw new InterpreterException(e); | ||
656 | } | ||
657 | } | ||
658 | |||
659 | @Override | ||
660 | public QueryScope getScope() { | ||
661 | return scope; | ||
662 | } | ||
663 | |||
664 | @Override | ||
665 | public InterpreterEngineOptions getEngineOptions() { | ||
666 | return engineOptions; | ||
667 | } | ||
668 | |||
669 | @Override | ||
670 | public IQueryResultProvider getResultProviderOfMatcher(InterpreterMatcher<? extends IPatternMatch> matcher) { | ||
671 | return ((QueryResultWrapper) matcher).backend; | ||
672 | } | ||
673 | |||
674 | @Override | ||
675 | public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint overrideHints) { | ||
676 | try { | ||
677 | return getResultProviderInternal(query, overrideHints); | ||
678 | } catch (InterpreterException e) { | ||
679 | getLogger().error(ERROR_ACCESSING_BACKEND, e); | ||
680 | throw e; | ||
681 | } | ||
682 | } | ||
683 | |||
684 | @Override | ||
685 | public boolean isDisposed() { | ||
686 | return disposed; | ||
687 | } | ||
688 | |||
689 | } | ||