/** * Copyright (c) 2013 Atlanmod INRIA LINA Mines Nantes * 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: * Atlanmod INRIA LINA Mines Nantes - initial API and implementation * Descritpion ! To come * @author Sunye */ package fr.inria.atlanmod.neo4emf.drivers; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EReference; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.DynamicRelationshipType; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.index.Index; import org.neo4j.graphdb.index.IndexManager; import fr.inria.atlanmod.neo4emf.INeo4emfObject; import fr.inria.atlanmod.neo4emf.PersistentPackage; import fr.inria.atlanmod.neo4emf.RelationshipMapping; import fr.inria.atlanmod.neo4emf.drivers.impl.NETransaction; import fr.inria.atlanmod.neo4emf.drivers.impl.Neo4JTransaction; /** * @author sunye */ public class NEConnection { /** * Current state of the session */ private ConnectionState state = ConnectionState.CLOSED; /** * The Neo4j database service. */ private final GraphDatabaseService service; /** * Registered PersistentPackage for this session. */ private final PersistentPackage epackage; /** * Mapping between ERefs and Relationships */ private final RelationshipMapping mapping; /** * Index for EClass nodes. */ private Index<Node> metaIndex; private Index<Relationship> relationshipIndex; /** * Node representing the resource. */ private Node resourceNode; /** * Nodes representing the EClasses for the EPackage. */ private Node[] nodeTypes; /** * Cache for already loaded nodes. */ private Map<Long, INeo4emfObject> cache = new TreeMap<Long, INeo4emfObject>(); public NEConnection(GraphDatabaseService gdb, NEConfiguration configuration) { assert gdb != null : "Null Database Service"; assert configuration != null : "Null Configuration"; service = gdb; epackage = configuration.ePackage(); mapping = epackage.getRelationshipMapping(); } /** * Opens a connection with the DBMS. */ public void open() { assert state == ConnectionState.CLOSED : "Connection already open"; state = ConnectionState.OPEN; Transaction tx = service.beginTx(); try { this.initializeIndexes(); this.initializeResource(); this.initializePackage(); tx.success(); } catch (Exception e) { tx.failure(); } finally { tx.finish(); } } /** * Closes the connection with the DBMS */ public void close() { assert state == ConnectionState.OPEN : "Connection already closed"; resourceNode = null; nodeTypes = null; metaIndex = null; cache.clear(); state = ConnectionState.CLOSED; service.shutdown(); } /** * Creates and starts a transaction. All database operations must be * performed within a transaction. * * @return */ public NETransaction createTransaction() { assert state == ConnectionState.OPEN : "Connection closed"; return new Neo4JTransaction(service.beginTx()); } /** * Creates a database node from a PersistentObject and adds a link * "INSTANCE OF" between the new node and the object's EClass node. * * @param obj */ public Node addObject(INeo4emfObject obj, boolean isTmp) { Node newNode; try { if(obj.eContainer() == null && obj.eResource() != null) { // Root object newNode = this.addRootObject(obj, isTmp); } else { newNode = this.basicAddNode(obj, isTmp); } }catch(Exception e) { newNode = null; } return newNode; } public Node addObject(INeo4emfObject obj) { return this.addObject(obj, false); } /** * Creates an EObject from an ID. The object type (EClass) is retrieved. * Attribute values are not loaded. * * @param id * @return the new EObject. */ public INeo4emfObject retrieveObject(long id) { INeo4emfObject result = cache.get(id); if (result == null) { Node node = service.getNodeById(id); Relationship r = node.getSingleRelationship( IPersistenceService.INSTANCE_OF, Direction.OUTGOING); Node typeNode = r.getEndNode(); int nodeId = (int) typeNode .getProperty(IPersistenceService.ECLASS_ID); EClass klass = (EClass) epackage.getEClassifiers().get(nodeId); result = (INeo4emfObject) epackage.getEFactoryInstance().create( klass); result.setNodeId(id); cache.put(id, result); } return result; } /** * * @param obj */ public void saveAllAttributes(INeo4emfObject obj) { Node n = service.getNodeById(obj.getNodeId()); obj.saveAllAttributesTo(n); } /** * Load all attributes of a EObject. * * @param obj */ public void loadAttributes(INeo4emfObject obj) { assert obj != null : "Null Persistent Object"; Node n = service.getNodeById(obj.getNodeId()); obj.loadAllAttributesFrom(n); } /** * Load all references of a EObject. * * @param obj */ public void loadReferences(INeo4emfObject obj) { Node n = service.getNodeById(obj.getNodeId()); // TODO // obj.loadFrom(n); } /** * Removes a link between two persistent objects * * @param source * the source persistent object * @param eRef * the link type * @param target * the target persistent object */ public boolean removeLink(INeo4emfObject source, EReference eRef, INeo4emfObject target) { boolean found = false; try { Node sourceNode = service.getNodeById(source.getNodeId()); Node targetNode = service.getNodeById(target.getNodeId()); RelationshipType rel = mapping.relationshipAt(source.eClass() .getClassifierID(), eRef.getFeatureID()); Iterator<Relationship> it = sourceNode.getRelationships(rel) .iterator(); while (!found && it.hasNext()) { Relationship relship = it.next(); if (relship.getEndNode() == targetNode) { relship.delete(); found = true; } } } catch (Exception e) { e.printStackTrace(); return false; } return found; } /** * Adds a link between two Nodes. * * @param source * the source persistent object. * @param eRef * the link type. * @param target * the target persistent object. * @return true if the link was successfully added. False otherwise. */ public boolean addLink(INeo4emfObject source, EReference eRef, INeo4emfObject target) { try { Node sourceNode = service.getNodeById(source.getNodeId()); Node targetNode = service.getNodeById(target.getNodeId()); RelationshipType rel = mapping.relationshipAt(source.eClass() .getClassifierID(), eRef.getFeatureID()); sourceNode.createRelationshipTo(targetNode, rel); return true; } catch (Exception e) { return false; } } public void loadReferences(INeo4emfObject source, EReference eRef) { List<INeo4emfObject> result = new LinkedList<INeo4emfObject>(); RelationshipType rel = mapping.relationshipAt(source.eClass() .getClassifierID(), eRef.getFeatureID()); Node sourceNode = service.getNodeById(source.getNodeId()); for (Relationship each : sourceNode.getRelationships(rel, Direction.OUTGOING)) { long id = each.getEndNode().getId(); result.add(this.retrieveObject(id)); } } /** * Adds a node representing a persistent object to the database, a 'IS_ROOT' * link between the new node and the resource and a 'INSTANCE-OF' link * between the new node and the node that represents the object's type * (EClass). * * @param obj * @return */ public Node addRootObject(INeo4emfObject obj, boolean isTmp) { assert obj.eContainer() == null : "Container not null (not a root node)"; try { Node newNode = this.basicAddNode(obj, isTmp); Relationship isRootRel = resourceNode.createRelationshipTo(newNode, IPersistenceService.IS_ROOT); if(isTmp) { relationshipIndex.add(isRootRel, IPersistenceService.ID_META, IPersistenceService.TMP_RELATIONSHIP); } return newNode; } catch (Exception e) { return null; } } /** * Adds a node representing a persistent object to the database and an * 'INSTANCE-OF' link between the new node and the node that represents the * object's type (EClass). * * @param obj * @return * @throws Exception */ private Node basicAddNode(INeo4emfObject obj, boolean isTmp) throws Exception { assert obj.eClass().getClassifierID() < nodeTypes.length : "Invalid type ID"; int typeID = obj.eClass().getClassifierID(); Node newNode = service.createNode(); obj.setNodeId(newNode.getId()); Relationship instanceOfRel = newNode.createRelationshipTo(nodeTypes[typeID], IPersistenceService.INSTANCE_OF); if(isTmp) { metaIndex.add(newNode,IPersistenceService.ID_META,IPersistenceService.TMP_NODE); relationshipIndex.add(instanceOfRel, IPersistenceService.ID_META, IPersistenceService.TMP_RELATIONSHIP); } return newNode; } public Node addAttribute(INeo4emfObject obj) { Node base = service.getNodeById(obj.getNodeId()); Node attributeNode = service.createNode(); obj.setAttributeNodeId(attributeNode.getId()); Relationship setAttributeRel = base.createRelationshipTo(attributeNode, IPersistenceService.SET_ATTRIBUTE); metaIndex.add(attributeNode, IPersistenceService.ID_META, IPersistenceService.TMP_NODE); relationshipIndex.add(setAttributeRel, IPersistenceService.ID_META, IPersistenceService.TMP_RELATIONSHIP); return attributeNode; } public void removeObject(INeo4emfObject obj) { Node objectNode = service.getNodeById(obj.getNodeId()); Iterator<Relationship> it = objectNode.getRelationships().iterator(); while(it.hasNext()) { it.next().delete(); } objectNode.delete(); } public void createRelationship(INeo4emfObject from, INeo4emfObject to, RelationshipType relType) { Node fromNode = service.getNodeById(from.getNodeId()); Node toNode = service.getNodeById(to.getNodeId()); fromNode.createRelationshipTo(toNode, relType); } public Relationship addTmpRelationshipBetween(INeo4emfObject from, INeo4emfObject to, RelationshipType relType) { Node fromNode; Node toNode; if(from.getNodeId() == -1) { if(from.eResource() != null) { /* * Happen when a user first add a non containment link then * add the referenced object to its container. */ fromNode = this.addObject(from, true); } else { /* * Else do nothing, there is no reason to save the object */ return null; } } else { fromNode = service.getNodeById(from.getNodeId()); } if(to.getNodeId() == -1) { if(to.eResource() != null) { /* * Happen when a user first add a non containment link then * add the referenced object to its container. */ toNode = this.addObject(to, true); } else { /* * Else do nothing, there is no reason to save the object */ return null; } } else { toNode = service.getNodeById(to.getNodeId()); } /* * Remove the DELETE relationship that may have been generated. (This * happens when the EObject has been removed from its container). */ Iterator<Relationship> it = fromNode.getRelationships( IPersistenceService.DELETE).iterator(); while (it.hasNext()) { it.next().delete(); } /* * If there is a REMOVE_LINK relationship with the same base * RelationshipType removes it. In that case it is not necessary to * create a ADD_LINK relationship because the base graph contains the * base RelationshipType. */ it = fromNode.getRelationships(IPersistenceService.REMOVE_LINK) .iterator(); while (it.hasNext()) { Relationship rel = it.next(); if (rel.getProperty("gen_rel").equals(relType.name()) && rel.getOtherNode(fromNode).getId() == toNode.getId()) { rel.delete(); return null; } } Relationship addLinkRel = fromNode.createRelationshipTo(toNode, IPersistenceService.ADD_LINK); addLinkRel.setProperty("gen_rel", relType.name()); relationshipIndex.add(addLinkRel, IPersistenceService.ID_META, IPersistenceService.TMP_RELATIONSHIP); return addLinkRel; } public void removeRelationship(INeo4emfObject from, INeo4emfObject to, RelationshipType relType) { Node fromNode = service.getNodeById(from.getNodeId()); Node toNode = service.getNodeById(to.getNodeId()); Iterator<Relationship> it = fromNode.getRelationships(relType).iterator(); while(it.hasNext()) { Relationship rel = it.next(); // TODO check if the toNode is needed ? if(rel.getEndNode().getId() == toNode.getId()) { rel.delete(); // TODO add a return ? } } } public Relationship removeTmpRelationshipBetween(INeo4emfObject from, INeo4emfObject to, RelationshipType relType) { Node fromNode = service.getNodeById(from.getNodeId()); Node toNode = service.getNodeById(to.getNodeId()); /* * If there is a ADD_LINK relationship with the same base * RelationshipType removes it. In that case it is not necessary to * create a REMOVE_LINK relationship because the base graph doesn't have * a RelationshipType relationship between from and to. */ Iterator<Relationship> it = fromNode.getRelationships( IPersistenceService.ADD_LINK).iterator(); while (it.hasNext()) { Relationship rel = it.next(); if(rel.getProperty("gen_rel").equals(relType.name()) && rel.getOtherNode(fromNode).getId() == toNode.getId()) { rel.delete(); return null; } } Relationship removeLinkRel = fromNode.createRelationshipTo(toNode, IPersistenceService.REMOVE_LINK); removeLinkRel.setProperty("gen_rel", relType.name()); relationshipIndex.add(removeLinkRel, IPersistenceService.ID_META, IPersistenceService.TMP_RELATIONSHIP); return removeLinkRel; } public Relationship addDeleteRelationship(INeo4emfObject obj) { Node objectNode = service.getNodeById(obj.getNodeId()); Relationship deleteRel = objectNode.createRelationshipTo(objectNode, IPersistenceService.DELETE); relationshipIndex.add(deleteRel, IPersistenceService.ID_META, IPersistenceService.TMP_RELATIONSHIP); return deleteRel; } public void cleanIndexes() { Iterator<Relationship> relIt = relationshipIndex.get(IPersistenceService.ID_META, IPersistenceService.TMP_RELATIONSHIP).iterator(); while(relIt.hasNext()) { relIt.next().delete(); } Iterator<Node> nodesIt = metaIndex.get(IPersistenceService.ID_META, IPersistenceService.TMP_NODE).iterator(); while(nodesIt.hasNext()) { nodesIt.next().delete(); } } public void flushTmpRelationships(List<Relationship> rels) { // Iterator<Relationship> it = relationshipIndex.get(IPersistenceService.ID_META, IPersistenceService.TMP_RELATIONSHIP).iterator(); Iterator<Relationship> it = rels.iterator(); while(it.hasNext()) { Relationship r = it.next(); if(r.getType().equals(IPersistenceService.SET_ATTRIBUTE)) { Node baseNode = r.getStartNode(); Node attributeNode = r.getEndNode(); Iterator<String> updatedProperties = attributeNode.getPropertyKeys().iterator(); while(updatedProperties.hasNext()) { String propKey = updatedProperties.next(); baseNode.setProperty(propKey, attributeNode.getProperty(propKey)); } relationshipIndex.remove(r); r.delete(); attributeNode.delete(); } else if(r.getType().equals(IPersistenceService.ADD_LINK)) { String baseRelationName = (String) r.getProperty("gen_rel"); Node from = r.getStartNode(); Node to = r.getEndNode(); // also fix that from.createRelationshipTo(to, DynamicRelationshipType.withName(baseRelationName)); relationshipIndex.remove(r); r.delete(); metaIndex.remove(from, IPersistenceService.ID_META); metaIndex.remove(to, IPersistenceService.ID_META); // fix that, ugly Iterator<Relationship> fromIO = from.getRelationships(IPersistenceService.INSTANCE_OF).iterator(); Iterator<Relationship> toIO = to.getRelationships(IPersistenceService.INSTANCE_OF).iterator(); while(fromIO.hasNext()) { relationshipIndex.remove(fromIO.next(),IPersistenceService.ID_META); } while(toIO.hasNext()) { relationshipIndex.remove(toIO.next(),IPersistenceService.ID_META); } } else if(r.getType().equals(IPersistenceService.REMOVE_LINK)) { /* * Find a more efficient implementation (maybe with RelationShipType directly * as a relationship property */ String baseRelationName = (String) r.getProperty("gen_rel"); Node from = r.getStartNode(); Node to = r.getEndNode(); Iterator<Relationship> relationships = from.getRelationships().iterator(); while(relationships.hasNext()) { Relationship rel = relationships.next(); if(rel.getType().toString().equals(baseRelationName) && rel.getEndNode().equals(to)) { rel.delete(); break; } } relationshipIndex.remove(r); r.delete(); } else if(r.getType().equals(IPersistenceService.DELETE)) { Node n = r.getStartNode(); relationshipIndex.remove(r); r.delete(); Iterator<Relationship> instanceOfRels = n.getRelationships(IPersistenceService.INSTANCE_OF).iterator(); while(instanceOfRels.hasNext()) { instanceOfRels.next().delete(); } n.delete(); } else if(r.getType().equals(IPersistenceService.IS_ROOT)) { relationshipIndex.remove(r); } } } /* * Creates indexes for Meta-classes (ECLass), if absent. */ private void initializeIndexes() { assert state == ConnectionState.OPEN : "Connection closed"; IndexManager manager = service.index(); metaIndex = manager.forNodes(IPersistenceService.META_ELEMENTS); // [Unload add] relationshipIndex = manager.forRelationships(IPersistenceService.META_RELATIONSHIPS); // /[Unload add] } /* * Creates a node corresponding to a resource and adds it to the * meta-classes index, if absent. */ private void initializeResource() { assert metaIndex != null : "Null meta index"; resourceNode = metaIndex.get(IPersistenceService.ID_META, IPersistenceService.RESOURCE_NODE).getSingle(); if (resourceNode == null) { resourceNode = service.createNode(); metaIndex.putIfAbsent(resourceNode, IPersistenceService.ID_META, IPersistenceService.RESOURCE_NODE); } } /* * Creates nodes corresponding to all meta-classes of the associated package, if needed * and adds these nodes to the nodeTypes array. */ private void initializePackage() { assert epackage != null; assert metaIndex != null; nodeTypes = new Node[epackage.getEClassifiers().size()]; for (EClassifier each : epackage.getEClassifiers()) { String id = each.getEPackage().getName() + "_" + each.getClassifierID(); Node n = metaIndex.get(IPersistenceService.ID_META, id).getSingle(); if (n == null) { n = this.createTypeNode(id, each); } nodeTypes[each.getClassifierID()] = n; } } /* * creates a node corresponding to a meta-class and adds it to the meta-classes index. */ private Node createTypeNode(String id, EClassifier type) { Node n = service.createNode(); n.setProperty(IPersistenceService.ECLASS_NAME, type.getName()); n.setProperty(IPersistenceService.NS_URI, type.getEPackage().getNsURI()); n.setProperty(IPersistenceService.ECLASS_ID, type.getClassifierID()); metaIndex.putIfAbsent(n, IPersistenceService.ID_META, id); return n; } /** * @deprecated Use {@link #addObject()} instead */ @Deprecated public Node createNode() { return service.createNode(); } public Node getNodeById(long id) { return service.getNodeById(id); } public Relationship getRelationshipById(long id) { return service.getRelationshipById(id); } public IndexManager index() { return service.index(); } enum ConnectionState { OPEN, CLOSED } }