0
votes

I got stuck with an obviously quite common problem and was wondering what exactly I am doing wrong here.

I have two nodes of type "Account" and want to add a "FRIEND" relation between them. But I will not simply add the relation before the counterpart account is able to either accept or decline the friendship request (usual flow). So I created a rich relationship called "Friendship".

My unit tests show that the request will be added to the counterpart account but not on the initators one.

Here's an excerpt of my code I use so far:

   @Fetch
   @RelatedToVia(type =  "FRIEND", direction = Direction.BOTH)
   private Set<Friendship> friends;
   ...
   ... 
   public void addFriendshipRequest(Friendship friendship) {
      this.friends.add(friendship);
   }

   public Set<Friendship> getConfirmedFriends() {
    Set<Friendship> friendships = new HashSet<Friendship>();
    for(Friendship f : this.friends) {
        if(f.getState() == Friendship.State.CONFIRMED) {
            friendships.add(f);
        }
    }
    return friendships;
}

public Set<Friendship> getFriendships() {
    return this.friends;
}

public Set<Friendship> getFriendRequests() {
    Set<Friendship> requests = new HashSet<Friendship>();
    for(Friendship f : this.friends) {
        if(f.getState() == Friendship.State.REQUESTED) {
            requests.add(f);
        }
    }
    return requests;
} 
...
...
}

Now my "Friendship" Relationship entity

import org.springframework.data.neo4j.annotation.EndNode;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.RelationshipEntity;
import org.springframework.data.neo4j.annotation.StartNode;
import java.io.Serializable;
import java.util.Date;

@RelationshipEntity(type = "FRIEND")
public class Friendship implements Serializable {

public enum State { REQUESTED, CONFIRMED }

@GraphId
private Long id;

@StartNode
private Account requester;

@EndNode
private Account confirmer;

private State state;

private Date dateOfRequest;

private Date dateOfConfirmation;

public Friendship(Account requester, Account target) {
    this.state = State.REQUESTED;
    this.confirmer = target;
    this.requester = requester;
    this.dateOfRequest = new Date();
}

public Friendship() { }

... getter/setter and stuff ommitted


}

the corresponding service

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

import static org.springframework.util.Assert.notNull;

@Service("friendshipService")
@Transactional
@SuppressWarnings("SpringJavaAutowiringInspection")
public class FriendshipServiceImpl implements FriendshipService {

@Autowired
private Neo4jTemplate template;

/**
 * request a friendship with another account
 *
 * @param requester
 * @param target
 */
@Override
public void requestFriendship(Account requester, Account target) {
    notNull(target);
    if(!target.hasFriendshipWith(requester.getId())) {
        target.addFriendshipRequest(new Friendship(requester, target));
        template.save(target);
    }
}

/**
 * change pending state of friendship request to CONFIRMED
 *
 * @param confirmer
 * @param requester
 */
@Override
public void acceptFriendshipRequest(Account confirmer, Account requester) {
    notNull(confirmer);
    for(Friendship f : confirmer.getFriendRequests()) {
        if (f.getRequester().equals(requester)) {
            f.setState(Friendship.State.CONFIRMED);
            f.setDateOfConfirmation(new Date());
        }
    }
    template.save(confirmer);
}

/**
 * declines friendship request by removing it
 *
 * @param confirmer
 * @param requester
 */
@Override
public void declineFriendshipRequest(Account confirmer, Account requester) {
    notNull(confirmer);
    for(Friendship f : confirmer.getFriendRequests()) {
        if (f.getRequester().equals(requester)) {
            confirmer.getFriendships().remove(f);
        }
    }
    template.save(confirmer);
}

}

and my simple unit tests to check if everything goes smooth ...

@ContextConfiguration({"classpath:/test-applicationContext.xml"})
@SuppressWarnings("SpringJavaAutowiringInspection")
@RunWith(SpringJUnit4ClassRunner.class)
public class TestFriendships {

@Autowired
Neo4jTemplate template;

@Autowired
private AccountDetailsService accountDetailsService;

@Autowired
private FriendshipService friendshipService;

private Account myself;
private Account friendlyHans;
private Account unfriendlyPeter;


@Rollback(false)
@BeforeTransaction
public void cleanUpGraph() {
    Neo4jHelper.cleanDb(template);
}

@Before
public void prepareTests() {
    myself = accountDetailsService.createAccount(new Account("itsme"));
    friendlyHans = accountDetailsService.createAccount(new Account("butterflyhans"));
    unfriendlyPeter = accountDetailsService.createAccount(new Account("evilmindedpeter"));
}

@Test
@Transactional
public void testSendFriendshipRequest() throws Exception {
    friendshipService.requestFriendship(myself, friendlyHans);
    assertThat(friendlyHans.getFriendRequests().size(), is(1));
    assertThat(myself.getFriendships().size(), is(1));
    assertThat(friendlyHans.getFriendRequests().iterator().next().getRequester(), is(myself));
    assertThat(friendlyHans.getFriendRequests().iterator().next().getConfirmer(), is(friendlyHans));
    assertThat(friendlyHans.getFriendRequests().iterator().next().getState(), is(Friendship.State.REQUESTED));
}

So, now when I call friendshipService.requestFriendship(myself, friendlyHans), I assume that a relationship between both accounts will be established and as I pointed the direction to BOTH, I furthermore assumed, that, when I check the counterparts friendship list, there will be also a relation to the first account. Unfortunately this (assertThat(myself.getFriendships().size(), is(1));) fails as the set is empty.

Can you point out, what I am doing wrong here? I tried it for the last couple of days - unfortunately without success. Perhaps it's just a small thing. But I cannot find it.

Oh, the version numbers, I'm using are

  • Spring Data Neo4j 2.1.0.RELEASE
  • Spring Core 3.2.0.RELEASE
  • Neo4j 1.8.1

Many thanks in advance.

1

1 Answers

0
votes

You have to read back the myself object from the database in order to have the relationship field updated, otherwise you're just using the object you've read before creating the new relationship.