diff --git a/README.md b/README.md
index 2807ad2..c968bbc 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,7 @@ We should see `HTTP/1.1 201 Created` as signs of success.
- vocabulary count: https://api.paion-data.dev/wilhelm/languages/german?perPage=100&page=1
- query vocabulary paged: https://api.paion-data.dev/wilhelm/languages/german/count
- expand: https://api.paion-data.dev/wilhelm/expand/nämlich
+- search: https://api.paion-data.dev/wilhelm/search/das
License
-------
diff --git a/src/main/java/org/qubitpi/wilhelm/Link.java b/src/main/java/org/qubitpi/wilhelm/Link.java
index 3f69838..0e45f5e 100644
--- a/src/main/java/org/qubitpi/wilhelm/Link.java
+++ b/src/main/java/org/qubitpi/wilhelm/Link.java
@@ -47,6 +47,13 @@
@JsonIncludeProperties({ "label", "sourceNodeId", "targetNodeId", "attributes" })
public class Link {
+ /**
+ * The database node attribute name whose value is used for displaying the relationship caption on UI.
+ *
+ * For example, for Neo4J database, this would correspond to a relationship property.
+ */
+ public static final String LABEL_ATTRIBUTE = "label";
+
private static final Logger LOG = LoggerFactory.getLogger(Link.class);
private final String label;
@@ -93,18 +100,17 @@ private Link(
* @throws IllegalStateException if {@code relationship} is missing a "name" property
*/
public static Link valueOf(final Relationship relationship) {
- final String labelKey = "name";
- if (!Objects.requireNonNull(relationship).asMap().containsKey(labelKey)) {
- LOG.error("Neo4J relationship does not contain '{}' attribute: {}", labelKey, relationship.asMap());
+ if (!Objects.requireNonNull(relationship).asMap().containsKey(LABEL_ATTRIBUTE)) {
+ LOG.error("Neo4J relationship does not contain '{}' attribute: {}", LABEL_ATTRIBUTE, relationship.asMap());
throw new IllegalStateException(
"There seems to be a data format mismatch between Wilhelm webservice and Neo4J database. " +
"Please file an issue at https://github.com/QubitPi/wilhelm-ws/issues for a fix"
);
}
- final String label = relationship.asMap().get(labelKey).toString();
+ final String label = relationship.asMap().get(LABEL_ATTRIBUTE).toString();
final Map attributes = relationship.asMap().entrySet().stream()
- .filter(entry -> !labelKey.equals(entry.getKey()))
+ .filter(entry -> !LABEL_ATTRIBUTE.equals(entry.getKey()))
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
return new Link(label, relationship.startNodeElementId(), relationship.endNodeElementId(), attributes);
diff --git a/src/main/java/org/qubitpi/wilhelm/Node.java b/src/main/java/org/qubitpi/wilhelm/Node.java
index 82554ce..ad99242 100644
--- a/src/main/java/org/qubitpi/wilhelm/Node.java
+++ b/src/main/java/org/qubitpi/wilhelm/Node.java
@@ -45,6 +45,13 @@
@JsonIncludeProperties({ "id", "label", "attributes" })
public class Node {
+ /**
+ * The database node attribute name whose value is used for displaying the node caption on UI.
+ *
+ * For example, for Neo4J database, this would correspond to a node property.
+ */
+ public static final String LABEL_ATTRIBUTE = "label";
+
private static final Logger LOG = LoggerFactory.getLogger(Node.class);
private final String id;
@@ -82,18 +89,17 @@ private Node(@NotNull final String id, @NotNull final String label, @NotNull fin
* @throws IllegalStateException if {@code node} is missing a "name" property
*/
public static Node valueOf(@NotNull final org.neo4j.driver.types.Node node) {
- final String labelKey = "name";
- if (!Objects.requireNonNull(node).asMap().containsKey(labelKey)) {
- LOG.error("Neo4J node does not contain '{}' attribute: {}", labelKey, node.asMap());
+ if (!Objects.requireNonNull(node).asMap().containsKey(LABEL_ATTRIBUTE)) {
+ LOG.error("Neo4J node does not contain '{}' attribute: {}", LABEL_ATTRIBUTE, node.asMap());
throw new IllegalStateException(
"There seems to be a data format mismatch between Wilhelm webservice and Neo4J database. " +
"Please file an issue at https://github.com/QubitPi/wilhelm-ws/issues for a fix"
);
}
- final String label = node.asMap().get(labelKey).toString();
+ final String label = node.asMap().get(LABEL_ATTRIBUTE).toString();
final Map attributes = node.asMap().entrySet().stream()
- .filter(entry -> !labelKey.equals(entry.getKey()))
+ .filter(entry -> !LABEL_ATTRIBUTE.equals(entry.getKey()))
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
return new Node(node.elementId(), label, attributes);
diff --git a/src/main/java/org/qubitpi/wilhelm/web/endpoints/Neo4JServlet.java b/src/main/java/org/qubitpi/wilhelm/web/endpoints/Neo4JServlet.java
index cb7a7f4..7308cf7 100644
--- a/src/main/java/org/qubitpi/wilhelm/web/endpoints/Neo4JServlet.java
+++ b/src/main/java/org/qubitpi/wilhelm/web/endpoints/Neo4JServlet.java
@@ -126,9 +126,12 @@ public Response getVocabularyByLanguagePaged(
final String query = String.format(
"MATCH (t:Term WHERE t.language = '%s')-[r]->(d:Definition) " +
- "RETURN t.name AS term, d.name AS definition " +
+ "RETURN t.%s AS term, d.%s AS definition " +
"SKIP %s LIMIT %s",
- requestedLanguage.getDatabaseName(), (Integer.parseInt(page) - 1) * Integer.parseInt(perPage), perPage
+ requestedLanguage.getDatabaseName(),
+ Node.LABEL_ATTRIBUTE,
+ Node.LABEL_ATTRIBUTE,
+ (Integer.parseInt(page) - 1) * Integer.parseInt(perPage), perPage
);
return Response
@@ -142,14 +145,17 @@ public Response getVocabularyByLanguagePaged(
*
* @param keyword The provided keyword
*
- * @return all nodes whose "name" attribute contains the search keyword
+ * @return all nodes whose {@link Node#LABEL_ATTRIBUTE "label"} attribute contains the search keyword
*/
@GET
@Path("/search/{keyword}")
@Produces(MediaType.APPLICATION_JSON)
@SuppressWarnings("MultipleStringLiterals")
public Response search(@NotNull @PathParam("keyword") final String keyword) {
- final String query = String.format("MATCH (node) WHERE node.name =~ '.*%s.*' RETURN node", keyword);
+ final String query = String.format(
+ "MATCH (node) WHERE node.%s =~ '.*%s.*' RETURN node",
+ Node.LABEL_ATTRIBUTE, keyword
+ );
return Response
.status(Response.Status.OK)
@@ -313,12 +319,13 @@ public Response expandApoc(
final String query = String.format(
"""
- MATCH (node{name:'%s'})
+ MATCH (node{%s:'%s'})
CALL apoc.path.expand(node, "LINK", null, 1, %s)
YIELD path
RETURN path, length(path) AS hops
ORDER BY hops;
""",
+ Node.LABEL_ATTRIBUTE,
word.replace("'", "\\'"), maxHops
);
diff --git a/src/test/groovy/org/qubitpi/wilhelm/LinkSpec.groovy b/src/test/groovy/org/qubitpi/wilhelm/LinkSpec.groovy
index 0b85ed8..945bfa0 100644
--- a/src/test/groovy/org/qubitpi/wilhelm/LinkSpec.groovy
+++ b/src/test/groovy/org/qubitpi/wilhelm/LinkSpec.groovy
@@ -47,7 +47,7 @@ class LinkSpec extends Specification {
_ | "attributes"
}
- def "when a Neo4J relationship does not contain 'name' property, an error is thrown"() {
+ def "when a Neo4J relationship does not contain 'label' property, an error is thrown"() {
when: "a Neo4J node has no properties"
Link.valueOf(Mock(Relationship) {asMap() >> [:]})
@@ -62,7 +62,7 @@ class LinkSpec extends Specification {
when: "a happy path Neo4J relationship is being converted to a transparent link"
Link actual = Link.valueOf(Mock(Relationship) {
asMap() >> [
- name: "my node",
+ label: "my node",
type: "follows"
]
startNodeElementId() >> "node1"
diff --git a/src/test/groovy/org/qubitpi/wilhelm/NodeSpec.groovy b/src/test/groovy/org/qubitpi/wilhelm/NodeSpec.groovy
index 9a1d3d2..e73e898 100644
--- a/src/test/groovy/org/qubitpi/wilhelm/NodeSpec.groovy
+++ b/src/test/groovy/org/qubitpi/wilhelm/NodeSpec.groovy
@@ -44,7 +44,7 @@ class NodeSpec extends Specification {
_ | "attributes"
}
- def "when a Neo4J node does not contain 'name' property, an error is thrown"() {
+ def "when a Neo4J node does not contain 'label' property, an error is thrown"() {
when: "a Neo4J node has no properties"
Node.valueOf(Mock(org.neo4j.driver.types.Node) {asMap() >> [:]})
@@ -59,7 +59,7 @@ class NodeSpec extends Specification {
when: "a happy path Neo4J node is being converted to a transparent node"
Node actual = Node.valueOf(Mock(org.neo4j.driver.types.Node) {
asMap() >> [
- name: "my node",
+ label: "my node",
color: "blue",
size: "medium"
]
diff --git a/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletITSpec.groovy b/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletITSpec.groovy
index 7a94b98..4b40387 100644
--- a/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletITSpec.groovy
+++ b/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletITSpec.groovy
@@ -140,7 +140,7 @@ class Neo4JServletITSpec extends Specification {
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.when()
- .get("/neo4j/expand/nämlich")
+ .get("/neo4j/expand/dreißig")
.then()
.statusCode(200)
.body("", hasKey("nodes"))
diff --git a/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletSpec.groovy b/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletSpec.groovy
index 3dbb781..d6a48bf 100644
--- a/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletSpec.groovy
+++ b/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletSpec.groovy
@@ -32,8 +32,8 @@ class Neo4JServletSpec extends Specification {
keys() >> ["term", "definition"]
get("term") >> Mock(Value) {
type() >> InternalTypeSystem.TYPE_SYSTEM.NODE()
- keys() >> ["name", "language"]
- get("name") >> Mock(Value) {
+ keys() >> ["label", "language"]
+ get("label") >> Mock(Value) {
type() >> InternalTypeSystem.TYPE_SYSTEM.STRING()
asString() >> "Hallo"
}
@@ -44,8 +44,8 @@ class Neo4JServletSpec extends Specification {
}
get("definition") >> Mock(Value) {
type() >> InternalTypeSystem.TYPE_SYSTEM.NODE()
- keys() >> ["name"]
- get("name") >> Mock(Value) {
+ keys() >> ["label"]
+ get("label") >> Mock(Value) {
type() >> InternalTypeSystem.TYPE_SYSTEM.STRING()
asString() >> "Hello"
}
@@ -55,11 +55,11 @@ class Neo4JServletSpec extends Specification {
expect:
Neo4JServlet.expand(value) == [
term: [
- name: "Hallo",
+ label: "Hallo",
language: "German"
],
definition: [
- name: "Hello"
+ label: "Hello"
]
]
}