Skip to content

Commit

Permalink
Merge pull request #59 from /issues/58
Browse files Browse the repository at this point in the history
#58 Fix early VIATRA engine initialization in ViatraQueryAdapter
  • Loading branch information
tamasborbas authored Sep 11, 2020
2 parents 8397138 + 1f00708 commit c5b0b21
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@

package com.incquerylabs.v4md;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;

import org.apache.log4j.Logger;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine;
import org.eclipse.viatra.query.runtime.api.IQueryGroup;
import org.eclipse.viatra.query.runtime.api.IQuerySpecification;
import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions;
import org.eclipse.viatra.query.runtime.exception.ViatraQueryException;

import com.incquerylabs.v4md.internal.MagicDrawProjectScope;
import com.incquerylabs.v4md.internal.NopQueryBackend;
import com.nomagic.magicdraw.core.Project;
import com.nomagic.magicdraw.core.ProjectUtilities;

/**
* Centralized management class for managing the lifecycle of query engines. The
Expand All @@ -23,29 +31,164 @@
*
*/
public class ViatraQueryAdapter extends AdapterImpl {
public static final String MESSAGE_ENGINE_PREPARE_ACTION_ERROR =
"Required actions cannot be executed on the VIATRA engine!";
public static final String MESSAGE_ENGINE_NOT_READY =
"Cannot initialize VIATRA Query Engine until the project is loaded.";
private static final Logger LOGGER = Logger.getLogger(ViatraQueryAdapter.class);

private AdvancedViatraQueryEngine engine;
private Optional<AdvancedViatraQueryEngine> engine = Optional.empty();
private Project project;
private boolean engineDisposable = true;
private Set<String> identifierMap = new HashSet<>();
private boolean isInitialized = false;
private Notifier[] notifiers;
private List<Consumer<AdvancedViatraQueryEngine>> initializationActions = new ArrayList<>();

private ViatraQueryAdapter(AdvancedViatraQueryEngine engine, Project project) {
this.engine = engine;
private ViatraQueryAdapter(Project project, Notifier[] notifiers) {
this.project = project;
this.notifiers = notifiers;
}


/**
* @deprecated Use {@link #getInitializedEngineChecked()} or {@link #getInitializedEngine()} instead
* @return initialized engine
* @throws ViatraQueryException if no query engine was initialized before and initialization failed
*/
@Deprecated
public AdvancedViatraQueryEngine getEngine() {
return getInitializedEngineChecked();
}

/**
* Returns an initialized VIATRA query engine stored in the adapter.
* If a query engine was initialized earlier, that engine will be returned;
* if no query engine was initialized earlier, a new one is created. If
* the initialization fails, e.g. the engine was requested before the
* model was loaded entirely, a {@link ViatraQueryException} is thrown.
* </p>
* This method should only be called if the engine initialization is
* expected to be completed successfully, otherwise it is recommended
* to use {@link #getInitializedEngine()},
* {@link #executeActionOnEngine(Consumer)}
* or {@link #requireQueries(IQuerySpecification)} instead
* as they are aimed to handle the initialization issues better.
* @return the initialized engine
* @throws ViatraQueryException if no query engine was initialized before and initialization failed
*/
public AdvancedViatraQueryEngine getInitializedEngineChecked() {
return getInitializedEngine()
.orElseThrow(() ->
new ViatraQueryException(MESSAGE_ENGINE_NOT_READY, MESSAGE_ENGINE_NOT_READY));
}

/**
* Provide the initialized VIATRA if possible.<br />
* If the engine hasn't been initialized but it can be, then it will be initialize and
* every initialization action registered before will be executed.
*
* @return the initialized VIATRA engine if possible, otherwise an empty Optional
* @see ViatraQueryAdapter#executeActionOnEngine(Consumer)
* @see ViatraQueryAdapter#requireQueries(IQuerySpecification)
*/
public Optional<AdvancedViatraQueryEngine> getInitializedEngine() {
if(!isInitialized) {
initializeEngine();
}
return engine;
}

/**
* Executes the give action on the engine associated with the current adapter.
* If the engine is not yet initialized, the action is saved and will be executed
* later when the engine gets initialized.
*
* @param action the operation which would like to use the initialized VIATRA engine -
* the action should not throw an exception during execution
*/
public void executeActionOnEngine(Consumer<AdvancedViatraQueryEngine> action) {
if(action != null) {
initializationActions.add(action);
}
// we use initializeEngine instead of getInitializedEngine because the initializeEngine
// executes the actions anyway while the other is just when the initialization is not complete
initializeEngine();
}

private Optional<AdvancedViatraQueryEngine> initializeEngine() {
synchronized(this) {
if(!isInitialized && ProjectUtilities.isLoaded(project.getPrimaryProject())) {
engine = Optional.of(createQueryEngine(project, notifiers));
}
isInitialized = engine.map(e -> {
boolean thereWasException = false;
try {
e.getBaseIndex().coalesceTraversals(() -> {
initializationActions.forEach(action -> action.accept(e));
return null;
});
initializationActions.clear();
notifiers = new Notifier[0];
} catch (InvocationTargetException ite) {
// we can invalidate our engine because there is two option for this exception:
// 1. an exception is thrown directly by the action (which is not supported)
// 2. the engine got tainted because of an internal error, making the engine unusable
LOGGER.error(MESSAGE_ENGINE_PREPARE_ACTION_ERROR, ite);
e.dispose();
thereWasException = true;
}
return !thereWasException;
}).orElseGet(() -> {
LOGGER.warn(MESSAGE_ENGINE_NOT_READY);
return false;
});
if(!isInitialized) {
engine = Optional.empty();
}
}
return engine;
}

/**
* Prepare queries on the VIATRA query engine of the adapter. If it cannot be created
* immediately, the initialization of the queries will be done when the engine is ready.
*
* @param querySpecifications the initializable queries
*/
public void requireQueries(IQuerySpecification<?>... querySpecifications) {
Consumer<AdvancedViatraQueryEngine> initializer = e -> {
for (IQuerySpecification<?> querySpecification : querySpecifications) {
e.getMatcher(querySpecification);
}
};
executeActionOnEngine(initializer);
}

/**
* Prepare queries on the VIATRA query engine of the adapter. If it cannot be created
* immediately, the initialization of the queries will be done when the engine is ready.
*
* @param queryGroups the initializable queries
*/
public void requireQueries(IQueryGroup... queryGroups) {
Consumer<AdvancedViatraQueryEngine> initializer = e -> {
for (IQueryGroup queryGroup : queryGroups) {
queryGroup.prepare(e);
}
};
executeActionOnEngine(initializer);
}

/**
* This method is responsible for disposing of the VIATRA query engine and removing it from the list of project adapters.
* </p>
* <b>Note</b> Calling this method disposes the engine without any validation; it is not expected to be called by clients.
* @see #dispose(String)
*/
public void dispose(){
engine.dispose();
engine.ifPresent(AdvancedViatraQueryEngine::dispose);
isInitialized = false;
project.getPrimaryModel().eAdapters().remove(this);
}

Expand All @@ -64,14 +207,14 @@ public void dispose(String identifier) {
}

public void wipeEngine(){
engine.wipe();
engine.ifPresent(AdvancedViatraQueryEngine::wipe);
}

/**
* This method is called by a project change listener registered by V4MD; it should not be called by end-users.
*/
void projectStructureUpdated() {
((MagicDrawProjectScope)engine.getScope()).projectStructureUpdated();
engine.ifPresent(e -> ((MagicDrawProjectScope)e.getScope()).projectStructureUpdated());
}

/**
Expand Down Expand Up @@ -163,7 +306,7 @@ private static Optional<ViatraQueryAdapter> doGetAdapter(Project project) {

private static ViatraQueryAdapter doGetOrCreateAdapter(Project project, Notifier... notifiers) {
return getAdapter(project).orElseGet(() -> {
ViatraQueryAdapter adapter = new ViatraQueryAdapter(createQueryEngine(project, notifiers), project);
ViatraQueryAdapter adapter = new ViatraQueryAdapter(project, notifiers);
project.getPrimaryModel().eAdapters().add(adapter);
return adapter;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine;

import com.incquerylabs.v4md.ViatraQueryAdapter;
Expand Down Expand Up @@ -33,6 +34,8 @@
public class BinaryVQLExpression implements ParameterizedExpression {

public static final String LANGUAGE = "Binary VQL";
public static final String MESSAGE_ADAPTER_NOT_AVAILABLE = "ViatraQueryAdapter is not available for project '%s' yet.";
private static final Logger LOG = Logger.getLogger(BinaryVQLExpression.class);

protected Project project;
protected String className;
Expand All @@ -51,10 +54,15 @@ public BinaryVQLExpression(ParameterizedExpressionDescriptor descriptor, Express
}

public Object getValue(Element sourceParameter, ValueContext context) throws Exception {
ViatraQueryEngine engine = ViatraQueryAdapter.getAdapter(project).get().getEngine();
ViatraQueryEngine engine = ViatraQueryAdapter.getAdapter(project)
.flatMap(ViatraQueryAdapter::getInitializedEngine)
.orElseGet(()-> {
LOG.warn(String.format(MESSAGE_ADAPTER_NOT_AVAILABLE, project.getName()));
return null;
});

List<Object> returnSet = new ArrayList<>();
if (provider != null) {
if (engine != null && provider != null) {
provider.getResults(engine, sourceParameter).forEach(returnSet::add);
}
// MagicDraw sometimes handles single-valued returns differently, so instead of 1-length lists we should return the element itself
Expand All @@ -63,7 +71,7 @@ public Object getValue(Element sourceParameter, ValueContext context) throws Exc

@Override
public Class<?> getResultType() {
return provider != null ? null : provider.getResultType();
return provider == null ? null : provider.getResultType();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void testListener() {
ViatraQueryLoggingUtil.getLogger(ViatraQueryEngine.class).addAppender(testAppender);
}

Matcher matcher = PortConnections.Matcher.on(adapter.getEngine());
Matcher matcher = PortConnections.Matcher.on(adapter.getInitializedEngineChecked());

matcher.countMatches();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,10 @@
import java.io.File;
import java.io.IOException;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine;
import org.eclipse.viatra.query.runtime.emf.EMFScope;
import org.eclipse.viatra.query.testing.core.InitializedSnapshotMatchSetModelProvider;
import org.eclipse.viatra.query.testing.core.ViatraQueryTestCase;
import org.eclipse.viatra.query.testing.snapshot.QuerySnapshot;
import org.eclipse.viatra.query.testing.snapshot.SnapshotPackage;
import org.junit.AfterClass;
import org.junit.BeforeClass;

import com.incquerylabs.v4md.ViatraQueryAdapter;
import com.incquerylabs.v4md.test.provider.V4MDPatternBasedMatchSetProvider;
import com.incquerylabs.v4md.test.queries.Aggregator_Functions;
import com.incquerylabs.v4md.test.queries.Block_With_More_than_1_Parent;
import com.incquerylabs.v4md.test.queries.Check_Expression;
Expand All @@ -29,11 +19,6 @@
import com.incquerylabs.v4md.test.queries.Transitive_Closure;
import com.incquerylabs.v4md.test.queries.Unreachable_States;
import com.incquerylabs.v4md.test.runner.TestRunner;
import com.incquerylabs.v4md.test.snapshot.ISnapshotManager;
import com.incquerylabs.v4md.test.snapshot.SnapshotAnalyzer;
import com.incquerylabs.v4md.test.snapshot.SnapshotCreator;
import com.nomagic.ci.persistence.local.proxy.SnapshotManager;
import com.nomagic.magicdraw.core.Application;
import com.nomagic.magicdraw.core.Project;
import com.nomagic.magicdraw.tests.MagicDrawTestCase;
import com.nomagic.magicdraw.tests.common.TestEnvironment;
Expand All @@ -57,7 +42,7 @@ protected void setUpTest() throws Exception

protected EMFScope getScopeForProject(Project project) {
ViatraQueryAdapter adapter = ViatraQueryAdapter.getOrCreateAdapter(project);
AdvancedViatraQueryEngine engine = adapter.getEngine();
AdvancedViatraQueryEngine engine = adapter.getInitializedEngineChecked();
//V4MD uses EMFScope instances

return (EMFScope) engine.getScope();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.incquerylabs.v4md.test.provider;

import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine;
import org.eclipse.viatra.query.runtime.api.IPatternMatch;
import org.eclipse.viatra.query.runtime.api.IQuerySpecification;
import org.eclipse.viatra.query.runtime.api.ViatraQueryMatcher;
Expand Down Expand Up @@ -33,7 +32,7 @@ public V4MDPatternBasedMatchSetProvider(ViatraQueryAdapter adapter, SnapshotHelp
public <Match extends IPatternMatch> MatchSetRecord getMatchSetRecord(EMFScope scope,
IQuerySpecification<? extends ViatraQueryMatcher<Match>> querySpecification, Match filter) {

ViatraQueryMatcher<Match> matcher = adapter.getEngine().getMatcher(querySpecification);
ViatraQueryMatcher<Match> matcher = adapter.getInitializedEngineChecked().getMatcher(querySpecification);
return helper.createMatchSetRecordForMatcher(matcher,
filter == null ? matcher.newEmptyMatch() : filter);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ private void createAndSaveSnapshot(ViatraQueryEngine engine, IQueryGroup group,
@Override
public void assertQueryResult(Project project, IQueryGroup queryGroup, String snapshotFileName) {
try {
ViatraQueryEngine engine = ViatraQueryAdapter.getOrCreateAdapter(project).getEngine();
ViatraQueryEngine engine = ViatraQueryAdapter.getOrCreateAdapter(project).getInitializedEngineChecked();
createAndSaveSnapshot(engine, queryGroup, snapshotFileName);
} catch (IOException e) {
Assert.fail(e.getMessage());
Expand Down

0 comments on commit c5b0b21

Please sign in to comment.