Skip to content

Commit

Permalink
Implement search endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
QubitPi committed Nov 6, 2024
1 parent a1e001c commit b93c610
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 66 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ curl -v -i -s -k -X POST https://api.paion-data.dev:8444/services \
curl -i -k -X POST https://api.paion-data.dev:8444/services/wilhelm-ws-expand/routes \
--data "paths[]=/wilhelm/expand" \
--data name=wilhelm-ws-expand

# search
curl -v -i -s -k -X POST https://api.paion-data.dev:8444/services \
--data name=wilhelm-ws-search \
--data url="http://${GATEWAY_PUBLIC_IP}:8080/v1/neo4j/search"
curl -i -k -X POST https://api.paion-data.dev:8444/services/wilhelm-ws-search/routes \
--data "paths[]=/wilhelm/search" \
--data name=wilhelm-ws-search
```

We should see `HTTP/1.1 201 Created` as signs of success.
Expand Down
153 changes: 87 additions & 66 deletions src/main/java/org/qubitpi/wilhelm/web/endpoints/Neo4JServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,93 @@ public Response getVocabularyByLanguagePaged(
.build();
}

/**
* Search all nodes whose label contains a specified keyword.
*
* @param keyword The provided keyword
*
* @return all nodes whose "name" 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);

return Response
.status(Response.Status.OK)
.entity(executeNonPathQuery(query))
.build();
}

/**
* Runs a cypher query against Neo4J database and return result as a JSON-serializable.
* <p>
* Use this method only if the {@code query} does not involve path, because this method cannot handle query result
* that has path object nested in it
*
* @param query A standard cypher query string
*
* @return query's native result
*/
private Object executeNonPathQuery(@NotNull final String query) {
return executeNativeQuery(query).records()
.stream()
.map(
record -> record.keys()
.stream()
.map(key -> new AbstractMap.SimpleImmutableEntry<>(key, expand(record.get(key))))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
)
.collect(Collectors.toList());
}

/**
* Transforms a Neo4J {@link Value} object into a Jackson-serializable Java object.
*
* See https://neo4j.com/docs/java-manual/current/data-types/ for more details
*
* @param value An object graph. Cannot be {@code null}
*
* @return a {@link Map} representation of the object graph and can be Jackson-serialized
*/
private static Object expand(@NotNull final Value value) {
if (isTerminalValue(value)) {
if (value.type().equals(InternalTypeSystem.TYPE_SYSTEM.INTEGER())) {
return value.asInt();
} else if (value.type().equals(InternalTypeSystem.TYPE_SYSTEM.BOOLEAN())) {
return value.asBoolean();
} else {
return value.asString();
}
}

return StreamSupport.stream(value.keys().spliterator(), false)
.map(key -> new AbstractMap.SimpleImmutableEntry<>(key, expand(value.get(key))))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

/**
* Returns whether or not a {@link Value} object is the "leaf" node in Jackson serialization.
* <p>
* A "leaf" node is defined to be one of
* <ul>
* <li> integer
* <li> string
* <li> boolean
* </ul>
*
* @param value An object graph. Cannot be {@code null}
*
* @return {@code true} if the object is simply a Jackson-serializable leaf node or {@code false} otherwise
*/
private static boolean isTerminalValue(@NotNull final Value value) {
return value.type().equals(InternalTypeSystem.TYPE_SYSTEM.INTEGER())
|| value.type().equals(InternalTypeSystem.TYPE_SYSTEM.STRING())
|| value.type().equals(InternalTypeSystem.TYPE_SYSTEM.BOOLEAN());
}

/**
* Recursively find all related terms and definitions of a word.
*
Expand Down Expand Up @@ -253,27 +340,6 @@ RETURN path, length(path) AS hops
.build();
}

/**
* Runs a cypher query against Neo4J database and return result as a JSON-serializable.
* <p>
* Use this method only if the {@code query} does not involve path, because this method cannot handle query result
* that has path object nested in it
*
* @param query A standard cypher query string
*
* @return query's native result
*/
private Object executeNonPathQuery(@NotNull final String query) {
return executeNativeQuery(query).records()
.stream()
.map(
record -> record.keys()
.stream()
.map(key -> new AbstractMap.SimpleImmutableEntry<>(key, expand(record.get(key))))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
)
.collect(Collectors.toList());
}

/**
* Runs a cypher query against Neo4J database and return the query result unmodified.
Expand All @@ -294,49 +360,4 @@ private EagerResult executeNativeQuery(@NotNull final String query) {
.execute();
}
}

/**
* Transforms a Neo4J {@link Value} object into a Jackson-serializable Java object.
*
* See https://neo4j.com/docs/java-manual/current/data-types/ for more details
*
* @param value An object graph. Cannot be {@code null}
*
* @return a {@link Map} representation of the object graph and can be Jackson-serialized
*/
private static Object expand(@NotNull final Value value) {
if (isTerminalValue(value)) {
if (value.type().equals(InternalTypeSystem.TYPE_SYSTEM.INTEGER())) {
return value.asInt();
} else if (value.type().equals(InternalTypeSystem.TYPE_SYSTEM.BOOLEAN())) {
return value.asBoolean();
} else {
return value.asString();
}
}

return StreamSupport.stream(value.keys().spliterator(), false)
.map(key -> new AbstractMap.SimpleImmutableEntry<>(key, expand(value.get(key))))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

/**
* Returns whether or not a {@link Value} object is the "leaf" node in Jackson serialization.
* <p>
* A "leaf" node is defined to be one of
* <ul>
* <li> integer
* <li> string
* <li> boolean
* </ul>
*
* @param value An object graph. Cannot be {@code null}
*
* @return {@code true} if the object is simply a Jackson-serializable leaf node or {@code false} otherwise
*/
private static boolean isTerminalValue(@NotNull final Value value) {
return value.type().equals(InternalTypeSystem.TYPE_SYSTEM.INTEGER())
|| value.type().equals(InternalTypeSystem.TYPE_SYSTEM.STRING())
|| value.type().equals(InternalTypeSystem.TYPE_SYSTEM.BOOLEAN());
}
}

0 comments on commit b93c610

Please sign in to comment.