Skip to content

Commit

Permalink
[Docs] Add basic documentation content.
Browse files Browse the repository at this point in the history
Closes #2.
  • Loading branch information
meistermeier committed Dec 27, 2022
1 parent 04d3f25 commit f57da5c
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 12 deletions.
5 changes: 2 additions & 3 deletions README.md
Expand Up @@ -122,7 +122,7 @@ and the inner `Iterable` as the returned type of the `createCollectionConverterF
Also, there is a `RecordMapping` class in the _example_ folder.
Check this out if you are having trouble, using the given API.

## Parameter generator
## Parameter renderer

With a `Renderer`, it is possible to render a class into a map of driver values.
Those parameters can be used within the Driver query.
Expand Down Expand Up @@ -170,5 +170,4 @@ Otherwise, the Java compiler will generate incrementing names in the bytecode.
* If for whatever reasons the query returns the very same node in multiple records, those nodes will get mapped multiple times.
There is no concept of working with identifiers or similar.
* True symmetrical converters.
Currently only `Mapper` has a nice infrastructure.
* Real documentation, not just this _README_.
Currently only `Mapper` has a nice infrastructure.
167 changes: 167 additions & 0 deletions docs/index.adoc
@@ -0,0 +1,167 @@
= Neo4j Java tool belt
Gerrit Meier <meistermeier@gmail.com>
:toc: left
:doctype: book
:source-highlighter: rouge
:listing-caption: Listing

:current-version: 0.1.0

== About

NOTE: The project itself is not an official Neo4j library.

This project can be seen as a companion library for the official https://github.com/neo4j/neo4j-java-driver[Neo4j Java Driver].
It does not aim to replace or wrap functionality of the driver, but instead enriches already existing mechanisms.

The core idea (from today's perspective) is to take some concepts and learnings of object-graph-mapping libraries
like https://github.com/neo4j/neo4j-ogm[Neo4j-OGM] or https://github.com/spring-projects/spring-data-neo4j[Spring Data Neo4j]
and re-assemble them into light-weight modules.

Namely, those parts are currently:

* Read-only mapper
+
Converts Java driver's records into Java objects.
* Parameter renderer
+
Serializes a given Java object into a Java driver `Value` map.

Feel free to provide feedback and ideas or in general contributions at https://github.com/meistermeier/neo4j-java-toolbelt.

== Dependencies

The mapper and renderer are currently part of the very same artifact: `neo4j-java-toolbelt-mapper`.
This might change in the future or the combined artifact will get renamed.

[source,xml,subs="+attributes"]
.Maven dependency
----
<dependency>
<groupId>com.meistermeier.neo4j.toolbelt</groupId>
<artifactId>neo4j-java-toolbelt-mapper</artifactId>
<version>{current-version}</version>
</dependency>
----

//todo gradle dependency definition

== Mapper

The mapper is meant for converting Neo4j Java driver's `Record` into Java objects.
It generates a _typed_ `Function<Record, T>` that can be provided to the driver's `Result#list` method.

The supported return patterns are:

* Node(s)
* Values
* Map structure

Because the library wants to avoid any effect on the application code, there are no annotations or other kind meta-information.
This leads to the question: What does get mapped in the end?
The answer to this is pretty simple.
The node's properties, values names or keys in the map structure have to match the names of the defined parameter in the constructor of the target record/class.

Also, the mapper does not take care about post-initialization population of properties.
There is only one mapping phase per instance and this is the instantiation via the best-matching constructor (type and name wise).

NOTE: If you are working with classes, you need to compile your application with `-parameters` to preserve the constructor's parameter names.
Otherwise, the mapper cannot link the right values.

=== Example

To get started, a node returned from the driver, should get mapped into a record with two fields.

[source,java,indent=0]
.Record for mapping
----
include::../examples/src/main/java/com/meistermeier/neo4j/toolbelt/examples/mapper/Person.java[tag=map-record]
----

To retrieve the mapping function, the `Mapper` class provides a `createMapper` method.
Called with the matching type, it generates the right mapping function.

[source,java,indent=0]
.Generate mapping function
----
include::../examples/src/main/java/com/meistermeier/neo4j/toolbelt/examples/mapper/RecordReadingExample.java[tag=mapping-function]
----

This mapping function can be used in the `Result#list` function to map the individual `Records`.
This does, of course, also work for single results with a list size of _1_.

[source,java,indent=0]
.Use mapping function in list function
----
include::../examples/src/main/java/com/meistermeier/neo4j/toolbelt/examples/mapper/RecordReadingExample.java[tag=mapping-function-list]
----

Since this is a Java standard `Function`, it can also get applied to the `Result#single` returning `Record`.

[source,java,indent=0]
.Apply mapping function to driver `Record`
----
include::../examples/src/main/java/com/meistermeier/neo4j/toolbelt/examples/mapper/RecordReadingExample.java[tag=mapping-function-apply]
----


== Parameter Renderer

With a `Renderer`, it is possible to render a class into a map of driver values.
Those parameters can then be used within the driver's `Session#query` method.

[source,java,indent=0]
.Parameter providing record
----
include::../examples/src/main/java/com/meistermeier/neo4j/toolbelt/examples/parameters/ParameterRenderingExample.java[tag=parameter-record]
----

A populated instance of this type can then be handed over to the `Renderer` to get mapped into a driver `Value`.
In detail, it will become a `MapValue` in this case.

[source,java,indent=0]
.Parameter record rendering
----
include::../examples/src/main/java/com/meistermeier/neo4j/toolbelt/examples/parameters/ParameterRenderingExample.java[tag=parameter-record-use]
----

The `Renderer` can also produce Cypher collections of rendered instances (for driver's experts: `ListValue`).
By default, those collections are named _rows_.

[source,java,indent=0]
.Collection parameter rendering
----
include::../examples/src/main/java/com/meistermeier/neo4j/toolbelt/examples/parameters/ParameterRenderingExample.java[tag=parameter-record-collection-use]
----

In the example above the `Renderer#toParameters(Collection)` method is a shortcut for `Renderer#toParameters(Collection, String)`.
It is possible to use the overloaded method directly and name the collection that gets rendered explicitly.

[source,java,indent=0]
.Named collection parameter rendering
----
include::../examples/src/main/java/com/meistermeier/neo4j/toolbelt/examples/parameters/ParameterRenderingExample.java[tag=parameter-record-named-collection-use]
----

== Supported types

Although these are the supported types for the `Mapper`,
the `Renderer` might not support some of them yet.

|===
| Java | Cypher/Driver

| Long | Long
| Integer | Long
| Double | Double
| Float | Double
| String | String
| Boolean | Boolean
| LocalDate | LocalDate
| LocalDateTime | LocalDateTime
| LocalTime | LocalTime
| OffsetDateTime | OffsetDateTime
| OffsetTime | OffsetTime
| ZonedDateTime | ZonedDateTime
| List<> of everything ^^ | [] of everything ^^
|===
@@ -1,3 +1,5 @@
package com.meistermeier.neo4j.toolbelt.examples.mapper;

// tag::map-record[]
public record Person(String name, Integer yearBorn) {}
// end::map-record[]
Expand Up @@ -11,7 +11,6 @@
public class RecordReadingExample {

private static final StackWalker walker = StackWalker.getInstance();
private static final Mapper mapper = Mapper.INSTANCE;

public static void main(String[] args) {
Driver driver = Environment.getDriver();
Expand All @@ -34,13 +33,19 @@ public static void mapSingleRecord(Driver driver) {
driver.session().run("CREATE (:Person{name: 'Gerrit', yearBorn: 1983})").consume();

try (var session = driver.session()) {
Record singleNode = session.run("MATCH (p:Person) return p").single();
Function<Record, Person> personConverter = mapper.createMapperFor(Person.class);
// tag::mapping-function[]
Function<Record, Person> personConverter = Mapper.INSTANCE.createMapperFor(Person.class);
// end::mapping-function[]

// tag::mapping-function-apply[]
Record singleNode = session.run("MATCH (p:Person) return p").single();
Person person = personConverter.apply(singleNode);
// end::mapping-function-apply[]

// tag::mapping-function-list[]
List<Person> people = session.run("MATCH (p:Person) return p")
.list(personConverter);
// end::mapping-function-list[]

logOutput(person);
logOutput(people);
Expand All @@ -54,7 +59,7 @@ public static void mapMultipleRecords(Driver driver) {

try (var session = driver.session()) {
List<Person> people = session.run("MATCH (p:Person) return p")
.list(mapper.createMapperFor(Person.class));
.list(Mapper.INSTANCE.createMapperFor(Person.class));

logOutput(people);
}
Expand All @@ -67,7 +72,7 @@ public static void mapListOfNodes(Driver driver) {

try (var session = driver.session()) {
Record personCollectionRecord = session.run("MATCH (p:Person) return collect(p)").single();
Iterable<Person> people = mapper.createCollectionMapperFor(Person.class).apply(personCollectionRecord);
Iterable<Person> people = Mapper.INSTANCE.createCollectionMapperFor(Person.class).apply(personCollectionRecord);

logOutput(people);
}
Expand All @@ -80,7 +85,7 @@ public static void mapMultipleListsOfNodes(Driver driver) {

try (var session = driver.session()) {
List<Iterable<Person>> people = session.run("MATCH (p:Person) return collect(p)")
.list(mapper.createCollectionMapperFor(Person.class));
.list(Mapper.INSTANCE.createCollectionMapperFor(Person.class));

logOutput(people);
}
Expand All @@ -92,7 +97,7 @@ public static void mapFromValueMap(Driver driver) {

try (var session = driver.session()) {
Record singleNode = session.run("MATCH (p:Person) return p{.name, .yearBorn}").single();
Person person = mapper.createMapperFor(Person.class).apply(singleNode);
Person person = Mapper.INSTANCE.createMapperFor(Person.class).apply(singleNode);

logOutput(person);
}
Expand All @@ -104,7 +109,7 @@ public static void mapFromMultipleValues(Driver driver) {

try (var session = driver.session()) {
Record singleNode = session.run("MATCH (p:Person) return p.name as name, p.yearBorn as yearBorn").single();
Person person = mapper.createMapperFor(Person.class).apply(singleNode);
Person person = Mapper.INSTANCE.createMapperFor(Person.class).apply(singleNode);

logOutput(person);
}
Expand Down
Expand Up @@ -16,17 +16,51 @@ public class ParameterRenderingExample {
public static void main(String[] args) {
Driver driver = Environment.getDriver();
renderRecordAsParameters(driver);
renderCollectionOfRecordsAsParameters(driver);
renderNamedCollectionOfRecordsAsParameters(driver);
renderClassAsParameters(driver);

driver.close();
}

static void renderRecordAsParameters(Driver driver) {
try (var session = driver.session()) {
// tag::parameter-record-use[]
ParameterRecord parameterRecord = new ParameterRecord("test", 4711);
Value parameters = Renderer.INSTANCE.toParameters(parameterRecord);
Record result = session.run("RETURN $a, $b", parameters).single();
System.out.println(result); // Record<{$a: "test", $b: 4711}>
// end::parameter-record-use[]
}
}

static void renderCollectionOfRecordsAsParameters(Driver driver) {
try (var session = driver.session()) {
// tag::parameter-record-collection-use[]
ParameterRecord parameterRecord1 = new ParameterRecord("test1", 4711);
ParameterRecord parameterRecord2 = new ParameterRecord("test2", 42);
Value parameters = Renderer.INSTANCE.toParameters(List.of(parameterRecord1, parameterRecord2));
session.run("UNWIND $rows as row RETURN row.a, row.b", parameters).forEachRemaining(
System.out::println
);
// Record<{row.a: "test1", row.b: 4711}>
// Record<{row.a: "test2", row.b: 42}>
// end::parameter-record-collection-use[]
}
}

static void renderNamedCollectionOfRecordsAsParameters(Driver driver) {
try (var session = driver.session()) {
// tag::parameter-record-named-collection-use[]
ParameterRecord parameterRecord1 = new ParameterRecord("test1", 4711);
ParameterRecord parameterRecord2 = new ParameterRecord("test2", 42);
Value parameters = Renderer.INSTANCE.toParameters(List.of(parameterRecord1, parameterRecord2), "things");
session.run("UNWIND $things as thing RETURN thing.a, thing.b", parameters).forEachRemaining(
System.out::println
);
// Record<{thing.a: "test1", thing.b: 4711}>
// Record<{thing.a: "test2", thing.b: 42}>
// end::parameter-record-named-collection-use[]
}
}

Expand All @@ -38,8 +72,9 @@ static void renderClassAsParameters(Driver driver) {
System.out.println(result); // Record<{$c: "ClassTest", $d: ["a", "b"]}>
}
}

// tag::parameter-record[]
record ParameterRecord(String a, int b) {}
// end::parameter-record[]

static class ParameterClass {
// access either via public field
Expand Down
31 changes: 31 additions & 0 deletions pom.xml
Expand Up @@ -57,6 +57,7 @@
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<neo4j-java-driver.version>5.3.1</neo4j-java-driver.version>
<asciidoctor-maven-plugin.version>2.2.2</asciidoctor-maven-plugin.version>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -184,5 +185,35 @@
</plugins>
</build>
</profile>
<profile>
<id>docs</id>
<build>
<plugins>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>${asciidoctor-maven-plugin.version}</version>
<executions>
<execution>
<id>convert-to-html</id>
<phase>generate-resources</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<sourceDirectory>docs/</sourceDirectory>
<outputDirectory>${project.build.directory}/html</outputDirectory>
<attributes>
<source-highlighter>rouge</source-highlighter>
<toc>left</toc>
<icons>font</icons>
</attributes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

0 comments on commit f57da5c

Please sign in to comment.