Java tutorial
/* * 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; } } }