mirror of
https://github.com/simon987/music-graph-api.git
synced 2025-04-16 00:26:41 +00:00
refactoring, labels support, autocomplete for tags
This commit is contained in:
parent
fa1e1a5515
commit
99c2391aab
@ -29,6 +29,7 @@ public class Main {
|
||||
|
||||
rc.registerClasses(Index.class);
|
||||
rc.registerClasses(ArtistController.class);
|
||||
rc.registerClasses(AutocompleteController.class);
|
||||
rc.registerClasses(CoverController.class);
|
||||
rc.registerClasses(TagController.class);
|
||||
rc.registerClasses(ReleaseController.class);
|
||||
|
@ -5,14 +5,16 @@ import java.util.List;
|
||||
|
||||
public class ArtistDetails {
|
||||
|
||||
public ArtistDetails() {
|
||||
releases = new ArrayList<>();
|
||||
tags = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String name;
|
||||
public String comment;
|
||||
public long year;
|
||||
public List<Release> releases;
|
||||
public List<Tag> tags;
|
||||
public List<WeightedTag> tags;
|
||||
public List<Label> labels;
|
||||
|
||||
public ArtistDetails() {
|
||||
releases = new ArrayList<>();
|
||||
tags = new ArrayList<>();
|
||||
labels = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
14
src/main/java/net/simon987/musicgraph/entities/Label.java
Normal file
14
src/main/java/net/simon987/musicgraph/entities/Label.java
Normal file
@ -0,0 +1,14 @@
|
||||
package net.simon987.musicgraph.entities;
|
||||
|
||||
public class Label {
|
||||
|
||||
public long id;
|
||||
public String mbid;
|
||||
public String name;
|
||||
|
||||
public Label(long id, String mbid, String name) {
|
||||
this.id = id;
|
||||
this.mbid = mbid;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package net.simon987.musicgraph.entities;
|
||||
|
||||
|
||||
public class LabelSearchResult extends SearchResult {
|
||||
|
||||
public Label label;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package net.simon987.musicgraph.entities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class RelatedLabels {
|
||||
public Set<Label> labels;
|
||||
public List<Relation> relations;
|
||||
|
||||
public RelatedLabels() {
|
||||
labels = new HashSet<>();
|
||||
relations = new ArrayList<>();
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package net.simon987.musicgraph.entities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class RelatedTags {
|
||||
public Set<Tag> tags;
|
||||
public List<Relation> relations;
|
||||
|
||||
public RelatedTags() {
|
||||
tags = new HashSet<>();
|
||||
relations = new ArrayList<>();
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ public class ReleaseDetails {
|
||||
public String mbid;
|
||||
public String artist;
|
||||
public long year;
|
||||
public List<Tag> tags;
|
||||
public List<WeightedTag> tags;
|
||||
|
||||
public ReleaseDetails() {
|
||||
this.tags = new ArrayList<>();
|
||||
|
@ -1,14 +1,12 @@
|
||||
package net.simon987.musicgraph.entities;
|
||||
|
||||
public class Tag {
|
||||
|
||||
public String name;
|
||||
public double weight;
|
||||
public long id;
|
||||
|
||||
|
||||
public Tag(long id, String name, double weight) {
|
||||
public Tag(long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.weight = weight;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
package net.simon987.musicgraph.entities;
|
||||
|
||||
public class WeightedTag extends Tag {
|
||||
|
||||
public double weight;
|
||||
|
||||
public WeightedTag(long id, String name, double weight) {
|
||||
super(id, name);
|
||||
this.weight = weight;
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ package net.simon987.musicgraph.io;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.simon987.musicgraph.entities.*;
|
||||
import net.simon987.musicgraph.logging.LogManager;
|
||||
import net.simon987.musicgraph.webapi.ArtistOverview;
|
||||
import net.simon987.musicgraph.webapi.AutocompleteLine;
|
||||
import net.simon987.musicgraph.webapi.AutoCompleteData;
|
||||
import org.glassfish.jersey.internal.inject.AbstractBinder;
|
||||
import org.neo4j.driver.v1.*;
|
||||
@ -83,7 +83,9 @@ public class MusicDatabase extends AbstractBinder {
|
||||
"MATCH (a:Artist {id: $mbid})-[:CREDITED_FOR]->(r:Release)\n" +
|
||||
"WITH collect({id: ID(r), mbid:r.id, name:r.name, year:r.year, labels:labels(r)}) as releases, a\n" +
|
||||
"OPTIONAL MATCH (a)-[r:IS_TAGGED]->(t:Tag)\n" +
|
||||
"RETURN a {name:a.name, year:a.year, comment:a.comment, releases:releases, tags:collect({weight: r.weight, name: t.name, id:ID(t)})}\n" +
|
||||
"WITH collect({weight: r.weight, name: t.name, id:ID(t)}) as tags, a, releases\n" +
|
||||
"OPTIONAL MATCH (a)-[r:CREDITED_FOR]->(:Release)-[]-(l:Label)\n" +
|
||||
"RETURN a {name:a.name, year:a.year, comment:a.comment, releases:releases, tags:tags, labels:collect(DISTINCT {id:ID(l),mbid:l.id,name:l.name})} \n" +
|
||||
"LIMIT 1",
|
||||
params);
|
||||
|
||||
@ -113,13 +115,24 @@ public class MusicDatabase extends AbstractBinder {
|
||||
((List<Map>) map.get("tags"))
|
||||
.stream()
|
||||
.filter(x -> x.get("name") != null)
|
||||
.map(x -> new Tag(
|
||||
.map(x -> new WeightedTag(
|
||||
(Long) x.get("id"),
|
||||
(String) x.get("name"),
|
||||
(Double) x.get("weight")
|
||||
))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
details.labels.addAll(
|
||||
((List<Map>) map.get("labels"))
|
||||
.stream()
|
||||
.filter(x -> x.get("id") != null)
|
||||
.map(x -> new Label(
|
||||
(Long) x.get("id"),
|
||||
(String) x.get("mbid"),
|
||||
(String) x.get("name")
|
||||
))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@ -141,7 +154,7 @@ public class MusicDatabase extends AbstractBinder {
|
||||
"WHERE a.id = $mbid\n" +
|
||||
// Only match artists with > 0 releases
|
||||
"MATCH (b)-[:CREDITED_FOR]->(:Release)\n" +
|
||||
"WHERE r.weight > 0.25\n" +
|
||||
"WHERE r.weight > 0.15\n" +
|
||||
"RETURN a as artist, {rels: collect(DISTINCT r), nodes: collect(DISTINCT b)} as rank1\n" +
|
||||
"LIMIT 1",
|
||||
params);
|
||||
@ -167,6 +180,7 @@ public class MusicDatabase extends AbstractBinder {
|
||||
StatementResult result = query(session,
|
||||
"MATCH (t:Tag)-[r:IS_TAGGED]-(a:Artist)\n" +
|
||||
"WHERE ID(t) = $tag_id\n" +
|
||||
// Is rels really necessary?
|
||||
"RETURN t, {rels: collect(DISTINCT r), nodes: collect(DISTINCT a)} as rank1\n" +
|
||||
"LIMIT 1",
|
||||
params);
|
||||
@ -186,6 +200,92 @@ public class MusicDatabase extends AbstractBinder {
|
||||
}
|
||||
}
|
||||
|
||||
public LabelSearchResult getRelatedByLabel(long id) {
|
||||
|
||||
try (Session session = driver.session()) {
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("label_id", id);
|
||||
|
||||
StatementResult result = query(session,
|
||||
"MATCH (l:Label)-[]-(:Release)-[:CREDITED_FOR]-(a:Artist) " +
|
||||
"WHERE ID(l) = $label_id " +
|
||||
"RETURN l, {nodes: collect(DISTINCT a)} as rank1 " +
|
||||
"LIMIT 1",
|
||||
params);
|
||||
|
||||
LabelSearchResult out = new LabelSearchResult();
|
||||
|
||||
if (result.hasNext()) {
|
||||
Record row = result.next();
|
||||
out.label = makeLabel(row.get(0).asNode());
|
||||
artistsFromRelMap(out, row.get(1).asMap());
|
||||
}
|
||||
|
||||
return out;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public RelatedLabels getRelatedLabelByLabel(long id) {
|
||||
|
||||
try (Session session = driver.session()) {
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("label_id", id);
|
||||
|
||||
StatementResult result = query(session,
|
||||
"MATCH (l:Label)-[r:IS_RELATED_TO]-(l2:Label) " +
|
||||
"WHERE ID(t) = $tag_id " +
|
||||
"RETURN {rels: collect(DISTINCT r), nodes: collect(DISTINCT t2)} as rank1 " +
|
||||
"LIMIT 1",
|
||||
params);
|
||||
|
||||
RelatedLabels out = new RelatedLabels();
|
||||
|
||||
if (result.hasNext()) {
|
||||
Record row = result.next();
|
||||
labelsFromRelMap(out, row.get(0).asMap());
|
||||
}
|
||||
|
||||
return out;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public RelatedTags getRelatedTagByTag(long id) {
|
||||
|
||||
try (Session session = driver.session()) {
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("tag_id", id);
|
||||
|
||||
StatementResult result = query(session,
|
||||
"MATCH (t:Tag)-[r:IS_RELATED_TO]-(t2:Tag) " +
|
||||
"WHERE ID(t) = $tag_id " +
|
||||
"RETURN {rels: collect(DISTINCT r), nodes: collect(DISTINCT t2)} as rank1 " +
|
||||
"LIMIT 1",
|
||||
params);
|
||||
|
||||
RelatedTags out = new RelatedTags();
|
||||
|
||||
if (result.hasNext()) {
|
||||
Record row = result.next();
|
||||
tagsFromRelMap(out, row.get(0).asMap());
|
||||
}
|
||||
|
||||
return out;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void parseRelatedResult(StatementResult result, SearchResult out) {
|
||||
long start = System.nanoTime();
|
||||
if (result.hasNext()) {
|
||||
@ -212,6 +312,32 @@ public class MusicDatabase extends AbstractBinder {
|
||||
));
|
||||
}
|
||||
|
||||
private void tagsFromRelMap(RelatedTags out, Map<String, Object> rank1) {
|
||||
out.relations.addAll(((List<Relationship>) rank1.get("rels"))
|
||||
.stream()
|
||||
.map(MusicDatabase::makeRelation)
|
||||
.collect(Collectors.toList()
|
||||
));
|
||||
out.tags.addAll(((List<Node>) rank1.get("nodes"))
|
||||
.stream()
|
||||
.map(MusicDatabase::makeTag)
|
||||
.collect(Collectors.toList()
|
||||
));
|
||||
}
|
||||
|
||||
private void labelsFromRelMap(RelatedLabels out, Map<String, Object> rank1) {
|
||||
out.relations.addAll(((List<Relationship>) rank1.get("rels"))
|
||||
.stream()
|
||||
.map(MusicDatabase::makeRelation)
|
||||
.collect(Collectors.toList()
|
||||
));
|
||||
out.labels.addAll(((List<Node>) rank1.get("nodes"))
|
||||
.stream()
|
||||
.map(MusicDatabase::makeLabel)
|
||||
.collect(Collectors.toList()
|
||||
));
|
||||
}
|
||||
|
||||
private static Artist makeArtist(Node node) {
|
||||
Artist artist = new Artist();
|
||||
artist.id = node.id();
|
||||
@ -228,8 +354,15 @@ public class MusicDatabase extends AbstractBinder {
|
||||
private static Tag makeTag(Node node) {
|
||||
return new Tag(
|
||||
node.id(),
|
||||
node.get("name").asString(),
|
||||
0
|
||||
node.get("name").asString()
|
||||
);
|
||||
}
|
||||
|
||||
private static Label makeLabel(Node node) {
|
||||
return new Label(
|
||||
node.id(),
|
||||
node.get("id").asString(),
|
||||
node.get("name").asString()
|
||||
);
|
||||
}
|
||||
|
||||
@ -275,7 +408,7 @@ public class MusicDatabase extends AbstractBinder {
|
||||
((List<Map>) map.get("tags"))
|
||||
.stream()
|
||||
.filter(x -> x.get("name") != null)
|
||||
.map(x -> new Tag(
|
||||
.map(x -> new WeightedTag(
|
||||
(Long) x.get("id"),
|
||||
(String) x.get("name"),
|
||||
(Double) x.get("weight")
|
||||
@ -298,6 +431,8 @@ public class MusicDatabase extends AbstractBinder {
|
||||
|
||||
try (Session session = driver.session()) {
|
||||
|
||||
AutoCompleteData data = new AutoCompleteData();
|
||||
|
||||
StatementResult result = query(session,
|
||||
"MATCH (a:Artist) " +
|
||||
"WHERE a.sortname STARTS WITH $prefix " +
|
||||
@ -305,22 +440,49 @@ public class MusicDatabase extends AbstractBinder {
|
||||
"LIMIT 30",
|
||||
params);
|
||||
|
||||
AutoCompleteData data = new AutoCompleteData();
|
||||
|
||||
while (result.hasNext()) {
|
||||
Map<String, Object> map = result.next().get("a").asMap();
|
||||
|
||||
ArtistOverview a = new ArtistOverview();
|
||||
a.name = (String) map.get("name");
|
||||
a.comment = (String) map.get("comment");
|
||||
a.year = (long) map.get("year");
|
||||
a.mbid = (String) map.get("id");
|
||||
AutocompleteLine line = new AutocompleteLine();
|
||||
line.name = (String) map.get("name");
|
||||
line.type = "artist";
|
||||
line.comment = (String) map.get("comment");
|
||||
line.year = (long) map.get("year");
|
||||
line.id = (String) map.get("id");
|
||||
|
||||
data.artists.add(a);
|
||||
data.lines.add(line);
|
||||
}
|
||||
|
||||
params.put("prefix", prefix.toLowerCase());
|
||||
StatementResult tagResult = query(session,
|
||||
"MATCH (t:Tag)-[:IS_TAGGED]-(:Artist) " +
|
||||
"WHERE t.name STARTS WITH $prefix " +
|
||||
"RETURN DISTINCT t ORDER BY t.occurrences DESC " +
|
||||
"LIMIT 15",
|
||||
params);
|
||||
|
||||
while (tagResult.hasNext()) {
|
||||
Node node = tagResult.next().get("t").asNode();
|
||||
|
||||
AutocompleteLine line = new AutocompleteLine();
|
||||
line.name = node.get("name").asString();
|
||||
line.type = "tag";
|
||||
line.id = String.valueOf(node.id());
|
||||
|
||||
// Interlace tags with the artists, keeping listeners order
|
||||
if (data.lines.size() > 0) {
|
||||
for (int i = 0; i < data.lines.size(); i++) {
|
||||
if (data.lines.get(i).name.toLowerCase().compareTo(line.name) > 0) {
|
||||
data.lines.add(i+1, line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data.lines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -49,14 +49,4 @@ public class ArtistController {
|
||||
return db.getArtistDetails(mbid);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("autocomplete/{prefix}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public AutoCompleteData autoComplete(@PathParam("prefix") String prefix) {
|
||||
|
||||
prefix = prefix.replace('+', ' ');
|
||||
logger.info(String.format("Autocomplete for '%s'", prefix));
|
||||
|
||||
return db.autoComplete(prefix);
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AutoCompleteData {
|
||||
public List<ArtistOverview> artists;
|
||||
public List<AutocompleteLine> lines;
|
||||
|
||||
public AutoCompleteData() {
|
||||
artists = new ArrayList<>(30);
|
||||
lines = new ArrayList<>(30);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
package net.simon987.musicgraph.webapi;
|
||||
|
||||
import net.simon987.musicgraph.io.MusicDatabase;
|
||||
import net.simon987.musicgraph.logging.LogManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@Path("/")
|
||||
public class AutocompleteController {
|
||||
|
||||
private static Logger logger = LogManager.getLogger();
|
||||
|
||||
@Inject
|
||||
private MusicDatabase db;
|
||||
|
||||
public AutocompleteController() {
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/autocomplete/{prefix}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public AutoCompleteData autoComplete(@PathParam("prefix") String prefix) {
|
||||
|
||||
prefix = prefix.replace('+', ' ');
|
||||
logger.info(String.format("Autocomplete for '%s'", prefix));
|
||||
|
||||
return db.autoComplete(prefix);
|
||||
}
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
package net.simon987.musicgraph.webapi;
|
||||
|
||||
public class ArtistOverview {
|
||||
public class AutocompleteLine {
|
||||
public String name;
|
||||
public String mbid;
|
||||
public String type;
|
||||
public String id;
|
||||
public String comment;
|
||||
public long year;
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package net.simon987.musicgraph.webapi;
|
||||
|
||||
import net.simon987.musicgraph.entities.RelatedLabels;
|
||||
import net.simon987.musicgraph.entities.SearchResult;
|
||||
import net.simon987.musicgraph.io.MusicDatabase;
|
||||
import net.simon987.musicgraph.logging.LogManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@Path("/label")
|
||||
public class LabelController {
|
||||
|
||||
private static Logger logger = LogManager.getLogger();
|
||||
|
||||
@Inject
|
||||
private MusicDatabase db;
|
||||
|
||||
public LabelController() {
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("related/{id}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public SearchResult getRelated(@PathParam("id") long id) {
|
||||
|
||||
logger.info(String.format("Related artist for label %d", id));
|
||||
return db.getRelatedByLabel(id);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("label/{id}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public RelatedLabels getRelatedLabel(@PathParam("id") long id) {
|
||||
|
||||
logger.info(String.format("Related labels for label %d", id));
|
||||
return db.getRelatedLabelByLabel(id);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package net.simon987.musicgraph.webapi;
|
||||
|
||||
import net.simon987.musicgraph.entities.RelatedTags;
|
||||
import net.simon987.musicgraph.entities.SearchResult;
|
||||
import net.simon987.musicgraph.io.MusicDatabase;
|
||||
import net.simon987.musicgraph.logging.LogManager;
|
||||
@ -28,7 +29,16 @@ public class TagController {
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public SearchResult getRelated(@PathParam("id") long id) {
|
||||
|
||||
logger.info(String.format("Related tag for %d", id));
|
||||
logger.info(String.format("Related artists for tag %d", id));
|
||||
return db.getRelatedByTag(id);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("tag/{id}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public RelatedTags getRelatedTag(@PathParam("id") long id) {
|
||||
|
||||
logger.info(String.format("Related tags for tag %d", id));
|
||||
return db.getRelatedTagByTag(id);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user