package org.qi4j.entitystore.neo4j; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.DynamicRelationshipType; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.qi4j.api.common.QualifiedName; import org.qi4j.api.entity.EntityDescriptor; import org.qi4j.api.entity.EntityReference; import org.qi4j.api.property.PropertyDescriptor; import org.qi4j.api.type.ValueType; import org.qi4j.api.value.ValueSerialization; import org.qi4j.api.value.ValueSerializationException; import org.qi4j.spi.entity.EntityState; import org.qi4j.spi.entity.EntityStatus; import org.qi4j.spi.entity.ManyAssociationState; import org.qi4j.spi.entitystore.EntityStoreException; import org.qi4j.spi.entitystore.EntityStoreUnitOfWork; public class NeoEntityState implements EntityState { static final String ENTITY_ID = "entity_id"; static final String VERSION = "version"; static final String MODIFIED = "modified"; private final NeoEntityStoreUnitOfWork uow; private final ValueSerialization valueSerialization; private final Node underlyingNode; private EntityStatus status; NeoEntityState( ValueSerialization valueSerialization, NeoEntityStoreUnitOfWork work, Node node, EntityStatus status ) { this.valueSerialization = valueSerialization; this.uow = work; this.underlyingNode = node; this.status = status; } protected void setUpdated() { if( status == EntityStatus.LOADED ) { status = EntityStatus.UPDATED; Long version = (Long) underlyingNode.getProperty( VERSION ); underlyingNode.setProperty( VERSION, version + 1 ); underlyingNode.setProperty( MODIFIED, uow.currentTime() ); } } static RelationshipType manyAssociation( QualifiedName stateName ) { return DynamicRelationshipType.withName( "many_association::" + stateName.toString() ); } static RelationshipType association( QualifiedName stateName ) { return DynamicRelationshipType.withName( "association::" + stateName.toString() ); } @Override public ManyAssociationState manyAssociationValueOf( QualifiedName stateName ) { RelationshipType manyAssociation = manyAssociation( stateName ); Relationship rel = underlyingNode.getSingleRelationship( manyAssociation, Direction.OUTGOING ); if( rel != null ) { return new NeoManyAssociationState( uow, this, rel.getEndNode() ); } Node node = uow.getNeo().createNode(); node.setProperty( NeoManyAssociationState.COUNT, 0 ); underlyingNode.createRelationshipTo( node, manyAssociation ); return new NeoManyAssociationState( uow, this, node ); } @Override public EntityReference associationValueOf( QualifiedName stateName ) { Relationship rel = underlyingNode.getSingleRelationship( association( stateName ), Direction.OUTGOING ); if( rel != null ) { String entityId = (String) rel.getEndNode().getProperty( ENTITY_ID ); return new EntityReference( entityId ); } return null; } @Override public void setAssociationValue( QualifiedName stateName, EntityReference newEntity ) { RelationshipType association = association( stateName ); Relationship rel = underlyingNode.getSingleRelationship( association, Direction.OUTGOING ); if( rel != null ) { Node otherNode = rel.getEndNode(); if( otherNode.getProperty( ENTITY_ID ).equals( identity().identity() ) ) { otherNode.delete(); } rel.delete(); } if( newEntity != null ) { Node otherNode = uow.getEntityStateNode( newEntity ); if( otherNode.equals( underlyingNode ) ) { // create a blank node for self reference otherNode = uow.getNeo().createNode(); otherNode.setProperty( ENTITY_ID, identity().identity() ); } underlyingNode.createRelationshipTo( otherNode, association ); } } @Override public Object propertyValueOf( QualifiedName stateName ) { try { PropertyDescriptor persistentProperty = entityDescriptor().state().findPropertyModelByQualifiedName( stateName ); Object prop = underlyingNode.getProperty( "prop::" + stateName.toString(), null ); if( prop == null ) { return null; } else if( ValueType.isPrimitiveValueType( persistentProperty.valueType() ) ) { return prop; } else { return valueSerialization.deserialize( persistentProperty.valueType(), prop.toString() ); } } catch( ValueSerializationException e ) { throw new EntityStoreException( e ); } } @Override public void setPropertyValue( QualifiedName stateName, Object prop ) { try { if( prop != null ) { PropertyDescriptor persistentProperty = entityDescriptor().state().findPropertyModelByQualifiedName( stateName ); if( ValueType.isPrimitiveValueType( persistentProperty.valueType() ) ) { underlyingNode.setProperty( "prop::" + stateName.toString(), prop ); } else { String jsonString = valueSerialization.serialize( prop ); underlyingNode.setProperty( "prop::" + stateName.toString(), jsonString ); } } else { underlyingNode.removeProperty( stateName.toString() ); } setUpdated(); } catch( ValueSerializationException e ) { throw new EntityStoreException( e ); } } @Override public void remove() { // Apparently remove should just force remove associations instead // of throwing exception if the entity has incomming associations // if ( underlyingNode.hasRelationship( Direction.INCOMING ) ) // { // throw new IllegalStateException( // "Cannot remove entity with reference: " + identity() // + ". It has incoming associtaions."); // } // remove of all incomming associations for( Relationship rel : underlyingNode.getRelationships( Direction.INCOMING ) ) { rel.delete(); } uow.getIndexService().removeIndex( underlyingNode, NeoEntityStoreUnitOfWork.ENTITY_STATE_ID, underlyingNode.getProperty( ENTITY_ID ) ); for( Relationship rel : underlyingNode.getRelationships( Direction.OUTGOING ) ) { Node endNode = rel.getEndNode(); boolean manyAssocNode = false; for( Relationship manyRel : endNode.getRelationships( RelTypes.MANY_ASSOCIATION, Direction.OUTGOING ) ) { manyRel.delete(); manyAssocNode = true; } if( manyAssocNode ) { endNode.delete(); } rel.delete(); } underlyingNode.delete(); status = EntityStatus.REMOVED; } @Override public EntityDescriptor entityDescriptor() { Node typeNode = underlyingNode.getSingleRelationship( RelTypes.IS_OF_TYPE, Direction.OUTGOING ).getEndNode(); String type = (String) typeNode.getProperty( NeoEntityStoreUnitOfWork.ENTITY_TYPE ); return uow.getEntityDescriptor( type ); } public void hasBeenApplied() { // TODO } @Override public EntityReference identity() { return new EntityReference( (String) underlyingNode.getProperty( ENTITY_ID ) ); } @Override public boolean isAssignableTo( Class<?> type ) { Node typeNode = underlyingNode.getSingleRelationship( RelTypes.IS_OF_TYPE, Direction.OUTGOING ).getEndNode(); String typeName = (String) typeNode.getProperty( NeoEntityStoreUnitOfWork.ENTITY_TYPE ); return typeName.equals( type.getName() ); } @Override public long lastModified() { long modified = (Long) underlyingNode.getProperty( MODIFIED ); return modified; } @Override public EntityStatus status() { return status; } @Override public String version() { long version = (Long) underlyingNode.getProperty( VERSION ); if( status == EntityStatus.UPDATED ) { version--; } return "" + version; } public EntityStoreUnitOfWork unitOfWork() { return uow; } }