diff --git a/src/main/java/net/simon987/musicgraph/entities/ArtistDetails.java b/src/main/java/net/simon987/musicgraph/entities/ArtistDetails.java index fcf1d40..43ce1f7 100644 --- a/src/main/java/net/simon987/musicgraph/entities/ArtistDetails.java +++ b/src/main/java/net/simon987/musicgraph/entities/ArtistDetails.java @@ -11,6 +11,6 @@ public class ArtistDetails { } public String name; - public List releases; + public List releases; public List tags; } diff --git a/src/main/java/net/simon987/musicgraph/entities/Release.java b/src/main/java/net/simon987/musicgraph/entities/Release.java new file mode 100644 index 0000000..e5d4d17 --- /dev/null +++ b/src/main/java/net/simon987/musicgraph/entities/Release.java @@ -0,0 +1,14 @@ +package net.simon987.musicgraph.entities; + +public class Release { + + public String mbid; + public String name; + public long year; + + public Release(String mbid, String name, long year) { + this.mbid = mbid; + this.name = name; + this.year = year; + } +} diff --git a/src/main/java/net/simon987/musicgraph/io/MusicDatabase.java b/src/main/java/net/simon987/musicgraph/io/MusicDatabase.java index 4e78064..a760f9a 100644 --- a/src/main/java/net/simon987/musicgraph/io/MusicDatabase.java +++ b/src/main/java/net/simon987/musicgraph/io/MusicDatabase.java @@ -9,7 +9,9 @@ import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Relationship; import javax.inject.Singleton; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -37,11 +39,37 @@ public class MusicDatabase extends AbstractBinder { long end = System.nanoTime(); long took = (end - start) / 1000000; - logger.info(String.format("Query %s (Took %dms)", query, took)); + logger.info(String.format("Query %s (Took %dms)", query.replace('\n', ' '), took)); return result; } + + public SearchResult getArtistMembers(String mbid) { + + try (Session session = driver.session()) { + + Map params = new HashMap<>(); + params.put("mbid", mbid); + + StatementResult result = query(session, + "MATCH (a:Artist)-[r]-(b)\n" + + "WHERE a.id = $mbid AND NOT type(r) = \"IS_RELATED_TO\"\n" + + "RETURN a as artist, a {rels: collect(DISTINCT r), nodes: collect(DISTINCT b)} as rank1\n" + + "LIMIT 1", + params); + + SearchResult out = new SearchResult(); + + parseRelatedResult(result, out); + + return out; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + public ArtistDetails getArtistDetails(String mbid) { Map params = new HashMap<>(); @@ -51,7 +79,7 @@ public class MusicDatabase extends AbstractBinder { StatementResult result = query(session, "MATCH (a:Artist {id: $mbid})-[:CREDITED_FOR]->(r:Release)\n" + - "WITH collect(r.id) as releases, a\n" + + "WITH collect({id:r.id, name:r.name, year:r.year}) as releases, a\n" + "OPTIONAL MATCH (a)-[r:IS_TAGGED]->(t:Tag)\n" + "RETURN a {name:a.name, releases:releases, tags:collect({weight: r.weight, name: t.name})}\n" + "LIMIT 1", @@ -65,7 +93,16 @@ public class MusicDatabase extends AbstractBinder { Map map = result.next().get("a").asMap(); details.name = (String) map.get("name"); - details.releases.addAll((Collection) map.get("releases")); + details.releases.addAll( + ((List) map.get("releases")) + .stream() + .map(x -> new Release( + (String) x.get("id"), + (String) x.get("name"), + (Long) x.get("year") + )).collect(Collectors.toList()) + + ); details.tags.addAll( ((List) map.get("tags")) .stream() @@ -85,7 +122,7 @@ public class MusicDatabase extends AbstractBinder { } } - public SearchResult getRelated(String mbid) { + public SearchResult getRelatedById(String mbid) { try (Session session = driver.session()) { @@ -104,28 +141,7 @@ public class MusicDatabase extends AbstractBinder { SearchResult out = new SearchResult(); - long start = System.nanoTime(); - while (result.hasNext()) { - Record row = result.next(); - out.artists.add(makeArtist(row.get(0).asNode())); - - var rank1 = row.get(1).asMap(); - - out.relations.addAll(((List) rank1.get("rels")) - .stream() - .map(MusicDatabase::makeRelation) - .collect(Collectors.toList() - )); - out.artists.addAll(((List) rank1.get("nodes")) - .stream() - .map(MusicDatabase::makeArtist) - .collect(Collectors.toList() - )); - - } - long end = System.nanoTime(); - long took = (end - start) / 1000000; - logger.info(String.format("Fetched search result (Took %dms)", took)); + parseRelatedResult(result, out); return out; } catch (Exception e) { @@ -134,14 +150,69 @@ public class MusicDatabase extends AbstractBinder { } } + public SearchResult getRelatedByName(String name) { + + try (Session session = driver.session()) { + + Map params = new HashMap<>(); + params.put("name", name); + + StatementResult result = query(session, + "MATCH (a:Artist)-[r:IS_RELATED_TO]-(b)\n" + + "WHERE a.name = $name\n" + + // Only match artists with > 0 releases + "MATCH (b)-[:CREDITED_FOR]->(:Release)\n" + + "WHERE r.weight > 0.25\n" + + "RETURN a as artist, a {rels: collect(DISTINCT r), nodes: collect(DISTINCT b)} as rank1\n" + + "LIMIT 1", + params); + + SearchResult out = new SearchResult(); + + parseRelatedResult(result, out); + + return out; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private void parseRelatedResult(StatementResult result, SearchResult out) { + long start = System.nanoTime(); + while (result.hasNext()) { + Record row = result.next(); + out.artists.add(makeArtist(row.get(0).asNode())); + + var rank1 = row.get(1).asMap(); + + out.relations.addAll(((List) rank1.get("rels")) + .stream() + .map(MusicDatabase::makeRelation) + .collect(Collectors.toList() + )); + out.artists.addAll(((List) rank1.get("nodes")) + .stream() + .map(MusicDatabase::makeArtist) + .collect(Collectors.toList() + )); + + } + long end = System.nanoTime(); + long took = (end - start) / 1000000; + logger.info(String.format("Fetched search result (Took %dms)", took)); + } + private static Artist makeArtist(Node node) { Artist artist = new Artist(); artist.id = node.id(); artist.mbid = node.get("id").asString(); artist.name = node.get("name").asString(); artist.labels = ImmutableList.copyOf(node.labels()); - artist.listeners = node.get("listeners").asInt(); - artist.playCount = node.get("playcount").asInt(); + if (node.containsKey("listeners")) { + artist.listeners = node.get("listeners").asInt(); + artist.playCount = node.get("playcount").asInt(); + } return artist; } @@ -150,7 +221,9 @@ public class MusicDatabase extends AbstractBinder { relation.source = rel.startNodeId(); relation.target = rel.endNodeId(); - relation.weight = rel.get("weight").asFloat(); + if (rel.containsKey("weight")) { + relation.weight = rel.get("weight").asFloat(); + } return relation; } diff --git a/src/main/java/net/simon987/musicgraph/webapi/ArtistController.java b/src/main/java/net/simon987/musicgraph/webapi/ArtistController.java index b086b15..9f1aa8a 100644 --- a/src/main/java/net/simon987/musicgraph/webapi/ArtistController.java +++ b/src/main/java/net/simon987/musicgraph/webapi/ArtistController.java @@ -26,8 +26,26 @@ public class ArtistController { @Produces(MediaType.APPLICATION_JSON) public SearchResult getRelated(@PathParam("mbid") String mbid) { - logger.info(String.format("Searching for %s", mbid)); - return db.getRelated(mbid); + logger.info(String.format("Related for %s", mbid)); + return db.getRelatedById(mbid); + } + + @GET + @Path("members/{mbid}") + @Produces(MediaType.APPLICATION_JSON) + public SearchResult getMembers(@PathParam("mbid") String mbid) { + + logger.info(String.format("Members for %s", mbid)); + return db.getArtistMembers(mbid); + } + + @GET + @Path("related_by_name/{name}") + @Produces(MediaType.APPLICATION_JSON) + public SearchResult getRelatedByName(@PathParam("name") String name) { + + logger.info(String.format("Related for %s", name)); + return db.getRelatedByName(name); } @GET