Announcement Announcement Module
Collapse
No announcement yet.
Spring Social Neo4j Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring Social Neo4j

    Hi people,
    I recently started to write a neo4j connector for spring social. This is the repo:

    https://github.com/maciossek/spring-social-neo4j

    Feel free to help me out here, since I do not have a lot of experience in this domain (java/spring/neo4j). I am working on a project, where we actually use a Neo4j database and we would like to have the spring social project working for us, too.

    Cheers,
    Mathias

  • #2
    The main thing you'll want to focus on for Neo4j is writing the implementations of ConnectionRepository and UsersConnectionRepository. The difference between these two interfaces can be a bit confusing at first, so let me clear it up by saying that UsersConnectionRepository has responsibilities that span all users in an application while ConnectionRepository's responsibilities are for managing connections for a single user (indeed, one request-scoped instance of a ConnectionRepository will be created for each user). UsersConnectionRepository's main job is to create a ConnectionRepository for a given user as well as lookup user IDs given a connection. ConnectionRepository's job is to add, remove, and otherwise maintain connections for a given user.

    As an example, consider the JDBC implementations at https://github.com/SpringSource/spri...l/connect/jdbc.

    I think that the trick in implementing this will be in not forcing a specific graph or node structure for how users are kept in Neo4j. I'd think that the best approach here is probably to have the "userId" parameter passed into UsersConnectionRepository.createConnectionReposito ry()...and ultimately to the ConnectionRepository implementation's constructor...contain the node ID of the user to create the connection for. The only flaw in that thinking is that (as I understand it) any once-used-but-no-longer-used node IDs may be reused and therefore be non-unique over time.

    Even so, that may not be a problem. (Thinking out loud) Your connection repository could create connections as nodes related to whatever node is identified by the given node ID. That node won't be removed until the relationship to the connection is removed, so the node ID will be unique over any period of time for which the connection exists.

    The other trick would be what the Connection node would look like. If you're planning on using Spring Data Neo4j's annotations, you should avoid annotating the existing Connection interface or its implementations, as they will also be used in non-Neo4j apps. Instead, you might subclass/wrap/copy the existing Connection data into a Neo4j-specific type for purposes of persistence. You could also go native Neo4j instead of using Spring Data Neo4j and create the nodes manually--this also has the benefit of not requiring the additional dependency on Spring Data (not that I don't like Spring Data--I really do like it--but I just would avoid the dependency on it if I could).

    Hopefully this message proves more useful than confusing. If you have any questions, don't hesitate to ask.

    Comment


    • #3
      That is great feedback, thank you Craig.

      I think the issue is, that I do not want to create Neo4j nodes without any connection to my existing user nodes within my system. So there must be a reference node, when creating a ConnectionRepository.

      Regarding spring-data: I see your point there, especially when it comes to a general module. Maybe this can be done in an iteration. I will try with spring data first and see how far I come.

      The JDBC uses the JdbcTemplate and a set datasource attribute. For Neo4j i do have the Neo4jTemplate, but I think I need to define the graphDatabase bean somewhere in my application, to access it. Right?

      HTML Code:
      <bean id="graphDbService" class="org.neo4j.kernel.EmbeddedGraphDatabase"
           init-method="enableRemoteShell" destroy-method="shutdown">
      
           <constructor-arg index="0" value="data/neo4j-db"/>
               <constructor-arg index="1" ref="configuration"  />
      </bean>

      The Domain Model Should then be sth. like this: Attachment

      Therefore I would have the following classes:
      Neo4jConnectionRepository extends GraphRepository<Neo4jSocialUser>
      Neo4jUsersConnectionRepository
      Neo4jSocialUser

      while the SocialUser would be the domain class for the Neo4j node like it is displayed on the screenshot.
      Attached Files

      Comment


      • #4
        Originally posted by maciossek View Post
        I think the issue is, that I do not want to create Neo4j nodes without any connection to my existing user nodes within my system. So there must be a reference node, when creating a ConnectionRepository.
        We're in agreement here and yes, when creating the Neo4jConnectionRepository, it must be given *something* to reference the user node for whom the connections are being created. Per the UsersConnectionRepository interface, the createConnectionRepository() method takes a String which is the userId. For Neo4j, that can be a String containing the node ID fo the user node for which the connections will managed. It's a bit odd, as the node ID would really be a numeric value. But the signature of that method is String, so I guess a conversion to/from String will be required to satisfy the method signature.

        The JDBC uses the JdbcTemplate and a set datasource attribute. For Neo4j i do have the Neo4jTemplate, but I think I need to define the graphDatabase bean somewhere in my application, to access it. Right?
        In the case of JDBC, JdbcUsersConnectionRepository is given a DataSource, from which is creates a JdbcTemplate. Likewise, Neo4jUsersConnectionRepository should be given the most basic thing it can be given to access the database. If you're going to use Neo4jTemplate internally, that's fine (except for the dependency issue I mentioned before), but Neo4jUsersConnectionRepository should be created with something lower-level, such as the graph database bean...pretty much what you said.

        Therefore I would have the following classes:
        Neo4jConnectionRepository extends GraphRepository<Neo4jSocialUser>
        Neo4jUsersConnectionRepository
        Neo4jSocialUser
        I'm not so sure I'd have Neo4jConnectionRepository extend GraphRepository. I'd just have it implement ConnectionRepository and internally use either Neo4jTemplate to do its work or (better, for dependency reasons) work with the database natively.

        In short, even if you're using Spring Data Neo4j internally, I'd keep it out of the external contract of those clasess so that you can rip it out later without changing the contract. Ideally, SDN wouldn't even be a dependency...but I understand your desire for an iterative approach using SDN to start. Just try to avoid getting too tied to SDN in the external contract so that it won't be hard to remove it in a later iteration.

        Comment


        • #5
          Is there a possibility to load the spring social configuration class after the XML definitions? My problem is, that this causes an error:


          Code:
          @Bean
          public UsersConnectionRepository usersConnectionRepository() {
              return new Neo4jUsersConnectionRepository(neo4jTemplate, connectionFactoryLocator(), textEncryptor);
          }
          No matching bean of type [org.springframework.data.neo4j.support.Neo4jTempla te] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Aut owired(required=true)}

          Comment


          • #6
            For Neo4j, that can be a String containing the node ID fo the user node for which the connections will managed.
            Peter Neubauer told me that I should never rely on the Node's ID. He said, that the ID might change after a DB crash, or when nodes are getting deleted. Neo4j does not need the connection between nodes, while they are getting created, but it makes the most sense. So using the module most likely expects nodes which map to a given userID. I think UUIDs could work fine.

            I still have troubles getting the application up and running. It might still be the issue with the bean configurations. I am going to drop neo4jTemplate and try using graphDBService instead, maybe that works better.

            Comment


            • #7
              You're right about node IDs...I had thought of that and that's why in my original post I said that it might not be best, but it might work, because you're not going to delete the user node without also eliminating all relationships tied to it (including the relationship to a FB/Twitter/etc connection node. In other words, the node ID will be constant as long as the connection exists. That said, I didn't consider that the node IDs might change when the DB crashes...in fact, I wasn't even aware of this scenario--I was only aware of node IDs changing and then being reused when a node is deleted.

              In that case, you should consider expecting the user node to have a certain property that can be used to uniquely identify the user. The Spring Social Neo4j code could default to a reasonable property name (e.g., "userId"), but also allow for that to be overridden with a different property name.

              Comment


              • #8
                My setup finally works and I thought about the neo4j integration. I do not know allow overwriting one of the properties, so far I still use the String userId to identify the user himself. Therefore I the query will start on the user node and search for relationship types called HAS_SOCIAL_CONNECTION. This should work for findAllConnections and the other methods.

                I am currently testing it with my web application, but there is still a lot of stuff from the spring security side missing. I have to work on that too, to test the neo4j module. My use case is, that a user gets authenticated by facebook. That means, that my signup page would only consist of a button saying: Login with facebook. The system must then interact with spring-social-neo4j.

                Comment


                • #9
                  I'm not sure what other stuff you need from the Spring Security side, but...

                  Spring Social should not depend on Spring Security at all, except for the crypto module for encrypting tokens when stored in the DB. Most of the samples use Spring Security, but Spring Social itself shouldn't depend directly on Spring Security. If you see something to the contrary, let me know.

                  Comment


                  • #10
                    Well, I need to authenticate the user in my AuthController class, and set the remember me token. But this is only needed for my webapp. The spring social does not depend on it. I only need to set up my application properly to test the spring social stuff. This will slow down the development of spring social neo4j a bit.

                    Comment


                    • #11
                      The authentication works and I am back on the track for SSN. Was not as hard as i expected.

                      I basically do the same stuff, as you did with the JDBC conncetor:
                      Code:
                      ExecutionResult results = engine.execute("start user=node:User(uuid={" + userId + "}) match user-[:HAS_SOCIAL_CONNECTION]->connection return connection");
                      I need to map the results to List<Connection<?>> resultList. But I dunno what exactly I have to do, because you use a RowMapper helper class.

                      How does the List look like?

                      Code:
                      results.columns() // returns List<String>
                      I think it should not be hard to convert that list into List<Connection<?>>, is it?

                      Comment


                      • #12
                        Are you implementing the findAllConnections() method here? That's what it looks like based on the Cypher query you're using.

                        I'll admit that I'm less familiar with the Java-native Neo4j API, but it appears that ExecutionResult.columns() returns a list of String where each value is the *name* of the so-called column (column seems like a poor choice of words here, but that's what they call it) in the result set. That's probably not what you want. Instead, you probably want to call iterator() to get a Map<String,Object> of result objects for each match in the Cypher query. From that Map<String,Object> you pull values and use them to create a Connection object that you add to a List<Connection<?>>.

                        Comment


                        • #13
                          Originally posted by habuma View Post
                          Are you implementing the findAllConnections() method here? That's what it looks like based on the Cypher query you're using.

                          I'll admit that I'm less familiar with the Java-native Neo4j API, but it appears that ExecutionResult.columns() returns a list of String where each value is the *name* of the so-called column (column seems like a poor choice of words here, but that's what they call it) in the result set. That's probably not what you want. Instead, you probably want to call iterator() to get a Map<String,Object> of result objects for each match in the Cypher query. From that Map<String,Object> you pull values and use them to create a Connection object that you add to a List<Connection<?>>.
                          Yep, it is the findAllConnections() method. Thanks for the guidance, I will try it out

                          Comment


                          • #14
                            The findAllConnections method is working, but I think by resultList's structure differs from the JDBC query list's structure. I do not really know what to do about that. My resultList is a type of LinkedList. I do not really know how to create something like a JDBCqueryList so that I do not get the following error message:

                            javax.servlet.ServletException: javax.servlet.jsp.JspTagException: No message found under code 'facebook.displayName' for locale 'de_DE'.

                            I think my result list should be something like a associative array, but I did not find a solution in the web yet. Maybe it is just a simple type of some list, that I do not know yet...?

                            Code:
                            	private Connection<?> mapEntry(ConnectionData connectionData) {
                            		ConnectionFactory<?> connectionFactory = connectionFactoryLocator.getConnectionFactory(connectionData.getProviderId());
                            		return connectionFactory.createConnection(connectionData);
                            	}
                            	
                            	private ConnectionData mapConnectionData(Node node) {
                            		return new ConnectionData((String) node.getProperty("providerId"),
                            				(String) node.getProperty("providerUserId"),
                            				(String) node.getProperty("displayName"), 
                            				(String) node.getProperty("profileUrl"),
                            				(String) node.getProperty("imageUrl"),
                            				decrypt((String) node.getProperty("accessToken")),
                            				decrypt((String) node.getProperty("secret")),
                            				decrypt((String) node.getProperty("refreshToken")),
                            				expireTime((Long) node.getProperty("expireTime")));
                            	}
                            	
                            	private List<Connection<?>> createResultList(ExecutionResult er) {
                            		List<Connection<?>> resultList = new LinkedList<Connection<?>>();
                            		Iterator<Node> userConnections = er.columnAs("connection");
                            		while (userConnections.hasNext()) {
                            			Node userConnectionNode = userConnections.next();
                            			resultList.add(mapEntry(mapConnectionData(userConnectionNode)));
                            		}
                            		return resultList;
                            	}

                            Comment


                            • #15
                              Hi

                              I saw this thread and thought that a test harness I wrote for UsersConnectionRepository implementations may be useful to you in testing your implementation.

                              I reworked the existing JdbcUsersConnectionRepositoryTest from spring-social-core into an abstract test class which can be subclassed for specific implementations of UsersConnectionRepository. The abstract test is available here:

                              https://github.com/michaellavelle/sp...itoryTest.java

                              A subclass which tests the JdbcUsersConnectionRepository (essentially a refactor of the original JdbcUsersConnectionRepositoryTest which uses this superclass ) is available here:

                              https://github.com/michaellavelle/sp...itoryTest.java

                              and a version for my RooUsersConnectionRepository implementation:

                              https://github.com/michaellavelle/sp...itoryTest.java

                              I thought that if you had the time to create your own subclass for Neo4JUsersConnectionRepository this may help you in testing your project.

                              Hope this helps,

                              Michael
                              Michael

                              Comment

                              Working...
                              X