diff options
Diffstat (limited to 'subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java')
-rw-r--r-- | subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java | 579 |
1 files changed, 579 insertions, 0 deletions
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java new file mode 100644 index 00000000..9bd499f4 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java | |||
@@ -0,0 +1,579 @@ | |||
1 | /******************************************************************************* | ||
2 | * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro | ||
3 | * This program and the accompanying materials are made available under the | ||
4 | * terms of the Eclipse Public License v. 2.0 which is available at | ||
5 | * http://www.eclipse.org/legal/epl-v20.html. | ||
6 | * | ||
7 | * SPDX-License-Identifier: EPL-2.0 | ||
8 | *******************************************************************************/ | ||
9 | |||
10 | package tools.refinery.viatra.runtime.rete.matcher; | ||
11 | |||
12 | import java.lang.reflect.InvocationTargetException; | ||
13 | import java.util.Collection; | ||
14 | import java.util.LinkedList; | ||
15 | import java.util.Map; | ||
16 | import java.util.concurrent.Callable; | ||
17 | |||
18 | import org.apache.log4j.Logger; | ||
19 | import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; | ||
20 | import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; | ||
21 | import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; | ||
22 | import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; | ||
23 | import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; | ||
24 | import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; | ||
25 | import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; | ||
26 | import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; | ||
27 | import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; | ||
28 | import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; | ||
29 | import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; | ||
30 | import tools.refinery.viatra.runtime.rete.boundary.Disconnectable; | ||
31 | import tools.refinery.viatra.runtime.rete.boundary.ReteBoundary; | ||
32 | import tools.refinery.viatra.runtime.rete.construction.RetePatternBuildException; | ||
33 | import tools.refinery.viatra.runtime.rete.construction.plancompiler.ReteRecipeCompiler; | ||
34 | import tools.refinery.viatra.runtime.rete.index.Indexer; | ||
35 | import tools.refinery.viatra.runtime.rete.network.Network; | ||
36 | import tools.refinery.viatra.runtime.rete.network.NodeProvisioner; | ||
37 | import tools.refinery.viatra.runtime.rete.network.ReteContainer; | ||
38 | import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; | ||
39 | |||
40 | /** | ||
41 | * @author Gabor Bergmann | ||
42 | * | ||
43 | */ | ||
44 | public class ReteEngine implements IQueryBackend { | ||
45 | |||
46 | protected Network reteNet; | ||
47 | protected final int reteThreads; | ||
48 | protected ReteBoundary boundary; | ||
49 | |||
50 | /** | ||
51 | * @since 2.2 | ||
52 | */ | ||
53 | protected final boolean deleteAndRederiveEvaluation; | ||
54 | /** | ||
55 | * @since 2.4 | ||
56 | */ | ||
57 | protected final TimelyConfiguration timelyConfiguration; | ||
58 | |||
59 | private IQueryBackendContext context; | ||
60 | private Logger logger; | ||
61 | protected IQueryRuntimeContext runtimeContext; | ||
62 | |||
63 | protected Collection<Disconnectable> disconnectables; | ||
64 | |||
65 | protected Map<PQuery, RetePatternMatcher> matchers; | ||
66 | |||
67 | protected ReteRecipeCompiler compiler; | ||
68 | |||
69 | protected final boolean parallelExecutionEnabled; // TRUE if model manipulation can go on | ||
70 | |||
71 | private boolean disposedOrUninitialized = true; | ||
72 | |||
73 | private HintConfigurator hintConfigurator; | ||
74 | |||
75 | /** | ||
76 | * @param context | ||
77 | * the context of the pattern matcher, conveying all information from the outside world. | ||
78 | * @param reteThreads | ||
79 | * the number of threads to operate the RETE network with; 0 means single-threaded operation, 1 starts an | ||
80 | * asynchronous thread to operate the RETE net, >1 uses multiple RETE containers. | ||
81 | */ | ||
82 | public ReteEngine(IQueryBackendContext context, int reteThreads) { | ||
83 | this(context, reteThreads, false, null); | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * @since 2.4 | ||
88 | */ | ||
89 | public ReteEngine(IQueryBackendContext context, int reteThreads, boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyConfiguration) { | ||
90 | super(); | ||
91 | this.context = context; | ||
92 | this.logger = context.getLogger(); | ||
93 | this.runtimeContext = context.getRuntimeContext(); | ||
94 | this.reteThreads = reteThreads; | ||
95 | this.parallelExecutionEnabled = reteThreads > 0; | ||
96 | this.deleteAndRederiveEvaluation = deleteAndRederiveEvaluation; | ||
97 | this.timelyConfiguration = timelyConfiguration; | ||
98 | initEngine(); | ||
99 | this.compiler = null; | ||
100 | } | ||
101 | |||
102 | /** | ||
103 | * @since 1.6 | ||
104 | */ | ||
105 | public IQueryBackendContext getBackendContext() { | ||
106 | return context; | ||
107 | } | ||
108 | |||
109 | /** | ||
110 | * @since 2.2 | ||
111 | */ | ||
112 | public boolean isDeleteAndRederiveEvaluation() { | ||
113 | return this.deleteAndRederiveEvaluation; | ||
114 | } | ||
115 | |||
116 | /** | ||
117 | * @since 2.4 | ||
118 | */ | ||
119 | public TimelyConfiguration getTimelyConfiguration() { | ||
120 | return this.timelyConfiguration; | ||
121 | } | ||
122 | |||
123 | /** | ||
124 | * initializes engine components | ||
125 | */ | ||
126 | private synchronized void initEngine() { | ||
127 | this.disposedOrUninitialized = false; | ||
128 | this.disconnectables = new LinkedList<Disconnectable>(); | ||
129 | // this.caughtExceptions = new LinkedBlockingQueue<Throwable>(); | ||
130 | |||
131 | |||
132 | this.hintConfigurator = new HintConfigurator(context.getHintProvider()); | ||
133 | |||
134 | this.reteNet = new Network(reteThreads, this); | ||
135 | this.boundary = new ReteBoundary(this); // prerequisite: network | ||
136 | |||
137 | this.matchers = CollectionsFactory.createMap(); | ||
138 | /* this.matchersScoped = new HashMap<PatternDescription, Map<Map<Integer,Scope>,RetePatternMatcher>>(); */ | ||
139 | |||
140 | // prerequisite: network, framework, boundary, disconnectables | ||
141 | //context.subscribeBackendForUpdates(this.boundary); | ||
142 | // prerequisite: boundary, disconnectables | ||
143 | // this.traceListener = context.subscribePatternMatcherForTraceInfluences(this); | ||
144 | |||
145 | } | ||
146 | |||
147 | @Override | ||
148 | public void flushUpdates() { | ||
149 | for (ReteContainer container : this.reteNet.getContainers()) { | ||
150 | container.deliverMessagesSingleThreaded(); | ||
151 | } | ||
152 | } | ||
153 | |||
154 | /** | ||
155 | * deconstructs engine components | ||
156 | */ | ||
157 | private synchronized void deconstructEngine() { | ||
158 | ensureInitialized(); | ||
159 | reteNet.kill(); | ||
160 | |||
161 | //context.unSubscribeBackendFromUpdates(this.boundary); | ||
162 | for (Disconnectable disc : disconnectables) { | ||
163 | disc.disconnect(); | ||
164 | } | ||
165 | |||
166 | this.matchers = null; | ||
167 | this.disconnectables = null; | ||
168 | |||
169 | this.reteNet = null; | ||
170 | this.boundary = null; | ||
171 | |||
172 | this.hintConfigurator = null; | ||
173 | |||
174 | // this.machineListener = new MachineListener(this); // prerequisite: | ||
175 | // framework, disconnectables | ||
176 | // this.traceListener = null; | ||
177 | |||
178 | this.disposedOrUninitialized = true; | ||
179 | } | ||
180 | |||
181 | /** | ||
182 | * Deconstructs the engine to get rid of it finally | ||
183 | */ | ||
184 | public void killEngine() { | ||
185 | deconstructEngine(); | ||
186 | // this.framework = null; | ||
187 | this.compiler = null; | ||
188 | this.logger = null; | ||
189 | } | ||
190 | |||
191 | /** | ||
192 | * Resets the engine to an after-initialization phase | ||
193 | * | ||
194 | */ | ||
195 | public void reset() { | ||
196 | deconstructEngine(); | ||
197 | |||
198 | initEngine(); | ||
199 | |||
200 | compiler.reset(); | ||
201 | } | ||
202 | |||
203 | /** | ||
204 | * Accesses the patternmatcher for a given pattern, constructs one if a matcher is not available yet. | ||
205 | * | ||
206 | * @pre: builder is set. | ||
207 | * @param query | ||
208 | * the pattern to be matched. | ||
209 | * @return a patternmatcher object that can match occurences of the given pattern. | ||
210 | * @throws ViatraQueryRuntimeException | ||
211 | * if construction fails. | ||
212 | */ | ||
213 | public synchronized RetePatternMatcher accessMatcher(final PQuery query) { | ||
214 | ensureInitialized(); | ||
215 | RetePatternMatcher matcher; | ||
216 | // String namespace = gtPattern.getNamespace().getName(); | ||
217 | // String name = gtPattern.getName(); | ||
218 | // String fqn = namespace + "." + name; | ||
219 | matcher = matchers.get(query); | ||
220 | if (matcher == null) { | ||
221 | constructionWrapper(() -> { | ||
222 | RecipeTraceInfo prodNode; | ||
223 | prodNode = boundary.accessProductionTrace(query); | ||
224 | |||
225 | RetePatternMatcher retePatternMatcher = new RetePatternMatcher(ReteEngine.this, | ||
226 | prodNode); | ||
227 | retePatternMatcher.setTag(query); | ||
228 | matchers.put(query, retePatternMatcher); | ||
229 | return null; | ||
230 | }); | ||
231 | matcher = matchers.get(query); | ||
232 | } | ||
233 | |||
234 | executeDelayedCommands(); | ||
235 | |||
236 | return matcher; | ||
237 | } | ||
238 | |||
239 | |||
240 | /** | ||
241 | * Constructs RETE pattern matchers for a collection of patterns, if they are not available yet. Model traversal | ||
242 | * during the whole construction period is coalesced (which may have an effect on performance, depending on the | ||
243 | * matcher context). | ||
244 | * | ||
245 | * @pre: builder is set. | ||
246 | * @param specifications | ||
247 | * the patterns to be matched. | ||
248 | * @throws ViatraQueryRuntimeException | ||
249 | * if construction fails. | ||
250 | */ | ||
251 | public synchronized void buildMatchersCoalesced(final Collection<PQuery> specifications) { | ||
252 | ensureInitialized(); | ||
253 | constructionWrapper(() -> { | ||
254 | for (PQuery specification : specifications) { | ||
255 | boundary.accessProductionNode(specification); | ||
256 | } | ||
257 | return null; | ||
258 | }); | ||
259 | } | ||
260 | |||
261 | /** | ||
262 | * @since 2.4 | ||
263 | */ | ||
264 | public <T> T constructionWrapper(final Callable<T> payload) { | ||
265 | T result = null; | ||
266 | // context.modelReadLock(); | ||
267 | // try { | ||
268 | if (parallelExecutionEnabled) | ||
269 | reteNet.getStructuralChangeLock().lock(); | ||
270 | try { | ||
271 | try { | ||
272 | result = runtimeContext.coalesceTraversals(() -> { | ||
273 | T innerResult = payload.call(); | ||
274 | this.executeDelayedCommands(); | ||
275 | return innerResult; | ||
276 | }); | ||
277 | } catch (InvocationTargetException ex) { | ||
278 | final Throwable cause = ex.getCause(); | ||
279 | if (cause instanceof RetePatternBuildException) | ||
280 | throw (RetePatternBuildException) cause; | ||
281 | if (cause instanceof RuntimeException) | ||
282 | throw (RuntimeException) cause; | ||
283 | assert (false); | ||
284 | } | ||
285 | } finally { | ||
286 | if (parallelExecutionEnabled) | ||
287 | reteNet.getStructuralChangeLock().unlock(); | ||
288 | reteNet.waitForReteTermination(); | ||
289 | } | ||
290 | // } finally { | ||
291 | // context.modelReadUnLock(); | ||
292 | // } | ||
293 | return result; | ||
294 | } | ||
295 | |||
296 | // /** | ||
297 | // * Accesses the patternmatcher for a given pattern with additional scoping, constructs one if | ||
298 | // * a matcher is not available yet. | ||
299 | // * | ||
300 | // * @param gtPattern | ||
301 | // * the pattern to be matched. | ||
302 | // * @param additionalScopeMap | ||
303 | // * additional, optional scopes for the symbolic parameters | ||
304 | // * maps the position of the symbolic parameter to its additional scope (if any) | ||
305 | // * @pre: scope.parent is non-root, i.e. this is a nontrivial constraint | ||
306 | // * use the static method RetePatternMatcher.buildAdditionalScopeMap() to create from PatternCallSignature | ||
307 | // * @return a patternmatcher object that can match occurences of the given | ||
308 | // * pattern. | ||
309 | // * @throws PatternMatcherCompileTimeException | ||
310 | // * if construction fails. | ||
311 | // */ | ||
312 | // public synchronized RetePatternMatcher accessMatcherScoped(PatternDescription gtPattern, Map<Integer, Scope> | ||
313 | // additionalScopeMap) | ||
314 | // throws PatternMatcherCompileTimeException { | ||
315 | // if (additionalScopeMap.isEmpty()) return accessMatcher(gtPattern); | ||
316 | // | ||
317 | // RetePatternMatcher matcher; | ||
318 | // | ||
319 | // Map<Map<Integer, Scope>, RetePatternMatcher> scopes = matchersScoped.get(gtPattern); | ||
320 | // if (scopes == null) { | ||
321 | // scopes = new HashMap<Map<Integer, Scope>, RetePatternMatcher>(); | ||
322 | // matchersScoped.put(gtPattern, scopes); | ||
323 | // } | ||
324 | // | ||
325 | // matcher = scopes.get(additionalScopeMap); | ||
326 | // if (matcher == null) { | ||
327 | // context.modelReadLock(); | ||
328 | // try { | ||
329 | // reteNet.getStructuralChangeLock().lock(); | ||
330 | // try { | ||
331 | // Address<? extends Production> prodNode; | ||
332 | // prodNode = boundary.accessProductionScoped(gtPattern, additionalScopeMap); | ||
333 | // | ||
334 | // matcher = new RetePatternMatcher(this, prodNode); | ||
335 | // scopes.put(additionalScopeMap, matcher); | ||
336 | // } finally { | ||
337 | // reteNet.getStructuralChangeLock().unlock(); | ||
338 | // } | ||
339 | // } finally { | ||
340 | // context.modelReadUnLock(); | ||
341 | // } | ||
342 | // // reteNet.flushUpdates(); | ||
343 | // } | ||
344 | // | ||
345 | // return matcher; | ||
346 | // } | ||
347 | |||
348 | /** | ||
349 | * Returns an indexer that groups the contents of this Production node by their projections to a given mask. | ||
350 | * Designed to be called by a RetePatternMatcher. | ||
351 | * | ||
352 | * @param production | ||
353 | * the production node to be indexed. | ||
354 | * @param mask | ||
355 | * the mask that defines the projection. | ||
356 | * @return the Indexer. | ||
357 | */ | ||
358 | synchronized Indexer accessProjection(RecipeTraceInfo production, TupleMask mask) { | ||
359 | ensureInitialized(); | ||
360 | NodeProvisioner nodeProvisioner = reteNet.getHeadContainer().getProvisioner(); | ||
361 | Indexer result = nodeProvisioner.peekProjectionIndexer(production, mask); | ||
362 | if (result == null) { | ||
363 | result = constructionWrapper(() -> | ||
364 | nodeProvisioner.accessProjectionIndexerOnetime(production, mask) | ||
365 | ); | ||
366 | } | ||
367 | |||
368 | return result; | ||
369 | } | ||
370 | |||
371 | // /** | ||
372 | // * Retrieves the patternmatcher for a given pattern fqn, returns null if | ||
373 | // the matching network hasn't been constructed yet. | ||
374 | // * | ||
375 | // * @param fqn the fully qualified name of the pattern to be matched. | ||
376 | // * @return the previously constructed patternmatcher object that can match | ||
377 | // occurences of the given pattern, or null if it doesn't exist. | ||
378 | // */ | ||
379 | // public RetePatternMatcher getMatcher(String fqn) | ||
380 | // { | ||
381 | // RetePatternMatcher matcher = matchersByFqn.get(fqn); | ||
382 | // if (matcher == null) | ||
383 | // { | ||
384 | // Production prodNode = boundary.getProduction(fqn); | ||
385 | // | ||
386 | // matcher = new RetePatternMatcher(this, prodNode); | ||
387 | // matchersByFqn.put(fqn, matcher); | ||
388 | // } | ||
389 | // | ||
390 | // return matcher; | ||
391 | // } | ||
392 | |||
393 | /** | ||
394 | * @since 2.3 | ||
395 | */ | ||
396 | public void executeDelayedCommands() { | ||
397 | for (final ReteContainer container : this.reteNet.getContainers()) { | ||
398 | container.executeDelayedCommands(); | ||
399 | } | ||
400 | } | ||
401 | |||
402 | /** | ||
403 | * Waits until the pattern matcher is in a steady state and output can be retrieved. | ||
404 | */ | ||
405 | public void settle() { | ||
406 | ensureInitialized(); | ||
407 | reteNet.waitForReteTermination(); | ||
408 | } | ||
409 | |||
410 | /** | ||
411 | * Waits until the pattern matcher is in a steady state and output can be retrieved. When steady state is reached, a | ||
412 | * retrieval action is executed before the steady state ceases. | ||
413 | * | ||
414 | * @param action | ||
415 | * the action to be run when reaching the steady-state. | ||
416 | */ | ||
417 | public void settle(Runnable action) { | ||
418 | ensureInitialized(); | ||
419 | reteNet.waitForReteTermination(action); | ||
420 | } | ||
421 | |||
422 | // /** | ||
423 | // * @return the framework | ||
424 | // */ | ||
425 | // public IFramework getFramework() { | ||
426 | // return framework.get(); | ||
427 | // } | ||
428 | |||
429 | /** | ||
430 | * @return the reteNet | ||
431 | */ | ||
432 | public Network getReteNet() { | ||
433 | ensureInitialized(); | ||
434 | return reteNet; | ||
435 | } | ||
436 | |||
437 | /** | ||
438 | * @return the boundary | ||
439 | */ | ||
440 | public ReteBoundary getBoundary() { | ||
441 | ensureInitialized(); | ||
442 | return boundary; | ||
443 | } | ||
444 | |||
445 | // /** | ||
446 | // * @return the pattern matcher builder | ||
447 | // */ | ||
448 | // public IRetePatternBuilder getBuilder() { | ||
449 | // return builder; | ||
450 | // } | ||
451 | |||
452 | /** | ||
453 | * @param builder | ||
454 | * the pattern matcher builder to set | ||
455 | */ | ||
456 | public void setCompiler(ReteRecipeCompiler builder) { | ||
457 | ensureInitialized(); | ||
458 | this.compiler = builder; | ||
459 | } | ||
460 | |||
461 | // /** | ||
462 | // * @return the manipulationListener | ||
463 | // */ | ||
464 | // public IManipulationListener getManipulationListener() { | ||
465 | // ensureInitialized(); | ||
466 | // return manipulationListener; | ||
467 | // } | ||
468 | |||
469 | // /** | ||
470 | // * @return the traceListener | ||
471 | // */ | ||
472 | // public IPredicateTraceListener geTraceListener() { | ||
473 | // ensureInitialized(); | ||
474 | // return traceListener; | ||
475 | // } | ||
476 | |||
477 | /** | ||
478 | * @param disc | ||
479 | * the new Disconnectable adapter. | ||
480 | */ | ||
481 | public void addDisconnectable(Disconnectable disc) { | ||
482 | ensureInitialized(); | ||
483 | disconnectables.add(disc); | ||
484 | } | ||
485 | |||
486 | /** | ||
487 | * @return the parallelExecutionEnabled | ||
488 | */ | ||
489 | public boolean isParallelExecutionEnabled() { | ||
490 | return parallelExecutionEnabled; | ||
491 | } | ||
492 | |||
493 | |||
494 | public Logger getLogger() { | ||
495 | ensureInitialized(); | ||
496 | return logger; | ||
497 | } | ||
498 | |||
499 | public IQueryRuntimeContext getRuntimeContext() { | ||
500 | ensureInitialized(); | ||
501 | return runtimeContext; | ||
502 | } | ||
503 | |||
504 | public ReteRecipeCompiler getCompiler() { | ||
505 | ensureInitialized(); | ||
506 | return compiler; | ||
507 | } | ||
508 | |||
509 | // /** | ||
510 | // * For internal use only: logs exceptions occurring during term evaluation inside the RETE net. | ||
511 | // * @param e | ||
512 | // */ | ||
513 | // public void logEvaluatorException(Throwable e) { | ||
514 | // try { | ||
515 | // caughtExceptions.put(e); | ||
516 | // } catch (InterruptedException e1) { | ||
517 | // logEvaluatorException(e); | ||
518 | // } | ||
519 | // } | ||
520 | // /** | ||
521 | // * Polls the exceptions caught and logged during term evaluation by this RETE engine. | ||
522 | // * Recommended usage: iterate polling until null is returned. | ||
523 | // * | ||
524 | // * @return the next caught exception, or null if there are no more. | ||
525 | // */ | ||
526 | // public Throwable getNextLoggedEvaluatorException() { | ||
527 | // return caughtExceptions.poll(); | ||
528 | // } | ||
529 | |||
530 | void ensureInitialized() { | ||
531 | if (disposedOrUninitialized) | ||
532 | throw new IllegalStateException("Trying to use a Rete engine that has been disposed or has not yet been initialized."); | ||
533 | |||
534 | } | ||
535 | |||
536 | @Override | ||
537 | public IQueryResultProvider getResultProvider(PQuery query) { | ||
538 | return accessMatcher(query); | ||
539 | } | ||
540 | |||
541 | /** | ||
542 | * @since 1.4 | ||
543 | */ | ||
544 | @Override | ||
545 | public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint hints) { | ||
546 | hintConfigurator.storeHint(query, hints); | ||
547 | return accessMatcher(query); | ||
548 | } | ||
549 | |||
550 | @Override | ||
551 | public IQueryResultProvider peekExistingResultProvider(PQuery query) { | ||
552 | ensureInitialized(); | ||
553 | return matchers.get(query); | ||
554 | } | ||
555 | |||
556 | @Override | ||
557 | public void dispose() { | ||
558 | killEngine(); | ||
559 | } | ||
560 | |||
561 | @Override | ||
562 | public boolean isCaching() { | ||
563 | return true; | ||
564 | } | ||
565 | |||
566 | /** | ||
567 | * @since 1.5 | ||
568 | * @noreference Internal API, subject to change | ||
569 | */ | ||
570 | public IQueryBackendHintProvider getHintConfiguration() { | ||
571 | return hintConfigurator; | ||
572 | } | ||
573 | |||
574 | @Override | ||
575 | public IQueryBackendFactory getFactory() { | ||
576 | return ReteBackendFactory.INSTANCE; | ||
577 | } | ||
578 | |||
579 | } | ||