/******************************************************************************* | |
* Copyright (c) 2011-2020 The University of York. | |
* | |
* This program and the accompanying materials are made available under the | |
* terms of the Eclipse Public License 2.0 which is available at | |
* http://www.eclipse.org/legal/epl-2.0. | |
* | |
* This Source Code may also be made available under the following Secondary | |
* Licenses when the conditions for such availability set forth in the Eclipse | |
* Public License, v. 2.0 are satisfied: GNU General Public License, version 3. | |
* | |
* SPDX-License-Identifier: EPL-2.0 OR GPL-3.0 | |
* | |
* Contributors: | |
* Konstantinos Barmpis - initial API and implementation | |
* Antonio Garcia-Dominguez - protect against null EPackage nsURIs | |
******************************************************************************/ | |
package org.eclipse.hawk.graph.updater; | |
import java.io.IOException; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Optional; | |
import java.util.Set; | |
import org.eclipse.hawk.core.IMetaModelResourceFactory; | |
import org.eclipse.hawk.core.IModelIndexer; | |
import org.eclipse.hawk.core.graph.IGraphChangeListener; | |
import org.eclipse.hawk.core.graph.IGraphDatabase; | |
import org.eclipse.hawk.core.graph.IGraphEdge; | |
import org.eclipse.hawk.core.graph.IGraphIterable; | |
import org.eclipse.hawk.core.graph.IGraphNode; | |
import org.eclipse.hawk.core.graph.IGraphNodeIndex; | |
import org.eclipse.hawk.core.graph.IGraphTransaction; | |
import org.eclipse.hawk.core.model.IHawkAttribute; | |
import org.eclipse.hawk.core.model.IHawkClass; | |
import org.eclipse.hawk.core.model.IHawkClassifier; | |
import org.eclipse.hawk.core.model.IHawkDataType; | |
import org.eclipse.hawk.core.model.IHawkMetaModelResource; | |
import org.eclipse.hawk.core.model.IHawkObject; | |
import org.eclipse.hawk.core.model.IHawkPackage; | |
import org.eclipse.hawk.core.model.IHawkReference; | |
import org.eclipse.hawk.core.model.IHawkStructuralFeature; | |
import org.eclipse.hawk.core.runtime.CompositeGraphChangeListener; | |
import org.eclipse.hawk.core.util.CompositeException; | |
import org.eclipse.hawk.graph.FileNode; | |
import org.eclipse.hawk.graph.ModelElementNode; | |
import org.eclipse.hawk.graph.Slot; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
public class GraphMetaModelResourceInjector { | |
private static final Logger LOGGER = LoggerFactory.getLogger(GraphMetaModelResourceInjector.class); | |
/** | |
* Represents the case when the insertion of a metamodel fails. | |
*/ | |
public static class FailedMetamodelRegistrationException extends Exception { | |
private static final long serialVersionUID = 1L; | |
public FailedMetamodelRegistrationException(String metamodelURI, Throwable reason) { | |
super(String.format("Failed to register metamodel '%s': %s", metamodelURI, reason.getMessage()), reason); | |
} | |
} | |
/** | |
* Represents the situation when a particular metamodel is missing, so a certain | |
* EClass cannot be registered. | |
*/ | |
public static class MissingMetamodelException extends Exception { | |
/** | |
* | |
*/ | |
private static final long serialVersionUID = 1L; | |
public MissingMetamodelException(String metamodelURI) { | |
super(String.format("Missing metamodel with URI '%s'", metamodelURI)); | |
} | |
} | |
// integer array containing the current number of added elements: | |
// (element,((ofType)M->MM)reference,((ofKind)M->MM)reference,(unset(M->M))reference) | |
private int objectCount = 0; | |
private IModelIndexer hawk; | |
private IGraphDatabase graph; | |
private final long startTime = System.nanoTime(); | |
private final CompositeGraphChangeListener listener; | |
public GraphMetaModelResourceInjector(IModelIndexer hawk, Set<IHawkMetaModelResource> set, CompositeGraphChangeListener listener) throws Exception { | |
this.hawk = hawk; | |
this.graph = hawk.getGraph(); | |
this.listener = listener; | |
LOGGER.info("ADDING METAMODELS: "); | |
LOGGER.info("ADDING: "); | |
int nodes = insertMetamodels(set); | |
LOGGER.info("{} METAMODEL NODES! (took ~{} sec)", nodes, (System.nanoTime() - startTime) / 1_000_000_000); | |
} | |
public GraphMetaModelResourceInjector(IModelIndexer hawk, CompositeGraphChangeListener listener) { | |
this.hawk = hawk; | |
this.graph = hawk.getGraph(); | |
this.listener = listener; | |
} | |
/** | |
* Removes all the metamodel resources and their dependent model elements from the index. | |
*/ | |
public void removeMetamodels(Set<IHawkMetaModelResource> set) { | |
try (IGraphTransaction t = graph.beginTransaction()) { | |
listener.changeStart(); | |
IGraphNodeIndex ePackageDictionary = graph.getMetamodelIndex(); | |
Set<IGraphNode> epns = new HashSet<>(); | |
for (IHawkMetaModelResource metamodelResource : set) { | |
// if (resourceset == null) | |
// resourceset = metamodelResource.getResourceSet(); | |
Set<IHawkObject> children = metamodelResource.getAllContents(); | |
for (IHawkObject child : children) { | |
// if (child.eIsProxy()) { | |
// throw new Exception("FAILED. PROXY UNRESOLVED: " | |
// + ((InternalEObject) child).eProxyURI() | |
// .fragment()); | |
// } | |
// add the element | |
if (child instanceof IHawkPackage) { | |
Iterator<? extends IGraphNode> it = ePackageDictionary.get("id", ((IHawkPackage) child).getNsURI()).iterator(); | |
if (!it.hasNext()) { | |
LOGGER.warn("Metamodel: {} with uri: {} not indexed. Nothing happened.", | |
((IHawkPackage) child).getName(), ((IHawkPackage) child).getNsURI()); | |
} else { | |
IGraphNode epn = it.next(); | |
LOGGER.info("Removing metamodel: {} with uri: {}", ((IHawkPackage) child).getName(), ((IHawkPackage) child).getNsURI()); | |
epns.add(epn); | |
} | |
} | |
} | |
} | |
removeAll(epns); | |
t.success(); | |
listener.changeSuccess(); | |
} catch (Exception e1) { | |
listener.changeFailure(); | |
LOGGER.error("Error in removing metamodels (all removal changes reverted)", e1); | |
} | |
} | |
/** | |
* Removes all the metamodels and models dependent on the <code>epsn</code> metamodel nodes. | |
* | |
* @return URIs of the repositories impacted by the removal. | |
*/ | |
private Set<String> removeAll(Set<IGraphNode> epns) throws Exception { | |
Set<String> affectedRepositories = new HashSet<>(); | |
DeletionUtils del = new DeletionUtils(graph); | |
for (IGraphNode epn : epns) | |
for (IGraphEdge rel : epn.getIncomingWithType(IModelIndexer.METAMODEL_DEPENDENCY_EDGE)) { | |
LOGGER.debug("dependency from {} to {}", | |
rel.getStartNode().getProperty(IModelIndexer.IDENTIFIER_PROPERTY), | |
epn.getProperty(IModelIndexer.IDENTIFIER_PROPERTY)); | |
// delete all dependent metamodels and models | |
IGraphNode depmm = rel.getStartNode(); | |
del.delete(rel); | |
epns.add(depmm); | |
} | |
Set<IGraphNode> files = new HashSet<>(); | |
for (IGraphNode n : epns) | |
files.addAll(remove(n)); | |
for (IGraphNode file : files) | |
affectedRepositories.add(file.getProperty(FileNode.PROP_REPOSITORY).toString()); | |
for (IGraphNode file : files) | |
del.delete(file); | |
return affectedRepositories; | |
} | |
private Set<IGraphNode> remove(IGraphNode epn) throws Exception { | |
long start = System.currentTimeMillis(); | |
HashSet<IGraphNode> fileNodes = new HashSet<IGraphNode>(); | |
try (IGraphTransaction transaction = graph.beginTransaction()) { | |
LOGGER.info("Deleting nodes from metamodel: {}", epn.getProperty(IModelIndexer.IDENTIFIER_PROPERTY)); | |
HashSet<IGraphNode> metaModelElements = new HashSet<IGraphNode>(); | |
HashSet<IGraphNode> modelElements = new HashSet<IGraphNode>(); | |
DeletionUtils del = new DeletionUtils(graph); | |
for (IGraphEdge rel : epn.getIncomingWithType("epackage")) { | |
metaModelElements.add(rel.getStartNode()); | |
del.delete(rel); | |
} | |
for (IGraphEdge rel : epn.getOutgoingWithType(IModelIndexer.METAMODEL_DEPENDENCY_EDGE)) { | |
del.delete(rel); | |
} | |
del.delete(epn); | |
for (IGraphNode metamodelelement : metaModelElements) { | |
for (IGraphEdge rel : metamodelelement.getIncomingWithType(ModelElementNode.EDGE_LABEL_OFTYPE)) { | |
modelElements.add(rel.getStartNode()); | |
del.delete(rel); | |
} | |
} | |
for (IGraphNode metamodelelement : metaModelElements) { | |
for (IGraphEdge rel : metamodelelement.getIncomingWithType(ModelElementNode.EDGE_LABEL_OFKIND)) { | |
modelElements.add(rel.getStartNode()); | |
del.delete(rel); | |
} | |
} | |
for (IGraphNode metaModelElement : metaModelElements) | |
del.delete(metaModelElement); | |
// remove model elements and update derived attributes | |
if (modelElements.size() > 0) { | |
LOGGER.info("Deleting nodes from relevant models..."); | |
Set<IGraphNode> toBeUpdated = new HashSet<>(); | |
final DirtyDerivedFeaturesListener l = new DirtyDerivedFeaturesListener(graph); | |
if (!hawk.getDerivedAttributes().isEmpty()) { | |
hawk.addGraphChangeListener(l); | |
} | |
for (IGraphNode modelElement : modelElements) { | |
Iterator<IGraphEdge> it = modelElement.getOutgoingWithType(ModelElementNode.EDGE_LABEL_FILE) | |
.iterator(); | |
if (it.hasNext()) { | |
IGraphEdge e = it.next(); | |
fileNodes.add(e.getEndNode()); | |
del.delete(e); | |
} | |
del.dereference(modelElement, listener, null); | |
} | |
for (IGraphNode modelElement : modelElements) { | |
del.delete(modelElement); | |
} | |
toBeUpdated.addAll(l.getNodesToBeUpdated()); | |
listener.remove(l); | |
LOGGER.info("Updating any relevant derived attributes..."); | |
try { | |
new GraphModelInserter(hawk, () -> del, new TypeCache(hawk)) | |
.updateDerivedAttributes(hawk.getDerivedAttributeExecutionEngine(), toBeUpdated); | |
toBeUpdated = new HashSet<>(); | |
} catch (Exception e) { | |
toBeUpdated = new HashSet<>(); | |
LOGGER.error("Exception while updating derived attributes", e); | |
} | |
} | |
transaction.success(); | |
} | |
LOGGER.info("deleted all, took: {}s", (System.currentTimeMillis() - start) / 1000.0); | |
return fileNodes; | |
} | |
/** | |
* Inserts a number of metamodels into the graph. | |
* | |
* @param metamodels Metamodel resources to be registered. | |
* @return Number of added objects. | |
* @throws A single exception, or a {@link CompositeException} with multiple | |
* exceptions that have happened for this set of metamodels. | |
*/ | |
private int insertMetamodels(Set<IHawkMetaModelResource> metamodels) throws Exception { | |
// Expand set with dependencies if needed | |
Map<String, IHawkPackage> expanded = new HashMap<>(); | |
expandWithDependencies(metamodels, expanded); | |
// TODO: simplify rest of code after testing early dependency detection | |
// Create the EPackage nodes | |
Set<IHawkPackage> addedPackages = new HashSet<>(); | |
try (IGraphTransaction t = graph.beginTransaction()) { | |
listener.changeStart(); | |
IGraphNodeIndex ePackageDictionary = graph.getMetamodelIndex(); | |
for (IHawkPackage pkg : expanded.values()) { | |
if (addEPackage(pkg, ePackageDictionary)) { | |
addedPackages.add(pkg); | |
} | |
} | |
t.success(); | |
listener.changeSuccess(); | |
} catch (Exception ex) { | |
listener.changeFailure(); | |
throw ex; | |
} | |
// Create the EClass nodes inside each EPackage | |
List<Exception> exceptions = new ArrayList<>(); | |
Iterator<IHawkPackage> itPackage = addedPackages.iterator(); | |
while (itPackage.hasNext()) { | |
IHawkPackage epackage = itPackage.next(); | |
boolean revert = false; | |
final IGraphTransaction t = graph.beginTransaction(); | |
try { | |
listener.changeStart(); | |
addEClasses(epackage); | |
t.success(); | |
listener.changeSuccess(); | |
} catch (Exception eAddPackage) { | |
LOGGER.error(eAddPackage.getMessage(), eAddPackage); | |
t.failure(); | |
listener.changeFailure(); | |
exceptions.add(new FailedMetamodelRegistrationException(epackage.getNsURI(), eAddPackage)); | |
itPackage.remove(); | |
revert = true; | |
} finally { | |
t.close(); | |
} | |
if (revert) { | |
// Revert the addition of the metamodel node | |
try (IGraphTransaction t2 = graph.beginTransaction()) { | |
IGraphNode ePackageNode = graph.getMetamodelIndex().get("id", epackage.getNsURI()).iterator().next(); | |
new DeletionUtils(graph).delete(ePackageNode); | |
t2.success(); | |
} catch (Exception eRevertAddition) { | |
LOGGER.error(eRevertAddition.getMessage(), eRevertAddition); | |
exceptions.add(eRevertAddition); | |
} | |
} | |
} | |
for (IHawkPackage ePackage : addedPackages) { | |
try (IGraphTransaction t = graph.beginTransaction()) { | |
final IGraphNode epackagenode = graph.getMetamodelIndex().get("id", ePackage.getNsURI()).getSingle(); | |
// add resource to package | |
final Optional<String> s = ePackage.getResource().getMetaModelResourceFactory().dumpPackageToString(ePackage); | |
if (s.isPresent()) { | |
epackagenode.setProperty(IModelIndexer.METAMODEL_RESOURCE_PROPERTY, s.get()); | |
} | |
t.success(); | |
} catch (Exception eSaveResource) { | |
LOGGER.error("Cannot save package into the metamodel node", eSaveResource); | |
exceptions.add(eSaveResource); | |
} | |
} | |
// Throw any collected exceptions if we have them | |
if (!exceptions.isEmpty()) { | |
throw new CompositeException(exceptions); | |
} | |
return objectCount; | |
} | |
/** | |
* Performs dependency resolution on a set of metamodels, by applying a | |
* fixed-point algorithm where we keep adding metamodels as we find new | |
* dependencies, until we do not have any pending metamodels or we run into a | |
* metamodel that we cannot obtain. | |
* | |
* If we successfully exit from this method, it means that the value set of | |
* {@code expanded} is complete, perhaps reusing some packages that are already | |
* in the graph. | |
* | |
* @throws MissingMetamodelException A metamodel that this set depends upon | |
* could not be found. | |
* @throws Exception Another type of exception occurred during | |
* the dependency resolution. This will most | |
* likely be an issue with the node indices. | |
*/ | |
private void expandWithDependencies(Set<IHawkMetaModelResource> metamodels, Map<String, IHawkPackage> expanded) throws Exception { | |
final Set<String> alreadyInGraph = new HashSet<>(); | |
boolean firstBatch = false; | |
do { | |
for (Iterator<IHawkMetaModelResource> itM = metamodels.iterator(); itM.hasNext();) { | |
IHawkMetaModelResource m = itM.next(); | |
for (IHawkObject child : m.getAllContents()) { | |
if (child instanceof IHawkPackage) { | |
final IHawkPackage pkg = (IHawkPackage) child; | |
if (pkg.getNsURI() == null) { | |
LOGGER.info("Package {} has null URI, ignoring", pkg.getName()); | |
} else if (firstBatch && isMetamodelRegistered(pkg.getNsURI())) { | |
// First round: the graph may already have some of the provided metamodels | |
alreadyInGraph.add(pkg.getNsURI()); | |
itM.remove(); | |
LOGGER.info("Package {} already in the graph, ignoring", pkg.getNsURI()); | |
} else { | |
expanded.put(pkg.getNsURI(), pkg); | |
} | |
} | |
} | |
} | |
firstBatch = false; | |
// Find out which metamodels this batch requires that we are missing so far | |
final Set<String> missingURIs = new HashSet<>(); | |
addPendingDependencies(metamodels, expanded, missingURIs, alreadyInGraph); | |
// Try to locate the missing metamodels, or abort | |
metamodels.clear(); | |
missingMetamodels: | |
for (String uri : missingURIs) { | |
for (IMetaModelResourceFactory mp : hawk.getMetaModelParsers()) { | |
IHawkMetaModelResource mr = mp.getMetamodel(uri); | |
if (mr != null) { | |
metamodels.add(mr); | |
continue missingMetamodels; | |
} | |
} | |
throw new MissingMetamodelException(uri); | |
} | |
} while (!metamodels.isEmpty()); | |
} | |
private void addPendingDependencies(Set<IHawkMetaModelResource> metamodels, Map<String, IHawkPackage> expanded, final Set<String> missingURIs, final Set<String> alreadyInGraph) throws Exception { | |
for (IHawkMetaModelResource m : metamodels) { | |
for (IHawkObject child : m.getAllContents()) { | |
if (child instanceof IHawkPackage) { | |
final IHawkPackage pkg = (IHawkPackage) child; | |
for (IHawkClassifier pkgClassifier : pkg.getClasses()) { | |
if (pkgClassifier instanceof IHawkClass) { | |
IHawkClass eClass = (IHawkClass) pkgClassifier; | |
for (IHawkClass st : eClass.getAllSuperTypes()) { | |
addPendingDependencies(expanded, missingURIs, alreadyInGraph, st); | |
} | |
for (IHawkAttribute e : eClass.getAllAttributes()) { | |
if (e.getType() != null) { | |
addPendingDependencies(expanded, missingURIs, alreadyInGraph, e.getType()); | |
} | |
} | |
for (IHawkReference r : eClass.getAllReferences()) { | |
addPendingDependencies(expanded, missingURIs, alreadyInGraph, r.getType()); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
private void addPendingDependencies(Map<String, IHawkPackage> toBeRegistered, final Set<String> missingURIs, final Set<String> alreadyInGraph, IHawkClassifier targetType) throws Exception { | |
final String nsURI = targetType.getPackageNSURI(); | |
if (nsURI != null && !toBeRegistered.containsKey(nsURI) && !missingURIs.contains(nsURI) && !alreadyInGraph.contains(nsURI)) { | |
if (!isMetamodelRegistered(nsURI)) { | |
// Not in the set to be registered, not pending yet, and not in the graph: mark as missing | |
missingURIs.add(nsURI); | |
} else { | |
// Not in the set to be registered, but it is already in the graph | |
alreadyInGraph.add(nsURI); | |
} | |
} | |
} | |
private boolean isMetamodelRegistered(String nsURI) throws Exception { | |
boolean ret = false; | |
try (IGraphTransaction tx = graph.beginTransaction()) { | |
final IGraphIterable<? extends IGraphNode> iterMatches = graph.getMetamodelIndex().get("id", nsURI); | |
ret = iterMatches.iterator().hasNext(); | |
tx.success(); | |
} | |
return ret; | |
} | |
private void addEClasses(IHawkPackage ePackage) throws MissingMetamodelException { | |
for (IHawkClassifier child : ePackage.getClasses()) { | |
if (child instanceof IHawkClass) { | |
addMetaClass((IHawkClass) child); | |
} else if (child instanceof IHawkDataType) { | |
// FIXME need to handle datatypes? | |
if (child.getInstanceType() != null) { | |
LOGGER.warn( | |
"Hawk does not support custom data types yet: {}::{} will be handled as its instance type {}", | |
ePackage.getNsURI(), child.getName(), child.getInstanceType()); | |
} else { | |
LOGGER.warn( | |
"Hawk does not support custom data types yet: {}::{} will be handled as a string", | |
ePackage.getNsURI(), child.getName()); | |
} | |
} else { | |
LOGGER.error("Unknown classifier: ({}): {}", child.getName(), child.getClass()); | |
} | |
} | |
} | |
/** | |
* Returns {@code true} iff we added a new package node to the graph. | |
*/ | |
private boolean addEPackage(IHawkPackage ePackage, IGraphNodeIndex ePackageDictionary) throws IOException { | |
final String uri = ePackage.getNsURI(); | |
if (uri == null) { | |
LOGGER.warn("ePackage {} has null nsURI, ignoring", ePackage); | |
return false; | |
} | |
if (ePackageDictionary.get("id", uri).iterator().hasNext() == false) { | |
Map<String, Object> fields = new HashMap<>(); | |
fields.put(IModelIndexer.IDENTIFIER_PROPERTY, uri); | |
fields.put(IModelIndexer.METAMODEL_TYPE_PROPERTY, ePackage.getResource().getMetaModelResourceFactory().getType()); | |
IGraphNode epackagenode = graph.createNode(fields, "epackage"); | |
ePackageDictionary.add(epackagenode, "id", uri); | |
listener.metamodelAddition(ePackage, epackagenode); | |
return true; | |
} else { | |
LOGGER.warn( | |
"metamodel: {} ({}) already in store, updating it instead NYI -- doing nothing!", | |
(ePackage).getName(), uri); | |
return false; | |
} | |
} | |
/** | |
* | |
* @param eClass | |
* @return the URI ID of an eClass | |
*/ | |
public String getEObjectId(IHawkClass eClass) { | |
return eClass.getPackageNSURI() + "/" + eClass.getName(); | |
} | |
/** | |
* Creates a node in the graph database with the given eClass's attributes | |
* in it. | |
* | |
* @param eObject | |
* @param id | |
* @return the Node | |
*/ | |
private void createEClassNode(IHawkClass eClass, String id) throws MissingMetamodelException { | |
final Map<String, Object> map = new HashMap<>(); | |
map.put(IModelIndexer.IDENTIFIER_PROPERTY, id); | |
final IGraphNode node = graph.createNode(new HashMap<String, Object>(), "eclass"); | |
final IGraphNode metamodelNode = graph.getMetamodelIndex().get("id", eClass.getPackageNSURI()).getSingle(); | |
graph.createRelationship(node, metamodelNode, "epackage"); | |
for (IHawkClass superType : eClass.getAllSuperTypes()) { | |
addMetamodelDependency(eClass, metamodelNode, superType); | |
} | |
for (IHawkAttribute e : eClass.getAllAttributes()) { | |
if (e.getType() != null) { | |
addMetamodelDependency(eClass, metamodelNode, e.getType()); | |
} | |
String[] metadata = new Slot.MetadataBuilder() | |
.attribute() | |
.many(e.isMany()) | |
.ordered(e.isOrdered()) | |
.unique(e.isUnique()) | |
.type(getPropertyType(e)) | |
.build(); | |
map.put(e.getName(), metadata); | |
} | |
for (IHawkReference r : eClass.getAllReferences()) { | |
addMetamodelDependency(eClass, metamodelNode, r.getType()); | |
String[] metadata = new Slot.MetadataBuilder() | |
.reference() | |
.many(r.isMany()) | |
.ordered(r.isOrdered()) | |
.unique(r.isUnique()) | |
.type(getPropertyType(r)) | |
.build(); | |
map.put(r.getName(), metadata); | |
} | |
for (String s : map.keySet()) { | |
node.setProperty(s, map.get(s)); | |
} | |
listener.classAddition(eClass, node); | |
} | |
private String getPropertyType(IHawkStructuralFeature e) { | |
String propType = "unknown"; | |
if (e.getType() != null) { | |
if (e.getType().getName() != null) { | |
// System.err.println(e.getType().getName()); | |
propType = e.getType().getInstanceType(); | |
} else { | |
LOGGER.warn( | |
"warning: unknown (null) type NAME found in metamodel parsing into db for attribute: {} of type: {}", | |
e.getName(), e.getType()); | |
} | |
} else { | |
LOGGER.warn("warning: unknown (null) type found in metamodel parsing into db for attribute: {}", e.getName()); | |
} | |
return propType; | |
} | |
private void addMetamodelDependency(IHawkClass sourceType, final IGraphNode metamodelNode, IHawkClassifier targetType) throws MissingMetamodelException { | |
final String targetTypeMetamodelURI = targetType.getPackageNSURI(); | |
final IGraphIterable<? extends IGraphNode> metamodels = graph.getMetamodelIndex().get("id", targetTypeMetamodelURI); | |
if (metamodels.iterator().hasNext() == false) { | |
LOGGER.error( | |
"EClass {} has supertype {} which is in a package not registered yet, " | |
+ "reverting all changes to this package registration, please register " | |
+ "package with URI {} first", | |
sourceType.getName(), targetType.getName() == null ? targetType.getUri() : targetType.getName(), targetTypeMetamodelURI); | |
throw new MissingMetamodelException(targetTypeMetamodelURI); | |
} else if (!targetTypeMetamodelURI.equals(sourceType.getPackageNSURI())) { | |
final IGraphNode superTypeEPackage = metamodels.getSingle(); | |
if (!metamodelDependencyExists(metamodelNode, superTypeEPackage)) { | |
LOGGER.debug("supertype dependency from {} to {}", sourceType.getPackageNSURI(), targetTypeMetamodelURI); | |
graph.createRelationship(metamodelNode, superTypeEPackage, IModelIndexer.METAMODEL_DEPENDENCY_EDGE); | |
} | |
} | |
} | |
private boolean metamodelDependencyExists(final IGraphNode metamodelNode, final IGraphNode superTypeEPackage) { | |
boolean alreadythere = false; | |
Iterable<IGraphEdge> depEdges = metamodelNode.getOutgoingWithType(IModelIndexer.METAMODEL_DEPENDENCY_EDGE); | |
for (IGraphEdge r : depEdges) { | |
if (r.getEndNode().equals(superTypeEPackage)) { | |
alreadythere = true; | |
break; | |
} | |
} | |
return alreadythere; | |
} | |
/** | |
* Turns an {@link IHawkClass} into a graph node. | |
* | |
* @param eClass Class to be added. | |
* @throws MissingMetamodelException A required metamodel is missing from the graph. | |
*/ | |
private void addMetaClass(IHawkClass eClass) throws MissingMetamodelException { | |
String id = eClass.getName(); | |
objectCount++; | |
createEClassNode(eClass, id); | |
} | |
/** | |
* Adds a new derived attribute to a type. Returns whether propagation to | |
* current instances of this type is necessary. | |
* | |
* @param metamodeluri | |
* @param typename | |
* @param attributename | |
* @param isMany | |
* @param isOrdered | |
* @param isUnique | |
* @param attributetype | |
* @param derivationlanguage | |
* @param derivationlogic | |
* @param graph | |
* @return | |
*/ | |
public static boolean addDerivedAttribute(String metamodeluri, String typename, String attributename, | |
boolean isMany, boolean isOrdered, boolean isUnique, String attributetype, String derivationlanguage, | |
String derivationlogic, IGraphDatabase graph, IGraphChangeListener listener) { | |
boolean requiresPropagationToInstances = false; | |
try (IGraphTransaction t = graph.beginTransaction()) { | |
listener.changeStart(); | |
IGraphIterable<? extends IGraphNode> ep = graph.getMetamodelIndex().get("id", metamodeluri); | |
IGraphNode packagenode = null; | |
if (ep.size() == 1) { | |
packagenode = ep.getSingle(); | |
} else { | |
throw new Exception("metamodel not found:" + metamodeluri); | |
} | |
IGraphNode typenode = null; | |
for (IGraphEdge e : packagenode.getIncomingWithType("epackage")) { | |
if (e.getStartNode().getProperty(IModelIndexer.IDENTIFIER_PROPERTY).equals(typename)) { | |
typenode = e.getStartNode(); | |
break; | |
} | |
} | |
if (typenode == null) { | |
LOGGER.error("type: {} in: {} does not exist, aborting operation: addDerivedAttribute", typename, metamodeluri); | |
} else { | |
/* | |
* at least one instance already present so derived attribute | |
* needs to be reconfigured for each element already present | |
* (whether the derived attribute is new or existed already and | |
* is being updated) | |
*/ | |
if (typenode.getIncomingWithType(ModelElementNode.EDGE_LABEL_OFTYPE).iterator().hasNext() | |
|| typenode.getIncomingWithType(ModelElementNode.EDGE_LABEL_OFKIND).iterator().hasNext()) | |
requiresPropagationToInstances = true; | |
String[] metadata = new String[7]; | |
metadata[0] = "d"; | |
metadata[1] = (isMany ? "t" : "f"); | |
metadata[2] = (isOrdered ? "t" : "f"); | |
metadata[3] = (isUnique ? "t" : "f"); | |
metadata[4] = attributetype; | |
metadata[5] = derivationlanguage; | |
metadata[6] = derivationlogic; | |
if (typenode.getProperty(attributename) != null) { | |
LOGGER.warn("Attribute already derived, nothing happened!"); | |
requiresPropagationToInstances = false; | |
} else { | |
typenode.setProperty(attributename, metadata); | |
if (LOGGER.isInfoEnabled()) { | |
final String logic = (derivationlogic.length() > 100 | |
? derivationlogic.substring(0, 100) + "\n[! long script, snipped !]" | |
: derivationlogic); | |
LOGGER.info( | |
"Derived attribute added: {}::{}#{} (isMany={}|isOrdered={}|isUnique={}|type={}) {}#\n{}", | |
metamodeluri, typename, attributename, | |
isMany, isOrdered, isUnique, attributetype, derivationlanguage, | |
logic); | |
} | |
} | |
/** Must create the empty index so it will be known by the {@link ModelIndexerImpl} */ | |
graph.getOrCreateNodeIndex(metamodeluri + "##" + typename + "##" + attributename); | |
} | |
t.success(); | |
listener.changeSuccess(); | |
} catch (Exception e1) { | |
LOGGER.error("error in adding a derived attribute to the metamodel", e1); | |
listener.changeFailure(); | |
} | |
return requiresPropagationToInstances; | |
} | |
/** | |
* Adds a new indexed attribute to a type. Returns whether propagation to | |
* current instances of this type is necessary. | |
* | |
* @param metamodeluri | |
* @param typename | |
* @param attributename | |
* @param graph | |
* @param listener2 | |
* @return | |
*/ | |
public static boolean addIndexedAttribute(String metamodeluri, String typename, String attributename, | |
IGraphDatabase graph, IGraphChangeListener listener) { | |
boolean requiresPropagationToInstances = false; | |
try (IGraphTransaction t = graph.beginTransaction()) { | |
listener.changeStart(); | |
IGraphNode packagenode = graph.getMetamodelIndex().get("id", metamodeluri).getSingle(); | |
IGraphNode typenode = null; | |
for (IGraphEdge e : packagenode.getIncomingWithType("epackage")) { | |
if (e.getStartNode().getProperty(IModelIndexer.IDENTIFIER_PROPERTY).equals(typename)) { | |
typenode = e.getStartNode(); | |
break; | |
} | |
} | |
if (typenode == null) { | |
LOGGER.error("type: {} in: {} does not exist, aborting operation: addIndexedAttribute", typename, metamodeluri); | |
} else { | |
// at least one instance already present so indexed attribute | |
// needs | |
// to be reconfigured for each element already present (whether | |
// the | |
// indexed attribute is new or existed already and is being | |
// updated) | |
String[] metadata = (String[]) typenode.getProperty(attributename); | |
if (metadata == null) { | |
LOGGER.error("attribute: {} in: {}#{} does not exist, aborting operation: addIndexedAttribute", attributename, metamodeluri, typename); | |
} else if (!metadata[0].equals("a")) { | |
LOGGER.error("{}#{} is not an attribute, aborting operation: addIndexedAttribute", metamodeluri, typename); | |
} else { | |
if (typenode.getIncomingWithType(ModelElementNode.EDGE_LABEL_OFTYPE).iterator().hasNext() | |
|| typenode.getIncomingWithType(ModelElementNode.EDGE_LABEL_OFKIND).iterator().hasNext()) | |
requiresPropagationToInstances = true; | |
if (metadata.length == 6) { | |
if ("t".equals(metadata[5])) { | |
LOGGER.warn("attribute already indexed, nothing happened!"); | |
requiresPropagationToInstances = false; | |
} else { | |
metadata[5] = "t"; | |
typenode.setProperty(attributename, metadata); | |
// | |
graph.getOrCreateNodeIndex(metamodeluri + "##" + typename + "##" + attributename); | |
// | |
LOGGER.info("indexed attribute added: {}::{}#{}", metamodeluri, typename, attributename); | |
} | |
} else if (metadata.length == 7) { | |
LOGGER.warn("derived attributes are already indexed, nothing happened."); | |
} else { | |
// FIXME log actual exception | |
LOGGER.error("unknown exception in addIndexedAttribute of GraphMetamodelResourceInjector"); | |
} | |
} | |
} | |
t.success(); | |
listener.changeSuccess(); | |
} catch (Exception e) { | |
LOGGER.error("Error in adding an indexed attribute", e); | |
listener.changeFailure(); | |
} | |
return requiresPropagationToInstances; | |
} | |
/** | |
* Removes all the metamodels with the specified URIs. | |
* | |
* @return URIs of the repositories impacted by the removal. | |
*/ | |
public Set<String> removeMetamodels(String[] mmuris) { | |
Set<String> ret = new HashSet<>(); | |
try (IGraphTransaction t = graph.beginTransaction()) { | |
Set<String> indexNames = graph.getNodeIndexNames(); | |
Set<IGraphNodeIndex> markedForRemoval = new HashSet<>(); | |
listener.changeStart(); | |
IGraphNodeIndex ePackageDictionary = graph.getMetamodelIndex(); | |
Set<IGraphNode> epns = new HashSet<>(); | |
for (String mmuri : mmuris) { | |
try { | |
for (Iterator<String> it = indexNames.iterator(); it.hasNext();) { | |
String s = it.next(); | |
if (s.startsWith(mmuri + "##")) { | |
IGraphNodeIndex i = graph.getOrCreateNodeIndex(s); | |
markedForRemoval.add(i); | |
it.remove(); | |
} | |
} | |
epns.add(ePackageDictionary.get("id", mmuri).getSingle()); | |
} catch (Exception e) { | |
LOGGER.error("Metamodel with URI " + mmuri + " not indexed. Nothing happened.", e); | |
} | |
} | |
if (epns.size() > 0) { | |
LOGGER.info("Removing metamodels with URIs {}", Arrays.toString(mmuris)); | |
ret = removeAll(epns); | |
} | |
for (IGraphNodeIndex i : markedForRemoval) { | |
LOGGER.info("Deleting index {} as its metamodel was removed.", i.getName()); | |
i.delete(); | |
} | |
t.success(); | |
listener.changeSuccess(); | |
} catch (Exception e) { | |
listener.changeFailure(); | |
LOGGER.error("Error in removing metamodels " + Arrays.toString(mmuris) + "\n(ALL removal changes reverted)", e); | |
} | |
return ret; | |
} | |
public static boolean removeIndexedAttribute(String metamodelUri, String typename, String attributename, | |
IGraphDatabase graph, CompositeGraphChangeListener listener) { | |
boolean found = false; | |
try (IGraphTransaction t = graph.beginTransaction()) { | |
listener.changeStart(); | |
IGraphNode packagenode = null; | |
IGraphIterable<? extends IGraphNode> ep = graph.getMetamodelIndex().get("id", metamodelUri); | |
if (ep.size() == 1) { | |
packagenode = ep.getSingle(); | |
} else { | |
throw new Exception("metamodel not found:" + metamodelUri); | |
} | |
IGraphNode typenode = null; | |
for (IGraphEdge e : packagenode.getIncomingWithType("epackage")) { | |
if (e.getStartNode().getProperty(IModelIndexer.IDENTIFIER_PROPERTY).equals(typename)) { | |
typenode = e.getStartNode(); | |
break; | |
} | |
} | |
if (typenode == null) { | |
LOGGER.error( | |
"type: {} in: {} does not exist, aborting operation: removeIndexedAttribute", | |
typename, metamodelUri | |
); | |
listener.changeFailure(); | |
} else { | |
String[] metadata = (String[]) typenode.getProperty(attributename); | |
if (metadata == null) { | |
LOGGER.error("attribute: {} in: {}::{} does not exist, aborting operation: removeIndexedAttribute", | |
attributename, metamodelUri, typename); | |
listener.changeFailure(); | |
} else if (!metadata[0].equals("a")) { | |
// System.err.println(Arrays.toString(metadata)); | |
LOGGER.error("{}::{} is a reference not an attribute, aborting operation: removeIndexedAttribute", | |
metamodelUri, typename); | |
listener.changeFailure(); | |
} else { | |
if (metadata.length == 6) { | |
if ("t".equals(metadata[5])) { | |
metadata[5] = "f"; | |
typenode.setProperty(attributename, metadata); | |
// | |
String indexname = metamodelUri + "##" + typename + "##" + attributename; | |
if (graph.nodeIndexExists(indexname)) { | |
graph.getOrCreateNodeIndex(indexname).delete(); | |
found = true; | |
} | |
t.success(); | |
listener.changeSuccess(); | |
} else { | |
LOGGER.error("attribute was not indexed, nothing happened!"); | |
listener.changeFailure(); | |
} | |
} else { | |
LOGGER.error("error in removeIndexedAttribute (metadata.length!=6), nothing happened!"); | |
listener.changeFailure(); | |
} | |
} | |
} | |
} catch (Exception e) { | |
LOGGER.error("Error while removing an indexed attribute", e); | |
listener.changeFailure(); | |
} | |
return found; | |
} | |
public static boolean removeDerivedAttribute(String metamodelUri, String typeName, String attributeName, | |
IGraphDatabase graph, CompositeGraphChangeListener listener) { | |
boolean found = false; | |
try (IGraphTransaction t = graph.beginTransaction()) { | |
listener.changeStart(); | |
IGraphIterable<? extends IGraphNode> ep = graph.getMetamodelIndex().get("id", metamodelUri); | |
IGraphNode packagenode = null; | |
if (ep.size() == 1) { | |
packagenode = ep.getSingle(); | |
} else { | |
throw new Exception("metamodel not found:" + metamodelUri); | |
} | |
IGraphNode typenode = null; | |
for (IGraphEdge e : packagenode.getIncomingWithType("epackage")) { | |
if (e.getStartNode().getProperty(IModelIndexer.IDENTIFIER_PROPERTY).equals(typeName)) { | |
typenode = e.getStartNode(); | |
break; | |
} | |
} | |
if (typenode == null) { | |
LOGGER.error( | |
"type: {} in: {} does not exist, aborting operation: removeDerivedAttribute", | |
typeName, metamodelUri); | |
listener.changeFailure(); | |
} else { | |
String[] metadata = (String[]) typenode.getProperty(attributeName); | |
if (metadata != null) { | |
if (metadata.length == 7 && metadata[0].equals("d")) { | |
LOGGER.info("derived attribute removed: {}::{}", metamodelUri, typeName); | |
IGraphNodeIndex derivedAccessDictionary = graph.getOrCreateNodeIndex(GraphModelInserter.DERIVED_ACCESS_IDXNAME); | |
IGraphNodeIndex derivedProxyDictionary = graph.getOrCreateNodeIndex("derivedproxydictionary"); | |
typenode.removeProperty(attributeName); | |
graph.getOrCreateNodeIndex(metamodelUri + "##" + typeName + "##" + attributeName).delete(); | |
boolean noerror = true; | |
for (IGraphEdge e : (typenode.getIncomingWithType(ModelElementNode.EDGE_LABEL_OFTYPE))) | |
noerror = noerror && removeDerivedAttribute(derivedAccessDictionary, derivedProxyDictionary, | |
attributeName, e); | |
for (IGraphEdge e : (typenode.getIncomingWithType(ModelElementNode.EDGE_LABEL_OFKIND))) | |
noerror = noerror && removeDerivedAttribute(derivedAccessDictionary, derivedProxyDictionary, | |
attributeName, e); | |
if (noerror) | |
found = true; | |
t.success(); | |
listener.changeSuccess(); | |
} else { | |
LOGGER.error("Error in removeDerivedAttribute, attribute metadata not valid"); | |
listener.changeFailure(); | |
} | |
} else { | |
LOGGER.error("Attribute was not already derived, nothing happened!"); | |
listener.changeFailure(); | |
} | |
} | |
} catch (Exception e1) { | |
LOGGER.error("Error in removing a derived attribute to the metamodel", e1); | |
listener.changeFailure(); | |
} | |
return found; | |
} | |
/** | |
* Internal, to remove instance derived attributes | |
*/ | |
private static boolean removeDerivedAttribute(IGraphNodeIndex derivedAccessDictionary, | |
IGraphNodeIndex derivedProxyDictionary, String attributeName, IGraphEdge instanceToTypeEdge) { | |
boolean error = true; | |
IGraphNode n = instanceToTypeEdge.getStartNode(); | |
IGraphEdge dae = null; | |
for (IGraphEdge ed : n.getOutgoingWithType(attributeName)) { | |
if (dae == null) | |
dae = ed; | |
else { | |
LOGGER.error("multiple edges found for derived attribute: {} in node {}", attributeName, n); | |
dae = null; | |
break; | |
} | |
} | |
if (dae == null) { | |
LOGGER.error("derived attribute ({}) not found for node {}", attributeName, n); | |
} else { | |
IGraphNode dan = dae.getEndNode(); | |
dae.delete(); | |
derivedAccessDictionary.remove(dan); | |
derivedProxyDictionary.remove(dan); | |
dan.delete(); | |
error = false; | |
} | |
return !error; | |
} | |
} |