/******************************************************************************* * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Gabor Bergmann - initial API and implementation *******************************************************************************/ package org.eclipse.incquery.runtime.api; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import org.apache.log4j.Appender; import org.apache.log4j.Logger; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.incquery.patternlanguage.patternLanguage.Pattern; import org.eclipse.incquery.runtime.base.api.IncQueryBaseFactory; import org.eclipse.incquery.runtime.base.api.NavigationHelper; import org.eclipse.incquery.runtime.base.exception.IncQueryBaseException; import org.eclipse.incquery.runtime.exception.IncQueryException; import org.eclipse.incquery.runtime.extensibility.EngineTaintListener; import org.eclipse.incquery.runtime.internal.EMFPatternMatcherRuntimeContext; import org.eclipse.incquery.runtime.internal.PatternSanitizer; import org.eclipse.incquery.runtime.internal.XtextInjectorProvider; import org.eclipse.incquery.runtime.internal.matcherbuilder.EPMBuilder; import org.eclipse.incquery.runtime.rete.construction.ReteContainerBuildable; import org.eclipse.incquery.runtime.rete.matcher.IPatternMatcherRuntimeContext; import org.eclipse.incquery.runtime.rete.matcher.ReteEngine; import org.eclipse.incquery.runtime.rete.network.Receiver; import org.eclipse.incquery.runtime.rete.network.Supplier; import org.eclipse.incquery.runtime.rete.remote.Address; import com.google.inject.Injector; /** * A EMF-IncQuery engine back-end, attached to a model such as an EMF resource. The engine hosts pattern matchers, and * will listen on EMF update notifications stemming from the given model in order to maintain live results. * * <p> * Pattern matchers within this engine may be instantiated in the following ways: * <ul> * <li>Instantiate the specific matcher class generated for the pattern, by passing to the constructor either this * engine or the EMF model root. * <li>Use the matcher factory associated with the generated matcher class to achieve the same. * <li>Use {@link GenericPatternMatcher} or {@link GenericMatcherFactory} instead of the various generated classes. * </ul> * Additionally, a group of patterns (see {@link IPatternGroup}) can be initialized together before usage; this improves * the performance of pattern matcher construction, unless the engine is in wildcard mode. * * <p> * The engine can be disposed in order to detach from the EMF model and stop listening on update notifications. * * @author Bergmann Gábor * */ public class IncQueryEngine { /** * The engine manager responsible for this engine. Null if this engine is unmanaged. */ private final EngineManager manager; /** * The model to which the engine is attached. */ private final Notifier emfRoot; /** * The base index keeping track of basic EMF contents of the model. */ private NavigationHelper baseIndex; /** * Whether to initialize the base index in wildcard mode. */ private static final boolean WILDCARD_MODE_DEFAULT = false; /** * The RETE pattern matcher component of the EMF-IncQuery engine. */ private ReteEngine<Pattern> reteEngine = null; /** * A sanitizer to catch faulty patterns. */ private PatternSanitizer sanitizer = null; /** * Indicates whether the engine is in a tainted, inconsistent state. */ private boolean tainted = false; private EngineTaintListener taintListener; private static class SelfTaintListener extends EngineTaintListener { WeakReference<IncQueryEngine> iqEngRef; public SelfTaintListener(IncQueryEngine iqEngine) { this.iqEngRef = new WeakReference<IncQueryEngine>(iqEngine); } @Override public void engineBecameTainted() { final IncQueryEngine iqEngine = iqEngRef.get(); iqEngine.tainted = true; } } /** * EXPERIMENTAL */ private final int reteThreads = 0; private Logger logger; private final Set<Runnable> afterWipeCallbacks; /** * @param manager * null if unmanaged * @param emfRoot * @throws IncQueryException * if the emf root is invalid */ IncQueryEngine(EngineManager manager, Notifier emfRoot) throws IncQueryException { super(); this.manager = manager; this.emfRoot = emfRoot; this.afterWipeCallbacks = new HashSet<Runnable>(); if (!(emfRoot instanceof EObject || emfRoot instanceof Resource || emfRoot instanceof ResourceSet)) throw new IncQueryException(IncQueryException.INVALID_EMFROOT + (emfRoot == null ? "(null)" : emfRoot.getClass().getName()), IncQueryException.INVALID_EMFROOT_SHORT); } /** * @return the root of the EMF model tree that this engine is attached to. */ public Notifier getEmfRoot() { return emfRoot; } /** * Internal accessor for the base index. * * @return the baseIndex the NavigationHelper maintaining the base index * @throws IncQueryException * if the base index could not be constructed */ protected NavigationHelper getBaseIndexInternal() throws IncQueryException { return getBaseIndexInternal(WILDCARD_MODE_DEFAULT, true); } /** * Internal accessor for the base index. * * @return the baseIndex the NavigationHelper maintaining the base index * @throws IncQueryException * if the base index could not be initialized * @throws IncQueryBaseException * if the base index could not be constructed */ protected NavigationHelper getBaseIndexInternal(boolean wildcardMode, boolean initNow) throws IncQueryException { if (baseIndex == null) { try { // sync to avoid crazy compiler reordering which would matter if derived features use eIQ and call this // reentrantly synchronized (this) { baseIndex = IncQueryBaseFactory.getInstance().createNavigationHelper(null, wildcardMode, getLogger()); } } catch (IncQueryBaseException e) { throw new IncQueryException("Could not create EMF-IncQuery base index", "Could not create base index", e); } if (initNow) { initBaseIndex(); } } return baseIndex; } /** * @throws IncQueryException */ private synchronized void initBaseIndex() throws IncQueryException { try { baseIndex.addRoot(getEmfRoot()); } catch (IncQueryBaseException e) { throw new IncQueryException("Could not initialize EMF-IncQuery base index", "Could not initialize base index", e); } } /** * Provides access to the internal base index component of the engine, responsible for keeping track of basic EMF * contents of the model. * * @return the baseIndex the NavigationHelper maintaining the base index * @throws IncQueryException * if the base index could not be constructed */ public NavigationHelper getBaseIndex() throws IncQueryException { return getBaseIndexInternal(); } /** * Provides access to the internal RETE pattern matcher component of the EMF-IncQuery engine. * * @noreference A typical user would not need to call this method. */ public ReteEngine<Pattern> getReteEngine() throws IncQueryException { if (reteEngine == null) { // if uninitialized, don't initialize yet getBaseIndexInternal(WILDCARD_MODE_DEFAULT, false); EMFPatternMatcherRuntimeContext context = new EMFPatternMatcherRuntimeContext(this, baseIndex); // if (emfRoot instanceof EObject) // context = new EMFPatternMatcherRuntimeContext.ForEObject<Pattern>((EObject)emfRoot, this); // else if (emfRoot instanceof Resource) // context = new EMFPatternMatcherRuntimeContext.ForResource<Pattern>((Resource)emfRoot, this); // else if (emfRoot instanceof ResourceSet) // context = new EMFPatternMatcherRuntimeContext.ForResourceSet<Pattern>((ResourceSet)emfRoot, this); // else throw new IncQueryRuntimeException(IncQueryRuntimeException.INVALID_EMFROOT); synchronized (this) { reteEngine = buildReteEngineInternal(context); } // lazy initialization now, initBaseIndex(); // if (reteEngine != null) engines.put(emfRoot, new WeakReference<ReteEngine<String>>(engine)); } return reteEngine; } /** * Completely disconnects and dismantles the engine. * <p> * Matcher objects will continue to return stale results. If no references are retained to the matchers or the * engine, they can eventually be GC'ed, and they won't block the EMF model from being GC'ed anymore. * * <p> * Cannot be reversed. * <p> * If the engine is managed (see {@link #isManaged()}), there may be other clients using it. Care should be taken * with disposing such engines. */ public void dispose() { if (manager != null) { manager.killInternal(emfRoot); logger.warn(String.format("Managed engine disposed for notifier %s !", emfRoot)); } killInternal(); } /** * Discards any pattern matcher caches and forgets known patterns. The base index built directly on the underlying * EMF model, however, is kept in memory to allow reuse when new pattern matchers are built. Use this method if you * have e.g. new versions of the same patterns, to be matched on the same model. * * <p> * Matcher objects will continue to return stale results. If no references are retained to the matchers, they can * eventually be GC'ed. * <p> * If the engine is managed (see {@link #isManaged()}), there may be other clients using it. Care should be taken * with wiping such engines. * */ public void wipe() { if (manager != null) { logger.warn(String.format("Managed engine wiped for notifier %s !", emfRoot)); } if (reteEngine != null) { reteEngine.killEngine(); reteEngine = null; } sanitizer = null; runAfterWipeCallbacks(); } /** * This will run before wipes. */ // * If there are any such, updates are settled before they are run. public void runAfterWipeCallbacks() { try { if (!afterWipeCallbacks.isEmpty()) { // settle(); for (Runnable runnable : new ArrayList<Runnable>(afterWipeCallbacks)) { runnable.run(); } } } catch (Exception ex) { logger.fatal("EMF-IncQuery encountered an error in delivering notifications about wipe. ", ex); } } private ReteEngine<Pattern> buildReteEngineInternal(IPatternMatcherRuntimeContext<Pattern> context) { ReteEngine<Pattern> engine; engine = new ReteEngine<Pattern>(context, reteThreads); ReteContainerBuildable<Pattern> buildable = new ReteContainerBuildable<Pattern>(engine); EPMBuilder<Address<? extends Supplier>, Address<? extends Receiver>> builder = new EPMBuilder<Address<? extends Supplier>, Address<? extends Receiver>>( buildable, context); engine.setBuilder(builder); return engine; } /** * To be called after already removed from engineManager. */ void killInternal() { wipe(); if (baseIndex != null) { baseIndex.dispose(); } getLogger().removeAppender(taintListener); } /** * Run-time events (such as exceptions during expression evaluation) will be logged to this logger. * <p> * DEFAULT BEHAVIOUR: If Eclipse is running, the default logger pipes to the Eclipse Error Log. Otherwise, messages * are written to stderr. * </p> * * @return the logger that errors will be logged to during runtime execution. */ public Logger getLogger() { if (logger == null) { final int hash = System.identityHashCode(this); logger = Logger.getLogger(getDefaultLogger().getName() + "." + hash); if (logger == null) throw new AssertionError( "Configuration error: unable to create EMF-IncQuery runtime logger for engine " + hash); // if an error is logged, the engine becomes tainted taintListener = new SelfTaintListener(this); logger.addAppender(taintListener); } return logger; } // // // /** // * Run-time events (such as exceptions during expression evaluation) will be logged to the specified logger. // * <p> // * DEFAULT BEHAVIOUR: // * If Eclipse is running, the default logger pipes to the Eclipse Error Log. // * Otherwise, messages are written to stderr. // * In both cases, debug messages are ignored. // * </p> // * @param logger a custom logger that errors will be logged to during runtime execution. // */ // public void setLogger(EMFIncQueryRuntimeLogger logger) { // this.logger = logger; // } /** * @return the sanitizer */ public PatternSanitizer getSanitizer() { if (sanitizer == null) { sanitizer = new PatternSanitizer(getLogger()); } return sanitizer; } /** * Provides a static default logger. */ public static Logger getDefaultLogger() { if (defaultRuntimeLogger == null) { final Injector injector = XtextInjectorProvider.INSTANCE.getInjector(); if (injector == null) throw new AssertionError("Configuration error: EMF-IncQuery injector not initialized."); Logger parentLogger = injector.getInstance(Logger.class); if (parentLogger == null) throw new AssertionError("Configuration error: EMF-IncQuery logger not found."); defaultRuntimeLogger = Logger.getLogger(parentLogger.getName() + ".runtime"); if (defaultRuntimeLogger == null) throw new AssertionError("Configuration error: unable to create default EMF-IncQuery runtime logger."); } return defaultRuntimeLogger; } private static Logger defaultRuntimeLogger; /** * Specifies whether the base index should be built in wildcard mode. See {@link NavigationHelper} for the * explanation of wildcard mode. * * @param wildcardMode * the wildcardMode to set * @throws IncQueryException * if the base index could not be initialized * @throws IllegalStateException * if baseIndex is already constructed in the opposite mode, since the mode can not be changed once * applied */ public void setWildcardMode(boolean wildcardMode) throws IncQueryException { if (baseIndex != null && baseIndex.isInWildcardMode() != wildcardMode) throw new IllegalStateException("Base index already built, cannot change wildcard mode anymore"); if (wildcardMode != WILDCARD_MODE_DEFAULT) getBaseIndexInternal(wildcardMode, true); } /** * Indicates whether the engine is in a tainted, inconsistent state due to some internal errors. If true, results * are no longer reliable; engine should be disposed. * * <p> * The engine is defined to be in a tainted state if any of its internal processes has logged a * <strong>fatal</strong> error to the engine's logger. The cause of the error can therefore be determined by * checking the contents of the log. This is possible e.g. through a custom {@link Appender} that was attached to * the engine's logger. * * @return the tainted state */ public boolean isTainted() { return tainted; } /** * Indicates whether the engine is managed by {@link EngineManager}. * * <p> * If the engine is managed, there may be other clients using it. Care should be taken with {@link #wipe()} and * {@link #dispose()}. Register a callback using {@link IncQueryMatcher#addCallbackAfterWipes(Runnable)} or directly * at {@link #getAfterWipeCallbacks()} to learn when a client has called these dangerous methods. * * @return true if the engine is managed, and therefore potentially shared with other clients querying the same EMF * model */ public boolean isManaged() { return manager != null; } /** * @return the set of callbacks that will be issued after a wipe */ public Set<Runnable> getAfterWipeCallbacks() { return afterWipeCallbacks; } // /** // * EXPERIMENTAL: Creates an EMF-IncQuery engine that executes post-commit, or retrieves an already existing one. // * @param emfRoot the EMF root where this engine should operate // * @param reteThreads experimental feature; 0 is recommended // * @return a new or previously existing engine // * @throws IncQueryRuntimeException // */ // public ReteEngine<String> getReteEngine(final TransactionalEditingDomain editingDomain, int reteThreads) throws // IncQueryRuntimeException { // final ResourceSet resourceSet = editingDomain.getResourceSet(); // WeakReference<ReteEngine<String>> weakReference = engines.get(resourceSet); // ReteEngine<String> engine = weakReference != null ? weakReference.get() : null; // if (engine == null) { // IPatternMatcherRuntimeContext<String> context = new // EMFPatternMatcherRuntimeContext.ForTransactionalEditingDomain<String>(editingDomain); // engine = buildReteEngine(context, reteThreads); // if (engine != null) engines.put(resourceSet, new WeakReference<ReteEngine<String>>(engine)); // } // return engine; // } }