Home > Enterprise >  How can I calculate Shortest Path through Neo4j in my Spring Boot through localhost and Docker?
How can I calculate Shortest Path through Neo4j in my Spring Boot through localhost and Docker?

Time:06-11

I have a problem about calculating the shortest path through Neo4j in my Spring Boot example.

After adding some cities with its route, I want to calculate their shortest path in terms of their connection and their duration. However, these two methods which defined in ShortestPathController cannot work.

1 ) getShortestPath throws this kind of error message

Neo.ClientNotification.Statement.UnboundedVariableLengthPatternWarning: The provided pattern is unbounded, consider adding an upper limit to the number of node hops.
    MATCH p=shortestPath((a:City {name:$from})-[*]->(b:City {name:$to})) RETURN p

2 ) getShortestPathInTime throws this kind of error message shown below.

org.neo4j.driver.exceptions.ClientException: There is no procedure with the name `apoc.algo.dijkstra` registered for this database instance. Please ensure you've spelled the procedure name correctly and that the procedure is properly deployed.

How can I fix it?

Here is my GitHub repository : Project Link

Here is my Controller class

public class ShortestPathController {

    private final ShortestPathService shortestPathService;

    public ShortestPathController(ShortestPathService shortestPathService) {
        this.shortestPathService = shortestPathService;
    }

    /* Sample Output (Not working)
    {
      "arrivalCity": "A",
      "departureCity": "B",
      "totalConnections": 1
    }
     */
    @GetMapping("/shortest-path")
    public Mono<PathShortestConnectionResponse> getShortestPath(@RequestBody PathRequest pathRequest) {

        return shortestPathService.getShortestPath(pathRequest.getFrom(), pathRequest.getDestination())
                .map(PathShortestConnectionResponse::new)
                .switchIfEmpty(Mono.error(new IllegalArgumentException("Error")));
    }

    /* Sample Output (Not working)
    {
      "arrivalCity": "A",
      "departureCity": "B",
      "totalHours": 5
    }
     */
    @GetMapping("/shortest-path-in-time")
    public Mono<PathShortestTimeResponse>  getShortestPathInTime(@RequestBody PathRequest pathRequest) {

        return shortestPathService.getShortestPathInTime(pathRequest.getFrom(), pathRequest.getDestination())
                .map(PathShortestTimeResponse::new)
                .switchIfEmpty(Mono.error(new IllegalArgumentException("Error")));
    }

    @ResponseStatus(
            value = HttpStatus.NOT_FOUND,
            reason = "Illegal arguments")
    @ExceptionHandler(IllegalArgumentException.class)
    public void illegalArgumentHandler() {

    }
}

Here is my Service class

public class ShortestPathServiceImpl implements ShortestPathService {

    private final ShortestPathRepository shortestPathRepository;

    @Override
    public Mono<PathShortestConnectionResponse> getShortestPath(String from, String to) {

        final Flux<PathValue> rows = shortestPathRepository.shortestPath(from, to);
        return rows
                .map(it -> this.convert(it.asPath()))
                .take(1)
                .next()
                .switchIfEmpty(Mono.empty());

    }

    @Override
    public Mono<PathShortestTimeResponse> getShortestPathInTime(String from, String to) {

        final Flux<PathValue> rows = shortestPathRepository.shortestPathInTime(from, to);
        return rows
                .map(it -> this.convertTimePath(it.asPath()))
                .take(1)
                .next()
                .switchIfEmpty(Mono.empty());
    }


    private PathShortestConnectionResponse convert(org.neo4j.driver.types.Path connection) {

        String departureCity = connection.start().get("name").asString();
        String arriveCity = connection.end().get("name").asString();
        int length = connection.length();

        return new PathShortestConnectionResponse(departureCity, arriveCity, length);
    }

    private PathShortestTimeResponse convertTimePath(org.neo4j.driver.types.Path connection) {

        String departureCity = connection.start().get("name").asString();
        String arriveCity = connection.end().get("name").asString();
        Stream<Relationship> targetStream = StreamSupport.stream(connection.relationships().spliterator(), false);
        int totalInTime = targetStream.mapToInt(it -> it.get("duration").asInt()).sum();

        return new PathShortestTimeResponse(departureCity, arriveCity, totalInTime);
    }
}

Here is my Repository class

public interface ShortestPathRepository extends ReactiveNeo4jRepository<City, UUID> {

    @Query("MATCH p=shortestPath((a:City {name:$from})-[*]->(b:City {name:$to})) RETURN p")
    Flux<PathValue> shortestPath(@Param("from") String from, @Param("to") String to);

    @Query("MATCH (a:City {name: $from})\n"
              "MATCH (b:City {name: $to})\n"
              "CALL apoc.algo.dijkstra(a, b, 'ROUTES', 'duration')\n"
              "YIELD path, weight\n"
              "RETURN path\n"
              "ORDER BY weight ASC LIMIT 1")
    Flux<PathValue> shortestPathInTime(@Param("from") String from, @Param("to") String to);
}

Here is my docker-compose.yml shown below.

version: '3'

services:
  neo4j-db:
    image: neo4j:4.1
    container_name: app-neo4j-db
    ports:
      - 7474:7474
      - 7687:7687
    volumes:
      - $HOME/neo4j/data:/data
      - $HOME/neo4j/logs:/logs
      - $HOME/neo4j/import:/import
      - $HOME/neo4j/plugins:/plugins
    environment:
      NEO4J_AUTH: neo4j/123456
      NEO4J_dbms_security_procedures_unrestricted: apoc.\\\*,gds.\\\*
      dbms_connector_bolt_listen__address: neo4j-db:7687
      dbms_connector_bolt_advertised__address: neo4j-db:7687
    healthcheck:
      test: cypher-shell --username neo4j --password 123456 'MATCH (n) RETURN COUNT(n);' # Checks if neo4j server is up and running
      interval: 10s
      timeout: 10s
      retries: 5

  app:
    image: 'springbootneo4jshortestpath:latest'
    build:
      context: .
      dockerfile: Dockerfile
    container_name: SpringBootNeo4jShortestPath
    depends_on:
      neo4j-db:
        condition: service_healthy # Wait for neo4j to be ready
    links:
      - neo4j-db
      environment:
        NEO4J_URI: bolt://neo4j-db:7687
        NEO4J_PASSWORD: 123456

volumes:
  app-neo4j-db:

CodePudding user response:

You need to ensure that APOC library is installed in your Neo4j server. Please follow this installation procedure.

 https://neo4j.com/labs/apoc/4.0/installation/#neo4j-server

Then update your conf file to whitelist that function.

CodePudding user response:

Part 1: This is just a warning. In most cases you would not be happy with unbound relationships because the paths can quickly grow in numbers and the server has to keep all possible combination in memory before you will get it.

Part 2: What @jose_bacoy says. Behind the link are informations about how to add APOC in Desktop, Server or Docker deployment. Test your query (or even a reduced version of it) in the shell/browser to ensure it is activated correctly.

Part 2 (again but different topic): You cannot return PathValue in a repository. If you want to use the Java driver's type, please use SDN's Neo4jClient for interaction. SDN documentation / Neo4jClient

  • Related