com.graphaware.neo4j.webexpo.service.Neo4jAttendeeService.java Source code

Java tutorial

Introduction

Here is the source code for com.graphaware.neo4j.webexpo.service.Neo4jAttendeeService.java

Source

/*
 * Copyright (c) 2013 GraphAware
 *
 * This file is part of GraphAware.
 *
 * GraphAware is free software: you can redistribute it and/or modify it under the terms of
 * the GNU General Public License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. You should have received a copy of
 * the GNU General Public License along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

package com.graphaware.neo4j.webexpo.service;

import com.graphaware.neo4j.webexpo.util.MapSorter;
import org.neo4j.cypher.javacompat.ExecutionEngine;
import org.neo4j.cypher.javacompat.ExecutionResult;
import org.neo4j.cypherdsl.Order;
import org.neo4j.cypherdsl.query.Query;
import org.neo4j.graphalgo.GraphAlgoFactory;
import org.neo4j.graphdb.*;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.kernel.Traversal;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.*;

import static com.graphaware.neo4j.webexpo.service.WebExpoConstants.*;
import static org.neo4j.cypherdsl.CypherQuery.*;
import static org.neo4j.graphdb.Direction.INCOMING;
import static org.neo4j.graphdb.Direction.OUTGOING;
import static org.neo4j.graphdb.traversal.Evaluators.atDepth;
import static org.neo4j.kernel.Uniqueness.NONE;

public class Neo4jAttendeeService extends Neo4jWebExpoService implements AttendeeService {

    private static final Traversal.PathDescriptor<Path> PATH_DESCRIPTOR = new CustomPathDescriptor();
    private static final int SUGGESTION_LIMIT = 3;

    @Autowired
    public Neo4jAttendeeService(GraphDatabaseService neo) {
        super(neo);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<String> getAllTopics() {
        Set<String> allTopics = new HashSet<String>();

        for (Relationship talksAt : neo.getReferenceNode().getRelationships(INCOMING, TALKS_AT)) {
            Node speaker = talksAt.getStartNode();
            for (Relationship delivers : speaker.getRelationships(OUTGOING, DELIVERS)) {
                Node talk = delivers.getEndNode();
                for (Relationship about : talk.getRelationships(OUTGOING, ABOUT)) {
                    allTopics.add((String) about.getEndNode().getProperty(NAME));
                }
            }
        }

        return allTopics;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void registerAttendee(String attendeeName, String... topics) {
        if (getNode(NAME, attendeeName, PEOPLE_INDEX) != null) {
            throw new IllegalArgumentException(attendeeName + " already exists");
        }
        Node attendee = createNode(NAME, attendeeName, PEOPLE_INDEX);
        for (String topicName : topics) {
            Node topic = getExistingTopic(topicName);
            attendee.createRelationshipTo(topic, INTERESTED);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<String> suggestInterestingTalks(String attendeeName) {
        TraversalDescription interestingTalksTraversalDescription = Traversal.description().uniqueness(NONE)
                .breadthFirst().relationships(INTERESTED, OUTGOING).relationships(ABOUT, INCOMING)
                .evaluator(atDepth(2));

        Node attendee = getExistingPerson(attendeeName);
        Iterable<Node> interestingTalks = interestingTalksTraversalDescription.traverse(attendee).nodes();
        return occurrencesAsStrings(interestingTalks, TITLE);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void evaluateTalk(String attendeeName, String justAttendedTalkTitle, TalkEvaluation evaluation) {
        RelationshipType evaluationRelationshipType;
        switch (evaluation) {
        case THUMBS_UP:
            evaluationRelationshipType = LIKED;
            break;
        case THUMBS_DOWN:
            evaluationRelationshipType = DISLIKED;
            break;
        default:
            throw new IllegalArgumentException(evaluation + " is not a valid evaluation");
        }

        Node attendee = getExistingPerson(attendeeName);
        clearNextTalks(attendee);
        Node talk = getExistingTalk(justAttendedTalkTitle);
        attendee.createRelationshipTo(talk, evaluationRelationshipType);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void reportNextTalk(String attendeeName, String nextTalkTitle) {
        Node attendee = getExistingPerson(attendeeName);
        clearNextTalks(attendee);

        Node talk = getExistingTalk(nextTalkTitle);
        attendee.createRelationshipTo(talk, ATTENDS_NEXT);
    }

    /**
     * Clear intended next talks for an attendee.
     *
     * @param attendeeName to clear next talks for.
     */
    private void clearNextTalks(Node attendeeName) {
        for (Relationship attendsNext : attendeeName.getRelationships(OUTGOING, ATTENDS_NEXT)) {
            attendsNext.delete();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<String> findWherePeopleAreGoing(String currentTalkTitle) {
        TraversalDescription nextTalksTraversalDescription = Traversal.description().breadthFirst().uniqueness(NONE)
                .relationships(LIKED, INCOMING).relationships(DISLIKED, INCOMING)
                .relationships(ATTENDS_NEXT, OUTGOING).evaluator(atDepth(2));

        Node currentTalk = getExistingTalk(currentTalkTitle);
        Iterable<Node> nextTalks = nextTalksTraversalDescription.traverse(currentTalk).nodes();

        return occurrencesAsStrings(nextTalks, TITLE);
    }

    /**
     * Returns nodes sorted by the number of occurrences descending.
     *
     * @param nodes       to find occurrences in.
     * @param propertyKey property of the nodes to be used in the result.
     * @return list of strings in the form of node_property: number_of_occurrences.
     */
    private List<String> occurrencesAsStrings(Iterable<Node> nodes, String propertyKey) {
        Map<Node, Integer> occurrences = new HashMap<Node, Integer>();
        for (Node node : nodes) {
            if (!occurrences.containsKey(node)) {
                occurrences.put(node, 0);
            }
            occurrences.put(node, occurrences.get(node) + 1);
        }

        List<String> result = new LinkedList<String>();
        SortedMap<Node, Integer> sortedOccurrences = MapSorter.sortMapByDescendingValue(occurrences);
        for (Map.Entry<Node, Integer> entry : sortedOccurrences.entrySet()) {
            result.add(entry.getKey().getProperty(propertyKey) + ": " + entry.getValue());
            if (result.size() >= SUGGESTION_LIMIT) {
                return result;
            }
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<String> suggestNextTalks(String attendeeName) {
        getExistingPerson(attendeeName); //just to throw exception if it isn't there

        ExecutionEngine engine = new ExecutionEngine(neo);
        //scores 1 point for each agreement / disagreement with a person + whey they go next
        ExecutionResult result = engine.execute("start a=node:people(name = \"" + attendeeName
                + "\") match (a)-[r1:LIKED|DISLIKED]->()<-[r2:LIKED|DISLIKED]-()-[r3:ATTENDS_NEXT]->(t) where type(r1)=type(r2) return distinct t.title, count(t) order by count(t) desc limit "
                + SUGGESTION_LIMIT);

        List<String> recommendedNextTalks = new LinkedList<String>();
        for (Map<String, Object> row : result) {
            recommendedNextTalks.add(row.get("t.title") + ": " + row.get("count(t)").toString());
        }
        return recommendedNextTalks;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<String> findThingsInCommon(String attendeeOneName, String attendeeTwoName) {
        Node attendeeOne = getExistingPerson(attendeeOneName);
        Node attendeeTwo = getExistingPerson(attendeeTwoName);

        //retrieve attendeeOne and attendeeTwo from index

        int maxDepth = 2;
        Iterable<Path> paths = GraphAlgoFactory.allPaths(Traversal.expanderForAllTypes(), maxDepth)
                .findAllPaths(attendeeOne, attendeeTwo);

        for (Path path : paths) {
            //print it
        }

        List<String> thingsInCommon = new LinkedList<String>();
        for (Path path : paths) {
            thingsInCommon.add(Traversal.pathToString(path, PATH_DESCRIPTOR));

            //            Node commonInterestNode = path.lastRelationship().getEndNode();
            //            if (commonInterestNode.hasProperty(NAME)) {
            //                thingsInCommon.add(commonInterestNode.getProperty(NAME).toString());
            //            } else if (commonInterestNode.hasProperty(TITLE)) {
            //                thingsInCommon.add(commonInterestNode.getProperty(TITLE).toString());
            //            } else {
            //                throw new IllegalStateException("Illegal thing in common: " + commonInterestNode);
            //            }
        }
        return thingsInCommon;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<String> suggestBeerMates(String attendeeName) {
        Node attendee = getExistingPerson(attendeeName);
        Query query = start(nodesById("attendee", attendee.getId()))
                .match(node("attendee").out(LIKED.name(), DISLIKED.name()).as("r1").node()
                        .in(LIKED.name(), DISLIKED.name()).as("r2").node("beerMate"))
                .where(type(identifier("r1")).eq(type(identifier("r2"))))
                .returns(distinct(identifier("beerMate").property(NAME)), count(identifier("beerMate")))
                .orderBy(order(count(identifier("beerMate")), Order.DESCENDING)).limit(SUGGESTION_LIMIT).toQuery();

        //scores 1 point for each agreement / disagreement with a person
        ExecutionResult result = new ExecutionEngine(neo).execute(query.toString());

        List<String> beerMates = new LinkedList<String>();
        for (Map<String, Object> row : result) {
            beerMates.add(row.get("beerMate.name") + ": " + row.get("count(beerMate)").toString());
        }
        return beerMates;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<String> suggestTwitterFollowings(String attendee) {
        return null; //To change body of implemented methods use File | Settings | File Templates.
        //todo only cypher in console
    }

    private static class CustomPathDescriptor extends Traversal.DefaultPathDescriptor<Path> {

        /**
         * {@inheritDoc}
         */
        @Override
        public String nodeRepresentation(Path path, Node node) {
            if (node.hasProperty(NAME)) {
                return "(" + node.getProperty(NAME).toString() + ")";
            } else if (node.hasProperty(TITLE)) {
                return "(" + node.getProperty(TITLE).toString() + ")";
            } else {
                return super.nodeRepresentation(path, node);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String relationshipRepresentation(Path path, Node from, Relationship relationship) {
            String prefix = "--", suffix = "--";
            if (from.equals(relationship.getEndNode())) {
                prefix = "<--";
            } else {
                suffix = "-->";
            }
            return prefix + "[" + relationship.getType().name() + "]" + suffix;
        }
    }

}