예제 #1
0
  public void importFromFile(String filePath) throws IOException {
    Map<String, Long> cache = new HashMap<String, Long>(COUNT);
    final File storeDir = new File(this.path);
    org.apache.commons.io.FileUtils.deleteDirectory(storeDir);
    BatchInserter batchInserter = new BatchInserterImpl(storeDir.getAbsolutePath());
    final BatchInserterIndexProvider indexProvider =
        new LuceneBatchInserterIndexProvider(batchInserter);
    final BatchInserterIndex index =
        indexProvider.nodeIndex("nodes", MapUtil.stringMap("type", "exact"));
    BufferedReader reader = new BufferedReader(new FileReader(filePath));
    String line = null;
    int nodes = 0;
    long time = System.currentTimeMillis();
    long batchTime = time;
    while ((line = reader.readLine()) != null) {
      final String[] nodeNames = line.split("\\|");
      final String name = nodeNames[0];
      final Map<String, Object> props = MapUtil.map("name", name);
      final long node = batchInserter.createNode(props);
      index.add(node, props);
      cache.put(name, node);
      nodes++;
      if ((nodes % REPORT_COUNT) == 0) {
        System.out.printf(
            "%d nodes created. Took %d %n", nodes, (System.currentTimeMillis() - batchTime));
        batchTime = System.currentTimeMillis();
      }
    }

    System.out.println("Creating nodes took " + (System.currentTimeMillis() - time) / 1000);
    index.flush();
    reader.close();
    reader = new BufferedReader(new FileReader(filePath));
    int rels = 0;
    time = System.currentTimeMillis();
    batchTime = time;
    String relationshipType = "KNOWS";
    while ((line = reader.readLine()) != null) {
      final String[] nodeNames = line.split("\\|");
      final String name = nodeNames[0];
      // final Long from = index.get("name", name).getSingle();
      Long from = cache.get(name);
      for (int j = 1; j < nodeNames.length; j++) {
        // final Long to = index.get("name", nodeNames[j]).getSingle();
        final Long to = cache.get(name);
        batchInserter.createRelationship(
            from, to, DynamicRelationshipType.withName(relationshipType), null);
      }
      rels++;
      if ((rels % REPORT_COUNT) == 0) {
        System.out.printf(
            "%d relationships created. Took %d %n", rels, (System.currentTimeMillis() - batchTime));
        batchTime = System.currentTimeMillis();
      }
    }
    System.out.println("Creating relationships took " + (System.currentTimeMillis() - time) / 1000);
    indexProvider.shutdown();
    batchInserter.shutdown();
  }
 protected List<Node> getRelatedNodes(Node startNode, String type, Direction direction) {
   List<Node> result = new ArrayList<Node>();
   for (Relationship relationship :
       startNode.getRelationships(DynamicRelationshipType.withName(type), direction)) {
     result.add(relationship.getOtherNode(startNode));
   }
   return result;
 }
 @Test
 @Transactional
 public void shouldGetDirectRelationship() throws Exception {
   assertSingleResult(
       "rel1",
       neo4jTemplate
           .convert(referenceNode.getRelationships(DynamicRelationshipType.withName("knows")))
           .to(String.class, new RelationshipNameConverter()));
 }
 @Test
 @Transactional
 public void shouldCreateRelationshipWithProperty() throws Exception {
   Relationship relationship =
       neo4jTemplate.createRelationshipBetween(referenceNode, node1, "has", map("name", "rel2"));
   assertNotNull(relationship);
   assertEquals(referenceNode, relationship.getStartNode());
   assertEquals(node1, relationship.getEndNode());
   assertEquals(HAS.name(), relationship.getType().name());
   assertEquals("rel2", relationship.getProperty("name", "not set"));
 }
  @Test
  public void testCreateRelationshipToNodeOutsideofBatch() throws Exception {
    final Node node1 = restAPI.createNode(map());
    final Transaction tx = restAPI.beginTx();

    Node node2 = restAPI.createNode(map());
    final Relationship relationship =
        node1.createRelationshipTo(node2, DynamicRelationshipType.withName("foo"));

    tx.success();
    tx.finish();
    assertEquals("foo", relationship.getType().name());
    assertEquals(
        "foo", getGraphDatabase().getRelationshipById(relationship.getId()).getType().name());
  }
예제 #6
0
  @Test
  public void deletedRelationshipWithNewTypeShouldNotInfluenceEquality() { // bug test
    try (Transaction tx = database.beginTx()) {
      Node node1 = database.createNode();
      Node node2 = database.createNode();
      node1.createRelationshipTo(node2, DynamicRelationshipType.withName("ACCIDENT"));
      tx.success();
    }

    try (Transaction tx = database.beginTx()) {
      GlobalGraphOperations.at(database).getAllRelationships().iterator().next().delete();
      tx.success();
    }

    String cypher = "CREATE (n), (m)";

    assertSameGraph(database, cypher);
  }
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:template-config-context.xml"})
public class FullNeo4jTemplateTests {
  private static final DynamicRelationshipType KNOWS = DynamicRelationshipType.withName("knows");
  private static final DynamicRelationshipType HAS = DynamicRelationshipType.withName("has");
  @Autowired Neo4jTemplate neo4jTemplate;
  @Autowired protected GraphDatabase graphDatabase;
  protected Node referenceNode;
  protected Relationship relationship1;
  protected Node node1;
  @Autowired PlatformTransactionManager neo4jTransactionManager;

  @BeforeTransaction
  public void cleanDb() {
    Neo4jHelper.cleanDb(neo4jTemplate);
  }

  @Before
  public void setUp() throws Exception {
    Transaction tx = neo4jTemplate.getGraphDatabase().beginTx();
    try {
      Neo4jHelper.cleanDb(neo4jTemplate);
    } finally {
      tx.success();
      tx.finish();
    }
    tx = neo4jTemplate.getGraphDatabase().beginTx();
    try {
      referenceNode = graphDatabase.getReferenceNode();
      createData();
    } finally {
      tx.success();
      tx.finish();
    }
  }

  private void createData() {

    new TransactionTemplate(neo4jTransactionManager)
        .execute(
            new TransactionCallbackWithoutResult() {
              @Override
              protected void doInTransactionWithoutResult(TransactionStatus status) {
                referenceNode.setProperty("name", "node0");
                graphDatabase
                    .createIndex(Node.class, "node", IndexType.SIMPLE)
                    .add(referenceNode, "name", "node0");
                node1 = graphDatabase.createNode(map("name", "node1"));
                relationship1 = referenceNode.createRelationshipTo(node1, KNOWS);
                relationship1.setProperty("name", "rel1");
                graphDatabase
                    .createIndex(Relationship.class, "relationship", IndexType.SIMPLE)
                    .add(relationship1, "name", "rel1");
              }
            });
  }

  @Test
  public void shouldExecuteCallbackInTransaction() throws Exception {
    Node refNode =
        neo4jTemplate.exec(
            new GraphCallback<Node>() {
              @Override
              public Node doWithGraph(GraphDatabase graph) throws Exception {
                Node referenceNode = graph.getReferenceNode();
                referenceNode.setProperty("test", "testDoInTransaction");
                return referenceNode;
              }
            });
    Transaction tx = graphDatabase.beginTx();
    try {
      assertEquals("same reference node", referenceNode, refNode);
      assertTestPropertySet(referenceNode, "testDoInTransaction");
    } finally {
      tx.success();
      tx.finish();
    }
  }

  @Test
  public void shouldRollbackTransactionOnException() {
    try {
      neo4jTemplate.exec(
          new GraphCallback.WithoutResult() {
            @Override
            public void doWithGraphWithoutResult(GraphDatabase graph) throws Exception {
              graph.getReferenceNode().setProperty("test", "shouldRollbackTransactionOnException");
              throw new RuntimeException("please rollback");
            }
          });
    } catch (RuntimeException re) {

    }
    Transaction tx = graphDatabase.beginTx();
    try {
      Assert.assertThat(
          (String) graphDatabase.getReferenceNode().getProperty("test", "not set"),
          not("shouldRollbackTransactionOnException"));
    } finally {
      tx.success();
      tx.finish();
    }
  }

  @Test
  public void shouldRollbackViaStatus() throws Exception {
    new TransactionTemplate(neo4jTransactionManager)
        .execute(
            new TransactionCallbackWithoutResult() {
              @Override
              protected void doInTransactionWithoutResult(final TransactionStatus status) {
                neo4jTemplate.exec(
                    new GraphCallback.WithoutResult() {
                      @Override
                      public void doWithGraphWithoutResult(GraphDatabase graph) throws Exception {
                        graph
                            .getReferenceNode()
                            .setProperty("test", "shouldRollbackTransactionOnException");
                        status.setRollbackOnly();
                      }
                    });
              }
            });
    Transaction tx = graphDatabase.beginTx();
    try {
      Assert.assertThat(
          (String) graphDatabase.getReferenceNode().getProperty("test", "not set"),
          not("shouldRollbackTransactionOnException"));
    } finally {
      tx.success();
      tx.finish();
    }
  }

  @Test(expected = RuntimeException.class)
  public void shouldNotConvertUserRuntimeExceptionToDataAccessException() {
    neo4jTemplate.exec(
        new GraphCallback.WithoutResult() {
          @Override
          public void doWithGraphWithoutResult(GraphDatabase graph) throws Exception {
            throw new RuntimeException();
          }
        });
  }

  @Test(expected = DataAccessException.class)
  @Ignore
  public void shouldConvertMissingTransactionExceptionToDataAccessException() {
    Neo4jTemplate template = new Neo4jTemplate(graphDatabase, null);
    template.exec(
        new GraphCallback.WithoutResult() {
          @Override
          public void doWithGraphWithoutResult(GraphDatabase graph) throws Exception {
            graph.createNode(null);
          }
        });
  }

  @Test(expected = DataAccessException.class)
  public void shouldConvertNotFoundExceptionToDataAccessException() {
    Neo4jTemplate template = new Neo4jTemplate(graphDatabase, neo4jTransactionManager);
    template.exec(
        new GraphCallback.WithoutResult() {
          @Override
          public void doWithGraphWithoutResult(GraphDatabase graph) throws Exception {
            graph.getNodeById(Long.MAX_VALUE);
          }
        });
  }

  @Test(expected = DataAccessException.class)
  public void shouldConvertTemplateNotFoundExceptionToDataAccessException() {
    neo4jTemplate.getNode(Long.MAX_VALUE);
  }

  @Test
  @Transactional
  public void shouldExecuteCallback() throws Exception {
    Long refNodeId =
        neo4jTemplate.exec(
            new GraphCallback<Long>() {
              @Override
              public Long doWithGraph(GraphDatabase graph) throws Exception {
                return graph.getReferenceNode().getId();
              }
            });
    assertEquals(referenceNode.getId(), (long) refNodeId);
  }

  @Test
  @Transactional
  public void testGetReferenceNode() throws Exception {
    assertEquals(referenceNode, neo4jTemplate.getReferenceNode());
  }

  @Test
  @Transactional
  public void testCreateNode() throws Exception {
    Node node = neo4jTemplate.createNode(null);
    assertNotNull("created node", node);
  }

  @Test
  @Transactional
  public void testCreateEntityWithProperties() throws Exception {
    Person person = neo4jTemplate.createNodeAs(Person.class, map("name", "name"));
    assertNotNull("created node", person);
    assertEquals("property created", "name", person.getName());
  }

  @Test
  @Transactional
  public void testCreateNodeTypeWithProperties() throws Exception {
    Node person = neo4jTemplate.createNodeAs(Node.class, map("name", "name"));
    assertNotNull("created node", person);
    assertEquals("property created", "name", person.getProperty("name"));
  }

  @Test
  @Transactional
  public void testCreateNodeWithProperties() throws Exception {
    Node node = neo4jTemplate.createNode(map("test", "testCreateNodeWithProperties"));
    assertTestPropertySet(node, "testCreateNodeWithProperties");
  }

  private void assertTestPropertySet(Node node, String testName) {
    assertEquals(testName, node.getProperty("test", "not set"));
  }

  @Test
  @Transactional
  public void testGetNode() throws Exception {
    Node lookedUpNode = neo4jTemplate.getNode(referenceNode.getId());
    assertEquals(referenceNode, lookedUpNode);
  }

  @Test
  @Transactional
  public void testGetRelationship() throws Exception {
    Relationship lookedUpRelationship = neo4jTemplate.getRelationship(relationship1.getId());
    assertThat(lookedUpRelationship, is(relationship1));
  }

  @Test
  @Transactional
  public void testIndexRelationship() throws Exception {
    Index<Relationship> index = graphDatabase.getIndex("relationship");
    Relationship lookedUpRelationship = index.get("name", "rel1").getSingle();
    assertThat("same relationship from index", lookedUpRelationship, is(relationship1));
  }

  @Test
  @Transactional
  public void testIndexNode() throws Exception {
    neo4jTemplate.index("node", node1, "name", "node1");
    Index<Node> index = graphDatabase.getIndex("node");
    Node lookedUpNode = index.get("name", "node1").getSingle();
    assertThat("same node from index", lookedUpNode, is(node1));
  }

  @Test
  @Transactional
  public void testQueryNodes() throws Exception {
    assertSingleResult(
        "node0",
        neo4jTemplate
            .lookup("node", new TermQuery(new Term("name", "node0")))
            .to(String.class, new PropertyContainerNameConverter()));
  }

  @Test
  @Transactional
  public void testRetrieveNodes() throws Exception {
    assertSingleResult(
        "node0",
        neo4jTemplate
            .lookup("node", "name", "node0")
            .to(String.class, new PropertyContainerNameConverter()));
  }

  @Test
  @Transactional
  public void testQueryRelationships() throws Exception {
    assertSingleResult(
        "rel1",
        neo4jTemplate
            .lookup("relationship", new TermQuery(new Term("name", "rel1")))
            .to(String.class, new PropertyContainerNameConverter()));
  }

  @Test
  @Transactional
  public void testRetrieveRelationships() throws Exception {
    assertSingleResult(
        "rel1",
        neo4jTemplate
            .lookup("relationship", "name", "rel1")
            .to(String.class, new PropertyContainerNameConverter()));
  }

  @SuppressWarnings("deprecation")
  @Test
  @Transactional
  public void testTraverse() throws Exception {
    // final TraversalDescription description =
    // Traversal.description().relationships(KNOWS).prune(Traversal.pruneAfterDepth(1)).filter(Traversal.returnAllButStartNode());
    final TraversalDescription description =
        Traversal.description()
            .relationships(KNOWS)
            .evaluator(Evaluators.toDepth(1))
            .evaluator(Evaluators.excludeStartPosition());
    assertSingleResult(
        "node1",
        neo4jTemplate
            .traverse(referenceNode, description)
            .to(String.class, new PathNodeNameMapper()));
  }

  @Test
  @Transactional
  public void shouldFindNextNodeViaCypher() throws Exception {
    assertSingleResult(
        node1,
        neo4jTemplate.query("start n=node(0) match n-[:knows]->m return m", null).to(Node.class));
  }

  @Test
  @Transactional
  public void shouldGetDirectRelationship() throws Exception {
    assertSingleResult(
        "rel1",
        neo4jTemplate
            .convert(referenceNode.getRelationships(DynamicRelationshipType.withName("knows")))
            .to(String.class, new RelationshipNameConverter()));
  }

  @Test
  @Transactional
  public void shouldGetDirectRelationshipForType() throws Exception {
    assertSingleResult(
        "rel1",
        neo4jTemplate
            .convert(referenceNode.getRelationships(KNOWS))
            .to(String.class, new RelationshipNameConverter()));
  }

  @Test
  @Transactional
  public void shouldGetDirectRelationshipForTypeAndDirection() throws Exception {
    assertSingleResult(
        "rel1",
        neo4jTemplate
            .convert(referenceNode.getRelationships(KNOWS, Direction.OUTGOING))
            .to(String.class, new RelationshipNameConverter()));
  }

  private <T> void assertSingleResult(T expected, Iterable<T> iterable) {
    Iterator<T> result = iterable.iterator();
    assertEquals(expected, result.next());
    assertEquals(false, result.hasNext());
  }

  @Test
  @Transactional
  public void shouldCreateRelationshipWithProperty() throws Exception {
    Relationship relationship =
        neo4jTemplate.createRelationshipBetween(referenceNode, node1, "has", map("name", "rel2"));
    assertNotNull(relationship);
    assertEquals(referenceNode, relationship.getStartNode());
    assertEquals(node1, relationship.getEndNode());
    assertEquals(HAS.name(), relationship.getType().name());
    assertEquals("rel2", relationship.getProperty("name", "not set"));
  }

  private static class PathRelationshipNameMapper
      extends ResultConverter.ResultConverterAdapter<Path, String> {
    @Override
    public String convert(Path path, Class<String> type) {
      return (String) path.lastRelationship().getProperty("name", "not set");
    }
  }

  private static class PathNodeNameMapper
      extends ResultConverter.ResultConverterAdapter<Path, String> {
    @Override
    public String convert(Path path, Class<String> type) {
      return (String) path.endNode().getProperty("name", "not set");
    }
  }

  private static class RelationshipNameConverter
      extends ResultConverter.ResultConverterAdapter<Relationship, String> {
    @Override
    public String convert(Relationship value, Class<String> type) {
      return (String) value.getProperty("name");
    }
  }

  private static class PropertyContainerNameConverter
      extends ResultConverter.ResultConverterAdapter<PropertyContainer, String> {
    @Override
    public String convert(PropertyContainer value, Class<String> type) {
      return (String) value.getProperty("name");
    }
  }
}
/**
 * @author mh
 * @since 12.10.11
 */
public class Neo4jPersistentTestBase {
  private Transaction tx;
  protected Neo4jTemplate template;
  protected NodeEntityStateFactory nodeEntityStateFactory;
  protected RelationshipEntityStateFactory relationshipEntityStateFactory;
  protected EntityStateHandler entityStateHandler;
  protected NodeEntityInstantiator nodeEntityInstantiator;
  protected RelationshipEntityInstantiator relationshipEntityInstantiator;
  protected TypeMapper<Node> nodeTypeMapper;
  protected SourceStateTransmitter<Node> nodeStateTransmitter;
  protected SourceStateTransmitter<Relationship> relationshipStateTransmitter;
  protected ConversionService conversionService;
  protected Neo4jEntityFetchHandler fetchHandler;
  protected Neo4jMappingContext mappingContext;
  protected Neo4jEntityPersister entityPersister;

  protected Group group;
  protected Person michael;
  protected Person emil;
  protected Person andres;
  public static final RelationshipType PERSONS = DynamicRelationshipType.withName("persons");
  protected static final RelationshipType KNOWS = DynamicRelationshipType.withName("knows");

  @NodeEntity
  public static class Developer {
    @GraphId Long id;
    String name;
  }

  @Before
  public void setUp() throws Exception {
    // todo cleanup !!
    mappingContext = new Neo4jMappingContext();
    MappingInfrastructure infrastructure = createInfrastructure(mappingContext);

    template = new Neo4jTemplate(infrastructure);
    nodeEntityStateFactory = createNodeEntityStateFactory(mappingContext);
    relationshipEntityStateFactory = createRelationshipEntityStateFactory(mappingContext);
    infrastructure.setNodeEntityStateFactory(nodeEntityStateFactory);
    infrastructure.setRelationshipEntityStateFactory(relationshipEntityStateFactory);
    template.postConstruct();

    entityStateHandler = infrastructure.getEntityStateHandler();

    nodeEntityInstantiator = new NodeEntityInstantiator(entityStateHandler);
    relationshipEntityInstantiator = new RelationshipEntityInstantiator(entityStateHandler);
    nodeTypeMapper =
        new DefaultTypeMapper<Node>(
            new TRSTypeAliasAccessor<Node>(infrastructure.getNodeTypeRepresentationStrategy()),
            asList(new ClassValueTypeInformationMapper()));
    nodeStateTransmitter = new SourceStateTransmitter<Node>(nodeEntityStateFactory);
    relationshipStateTransmitter =
        new SourceStateTransmitter<Relationship>(relationshipEntityStateFactory);
    conversionService = template.getConversionService();

    fetchHandler =
        new Neo4jEntityFetchHandler(
            entityStateHandler,
            conversionService,
            nodeStateTransmitter,
            relationshipStateTransmitter);
    final EntityTools<Node> nodeEntityTools =
        new EntityTools<Node>(
            infrastructure.getNodeTypeRepresentationStrategy(),
            nodeEntityStateFactory,
            nodeEntityInstantiator);
    final EntityTools<Relationship> relationshipEntityTools =
        new EntityTools<Relationship>(
            infrastructure.getRelationshipTypeRepresentationStrategy(),
            relationshipEntityStateFactory,
            relationshipEntityInstantiator);

    entityPersister =
        new Neo4jEntityPersister(
            conversionService,
            nodeEntityTools,
            relationshipEntityTools,
            mappingContext,
            entityStateHandler);

    tx = template.beginTx();
    group = new Group();
    michael = new Person("Michael", 37);
    emil = new Person("Emil", 30);
    andres = new Person("Andrés", 36);
  }

  private NodeEntityStateFactory createNodeEntityStateFactory(Neo4jMappingContext mappingContext) {
    final NodeEntityStateFactory nodeEntityStateFactory = new NodeEntityStateFactory();
    nodeEntityStateFactory.setMappingContext(mappingContext);
    nodeEntityStateFactory.setTemplate(template);
    nodeEntityStateFactory.setNodeDelegatingFieldAccessorFactory(
        new NodeDelegatingFieldAccessorFactory(template));
    return nodeEntityStateFactory;
  }

  private RelationshipEntityStateFactory createRelationshipEntityStateFactory(
      Neo4jMappingContext mappingContext) {
    final RelationshipEntityStateFactory relationshipEntityStateFactory =
        new RelationshipEntityStateFactory();
    relationshipEntityStateFactory.setMappingContext(mappingContext);
    relationshipEntityStateFactory.setTemplate(template);
    relationshipEntityStateFactory.setRelationshipDelegatingFieldAccessorFactory(
        new RelationshipDelegatingFieldAccessorFactory(template));
    return relationshipEntityStateFactory;
  }

  private MappingInfrastructure createInfrastructure(Neo4jMappingContext mappingContext)
      throws Exception {
    MappingInfrastructure infrastructure = new MappingInfrastructure();
    final GraphDatabaseService gdb = new ImpermanentGraphDatabase();
    infrastructure.setGraphDatabaseService(gdb);
    final DelegatingGraphDatabase graphDatabase = new DelegatingGraphDatabase(gdb);
    infrastructure.setGraphDatabase(graphDatabase);
    infrastructure.setMappingContext(mappingContext);
    final EntityStateHandler entityStateHandler =
        new EntityStateHandler(mappingContext, graphDatabase);
    infrastructure.setNodeTypeRepresentationStrategy(new NoopNodeTypeRepresentationStrategy());
    infrastructure.setRelationshipTypeRepresentationStrategy(
        new NoopRelationshipTypeRepresentationStrategy());
    infrastructure.setConversionService(new Neo4jConversionServiceFactoryBean().getObject());
    infrastructure.setEntityStateHandler(entityStateHandler);
    return infrastructure;
  }

  protected List<Node> groupMemberNodes() {
    return groupMemberNodes(groupNode());
  }

  private List<Node> groupMemberNodes(Node node) {
    return getRelatedNodes(node, "persons", Direction.OUTGOING);
  }

  @After
  public void tearDown() throws Exception {
    tx.failure();
    tx.finish();
    template.getGraphDatabaseService().shutdown();
  }

  protected Node michaelNode() {
    return template.getNode(michael.getId());
  }

  protected Node createNewNode() {
    return template.createNode();
  }

  protected Group storeInGraph(Group g) {
    final Long id = g.getId();
    if (id != null) {
      write(g, template.getNode(id));
    } else {
      write(g, null);
    }
    return g;
  }

  protected Person storeInGraph(Person p) {
    final Long id = p.getId();
    if (id != null) {
      write(p, template.getNode(id));
    } else {
      write(p, null);
    }
    return p;
  }

  protected Object write(Object entity, Node node) {
    entityPersister.write(entity, node);
    return entity;
  }

  @SuppressWarnings("unchecked")
  private <T> T storeInGraph(T obj) {
    return (T) write(obj, null);
  }

  protected Node groupNode() {
    return template.getNode(group.getId());
  }

  protected <T> Set<T> set(T... objs) {
    return new HashSet<T>(asList(objs));
  }

  protected <T> Set<T> set(Iterable<T> objs) {
    return IteratorUtil.addToCollection(objs, new HashSet<T>());
  }

  protected Node andresNode() {
    return template.getNode(andres.getId());
  }

  protected Node emilNode() {
    return template.getNode(emil.getId());
  }

  public Person readPerson(Node node) {
    return entityPersister.read(Person.class, node);
  }

  protected Relationship makeFriends(Node from, Node to, int years) {
    Relationship friendship = from.createRelationshipTo(to, KNOWS);
    friendship.setProperty("Friendship.years", years);
    return friendship;
  }

  public Group readGroup(Node node) {
    return entityPersister.read(Group.class, node);
  }

  protected List<Node> getRelatedNodes(Node startNode, String type, Direction direction) {
    List<Node> result = new ArrayList<Node>();
    for (Relationship relationship :
        startNode.getRelationships(DynamicRelationshipType.withName(type), direction)) {
      result.add(relationship.getOtherNode(startNode));
    }
    return result;
  }
}