Skip to content

Commit

Permalink
Return new types added in cypher as maps.
Browse files Browse the repository at this point in the history
The returned map will contains a `type` entry to indicate the type of the returned object.
If the object is present at the top level of the result (in `data`), besides the value returned in `row`, the type of this object is also returned in `meta`.

This approach is based on the assumption that the `map` type could only be returned at the top level of the result (in `data`). Any map that returned as part of a lager result, such as a property on a node could not be a map but a point, a temporal, etc.
Therefore with the conbination of the type info inside the map object and the meta data, there is no ambiguousness to decide whether the returned object is a real map or a new data type.

For example, if the result contains a single `Duration`, the entity of Rest response will be like:
```
{
  "results":[{
    "columns":["d"],
    "data":[{
      "row":[{"type":"duration","value":"P17D"}],
      "meta":[{"type":"duration"}]
    }]
  }],
  "errors":[]
}
```

If the result contains a node who has a property of `LocalDateTime` type, then the return json response would be like
```
{
  "results":[{
    "columns":["account"],
    "data":[{
      "row":[{
        "creationTime":{"type":"localdatetime","value":"1984-10-21T12:34"},
        "name":"zhen"
      }],
      "meta":[{
        "id":0,
        "type":"node",
        "deleted":false
      }]
    }]
  }],
  "errors":[]
}
```
  • Loading branch information
Zhen committed Mar 15, 2018
1 parent 7a0ac84 commit 9d89f67
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 72 deletions.
Expand Up @@ -139,5 +139,11 @@ public CRS getCRS()
{
return crs;
}

@Override
public String toString()
{
return geometryType;
}
}
}
Expand Up @@ -44,15 +44,15 @@
import org.neo4j.graphdb.spatial.Coordinate;
import org.neo4j.graphdb.spatial.Geometry;
import org.neo4j.graphdb.spatial.Point;
import org.neo4j.values.storable.PointValue;

import static java.util.Objects.requireNonNull;
import static org.neo4j.helpers.collection.MapUtil.genericMap;

public class Neo4jJsonCodec extends ObjectMapper
{
private enum Neo4jJsonMetaType
{
node, relationship, datetime, time, localdatetime, date, localtime, duration, point2d, point3d
node, relationship, datetime, time, localdatetime, date, localtime, duration, point
}

private TransitionalPeriodTransactionMessContainer container;
Expand Down Expand Up @@ -114,16 +114,25 @@ else if ( value instanceof CRS )
writeMap( out, genericMap( new LinkedHashMap<>(), "name", crs.getType(), "type", "link", "properties",
genericMap( new LinkedHashMap<>(), "href", crs.getHref() + "ogcwkt/", "type", "ogcwkt" ) ) );
}
else if ( value instanceof Temporal || value instanceof TemporalAmount )
else if ( value instanceof Temporal )
{
writeObject( out, parseTemporalType( (Temporal) value ), value.toString() );
}
else if ( value instanceof TemporalAmount )
{
super.writeValue( out, value.toString() );
writeObject( out, Neo4jJsonMetaType.duration, value.toString() );
}
else
{
super.writeValue( out, value );
}
}

private void writeObject( JsonGenerator out, Neo4jJsonMetaType type, String value ) throws IOException
{
writeMap( out, genericMap( new LinkedHashMap<>(), "type", type.name(), "value", value ) );
}

private void writeMap( JsonGenerator out, Map value ) throws IOException
{
out.writeStartObject();
Expand Down Expand Up @@ -238,15 +247,15 @@ void writeMeta( JsonGenerator out, Object value ) throws IOException
Node node = (Node) value;
try ( TransactionStateChecker stateChecker = TransactionStateChecker.create( container ) )
{
writeNodeOrRelationshipMeta( out, node.getId(), Neo4jJsonMetaType.node.name(), stateChecker.isNodeDeletedInCurrentTx( node.getId() ) );
writeNodeOrRelationshipMeta( out, node.getId(), Neo4jJsonMetaType.node, stateChecker.isNodeDeletedInCurrentTx( node.getId() ) );
}
}
else if ( value instanceof Relationship )
{
Relationship relationship = (Relationship) value;
try ( TransactionStateChecker transactionStateChecker = TransactionStateChecker.create( container ) )
{
writeNodeOrRelationshipMeta( out, relationship.getId(), Neo4jJsonMetaType.relationship.name(),
writeNodeOrRelationshipMeta( out, relationship.getId(), Neo4jJsonMetaType.relationship,
transactionStateChecker.isRelationshipDeletedInCurrentTx( relationship.getId() ) );
}
}
Expand Down Expand Up @@ -275,11 +284,11 @@ else if ( value instanceof Geometry )
}
else if ( value instanceof Temporal )
{
writeTemporalTypeMeta( out, (Temporal) value );
writeObjectMeta( out, parseTemporalType( (Temporal) value ) );
}
else if ( value instanceof TemporalAmount )
{
writeTypeMeta( out, Neo4jJsonMetaType.duration.name() );
writeObjectMeta( out, Neo4jJsonMetaType.duration );
}
else
{
Expand All @@ -290,28 +299,19 @@ else if ( value instanceof TemporalAmount )
private void writeGeometryTypeMeta( JsonGenerator out, Geometry value ) throws IOException
{
Neo4jJsonMetaType type = null;
if ( value instanceof PointValue )
if ( value instanceof Point )
{
PointValue p = (PointValue) value;
int size = p.coordinate().length;
if ( size == 2 )
{
type = Neo4jJsonMetaType.point2d;
}
else if ( size == 3 )
{
type = Neo4jJsonMetaType.point3d;
}
type = Neo4jJsonMetaType.point;
}
if ( type == null )
{
throw new IllegalArgumentException(
String.format( "Unsupported Geometry type: type=%s, value=%s", value.getClass().getSimpleName(), value.toString() ) );
String.format( "Unsupported Geometry type: type=%s, value=%s", value.getClass().getSimpleName(), value ) );
}
writeTypeMeta( out, type.name() );
writeObjectMeta( out, type );
}

private void writeTemporalTypeMeta( JsonGenerator out, Temporal value ) throws IOException
private Neo4jJsonMetaType parseTemporalType( Temporal value )
{
Neo4jJsonMetaType type = null;
if ( value instanceof ZonedDateTime )
Expand All @@ -334,13 +334,12 @@ else if ( value instanceof LocalTime )
{
type = Neo4jJsonMetaType.localtime;
}

if ( type == null )
{
throw new IllegalArgumentException(
String.format( "Unsupported Temporal type: type=%s, value=%s", value.getClass().getSimpleName(), value.toString() ) );
String.format( "Unsupported Temporal type: type=%s, value=%s", value.getClass().getSimpleName(), value ) );
}
writeTypeMeta( out, type.name() );
return type;
}

private void writeMetaPath( JsonGenerator out, Path value ) throws IOException
Expand All @@ -359,28 +358,30 @@ private void writeMetaPath( JsonGenerator out, Path value ) throws IOException
}
}

private void writeTypeMeta( JsonGenerator out, String type )
private void writeObjectMeta( JsonGenerator out, Neo4jJsonMetaType type )
throws IOException
{
requireNonNull( type, "The meta type cannot be null for known types." );
out.writeStartObject();
try
{
out.writeStringField( "type", type );
out.writeStringField( "type", type.name() );
}
finally
{
out.writeEndObject();
}
}

private void writeNodeOrRelationshipMeta( JsonGenerator out, long id, String type, boolean isDeleted )
private void writeNodeOrRelationshipMeta( JsonGenerator out, long id, Neo4jJsonMetaType type, boolean isDeleted )
throws IOException
{
requireNonNull( type, "The meta type could not be null for node or relationship." );
out.writeStartObject();
try
{
out.writeNumberField( "id", id );
out.writeStringField( "type", type );
out.writeStringField( "type", type.name() );
out.writeBooleanField( "deleted", isDeleted );
}
finally
Expand Down
Expand Up @@ -282,8 +282,8 @@ public String getSchemaConstraintLabelUniquenessPropertyUri( String label, Strin

public static int getLocalHttpPort()
{
ConnectorPortRegister connectorPortRegister = server().getDatabase().getGraph().getDependencyResolver()
.resolveDependency( ConnectorPortRegister.class );
ConnectorPortRegister connectorPortRegister =
server().getDatabase().getGraph().getDependencyResolver().resolveDependency( ConnectorPortRegister.class );
return connectorPortRegister.getLocalAddress( "http" ).getPort();
}

Expand Down
Expand Up @@ -27,6 +27,15 @@
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -64,8 +73,10 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.asList;
import static org.hamcrest.Matchers.sameInstance;
import static org.hamcrest.Matchers.startsWith;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
Expand Down Expand Up @@ -405,15 +416,11 @@ public void shouldSerializePointsAsListOfMapsOfProperties() throws Exception
ByteArrayOutputStream output = new ByteArrayOutputStream();
ExecutionResultSerializer serializer = getSerializerWith( output );

List<Coordinate> points = new ArrayList<>();
points.add( new Coordinate( 1, 2 ) );
points.add( new Coordinate( 2, 3 ) );
Result executionResult = mockExecutionResult(
map( "geom", SpatialMocks.mockPoint( 12.3, 45.6, mockWGS84() ) ),
map( "geom", SpatialMocks.mockPoint( 123, 456, mockCartesian() ) ),
map( "geom", SpatialMocks.mockPoint( 12.3, 45.6, 78.9, mockWGS84_3D() ) ),
map( "geom", SpatialMocks.mockPoint( 123, 456, 789, mockCartesian_3D() ) ),
map( "geom", SpatialMocks.mockGeometry( "LineString", points, mockCartesian() ) ) );
map( "geom", SpatialMocks.mockPoint( 123, 456, 789, mockCartesian_3D() ) ) );

// when
serializer.statementResult( executionResult, false );
Expand All @@ -425,27 +432,90 @@ public void shouldSerializePointsAsListOfMapsOfProperties() throws Exception
"{\"row\":[{\"type\":\"Point\",\"coordinates\":[12.3,45.6],\"crs\":" +
"{\"name\":\"WGS-84\",\"type\":\"link\",\"properties\":" +
"{\"href\":\"http://spatialreference.org/ref/epsg/4326/ogcwkt/\",\"type\":\"ogcwkt\"}" +
"}}],\"meta\":[null]}," +
"}}],\"meta\":[{\"type\":\"point\"}]}," +
"{\"row\":[{\"type\":\"Point\",\"coordinates\":[123.0,456.0],\"crs\":" +
"{\"name\":\"cartesian\",\"type\":\"link\",\"properties\":" +
"{\"href\":\"http://spatialreference.org/ref/sr-org/7203/ogcwkt/\",\"type\":\"ogcwkt\"}" +
"}}],\"meta\":[null]}," +
"}}],\"meta\":[{\"type\":\"point\"}]}," +
"{\"row\":[{\"type\":\"Point\",\"coordinates\":[12.3,45.6,78.9],\"crs\":" +
"{\"name\":\"WGS-84-3D\",\"type\":\"link\",\"properties\":" +
"{\"href\":\"http://spatialreference.org/ref/epsg/4979/ogcwkt/\",\"type\":\"ogcwkt\"}" +
"}}],\"meta\":[null]}," +
"}}],\"meta\":[{\"type\":\"point\"}]}," +
"{\"row\":[{\"type\":\"Point\",\"coordinates\":[123.0,456.0,789.0],\"crs\":" +
"{\"name\":\"cartesian-3D\",\"type\":\"link\",\"properties\":" +
"{\"href\":\"http://spatialreference.org/ref/sr-org/9157/ogcwkt/\",\"type\":\"ogcwkt\"}" +
"}}],\"meta\":[null]}," +
"{\"row\":[{\"type\":\"LineString\",\"coordinates\":[[1.0,2.0],[2.0,3.0]],\"crs\":" +
"{\"name\":\"cartesian\",\"type\":\"link\",\"properties\":" +
"{\"href\":\"http://spatialreference.org/ref/sr-org/7203/ogcwkt/\",\"type\":\"ogcwkt\"}" +
"}}],\"meta\":[null]}" +
"]}],\"errors\":[]}",
"}}],\"meta\":[{\"type\":\"point\"}]}" +
"]}],\"errors\":[]}",
result );
}

@Test
public void shouldSerializeTemporalAsListOfMapsOfProperties() throws Exception
{
// given
ByteArrayOutputStream output = new ByteArrayOutputStream();
ExecutionResultSerializer serializer = getSerializerWith( output );

Result executionResult = mockExecutionResult(
map( "temporal", LocalDate.of( 2018, 3, 12 ) ),
map( "temporal", ZonedDateTime.of( 2018, 3, 12, 13, 2, 10, 10, ZoneId.of( "UTC+1" ) ) ),
map( "temporal", OffsetTime.of( 12, 2, 4, 71, ZoneOffset.UTC ) ),
map( "temporal", LocalDateTime.of( 2018, 3, 12, 13, 2, 10, 10 ) ),
map( "temporal", LocalTime.of( 13, 2, 10, 10 ) ),
map( "temporal", Duration.of( 12, ChronoUnit.HOURS ) ) );

// when
serializer.statementResult( executionResult, false );
serializer.finish();

// then
String result = output.toString( UTF_8.name() );
assertEquals( "{\"results\":[{\"columns\":[\"temporal\"],\"data\":[" +
"{\"row\":[{\"type\":\"date\",\"value\":\"2018-03-12\"}],\"meta\":[{\"type\":\"date\"}]}," +
"{\"row\":[{\"type\":\"datetime\",\"value\":\"2018-03-12T13:02:10.000000010+01:00[UTC+01:00]\"}],\"meta\":[{\"type\":\"datetime\"}]}," +
"{\"row\":[{\"type\":\"time\",\"value\":\"12:02:04.000000071Z\"}],\"meta\":[{\"type\":\"time\"}]}," +
"{\"row\":[{\"type\":\"localdatetime\",\"value\":\"2018-03-12T13:02:10.000000010\"}],\"meta\":[{\"type\":\"localdatetime\"}]}," +
"{\"row\":[{\"type\":\"localtime\",\"value\":\"13:02:10.000000010\"}],\"meta\":[{\"type\":\"localtime\"}]}," +
"{\"row\":[{\"type\":\"duration\",\"value\":\"PT12H\"}],\"meta\":[{\"type\":\"duration\"}]}" +
"]}],\"errors\":[]}",
result );
}

@Test
public void shouldErrorWhenSerializingUnknownGeometryType() throws Exception
{
// given
ByteArrayOutputStream output = new ByteArrayOutputStream();
ExecutionResultSerializer serializer = getSerializerWith( output );

List<Coordinate> points = new ArrayList<>();
points.add( new Coordinate( 1, 2 ) );
points.add( new Coordinate( 2, 3 ) );
Result executionResult = mockExecutionResult(
map( "geom", SpatialMocks.mockGeometry( "LineString", points, mockCartesian() ) ) );

// when
try
{
serializer.statementResult( executionResult, false );
fail( "should have thrown exception" );
}
catch ( RuntimeException e )
{
serializer.errors( asList( new Neo4jError( Status.Statement.ExecutionFailed, e ) ) );
}

// then
String result = output.toString( UTF_8.name() );
assertThat( result, startsWith( "{\"results\":[{\"columns\":[\"geom\"],\"data\":[" +
"{\"row\":[{\"type\":\"LineString\",\"coordinates\":[[1.0,2.0],[2.0,3.0]],\"crs\":" +
"{\"name\":\"cartesian\",\"type\":\"link\",\"properties\":" +
"{\"href\":\"http://spatialreference.org/ref/sr-org/7203/ogcwkt/\",\"type\":\"ogcwkt\"}}}],\"meta\":[]}]}]," +
"\"errors\":[{\"code\":\"Neo.DatabaseError.Statement.ExecutionFailed\"," +
"\"message\":\"Unsupported Geometry type: type=MockGeometry, value=LineString\"," +
"\"stackTrace\":\"java.lang.IllegalArgumentException: Unsupported Geometry type: type=MockGeometry, value=LineString" ) );
}

@Test
public void shouldProduceWellFormedJsonEvenIfResultIteratorThrowsExceptionOnNext() throws Exception
{
Expand Down
Expand Up @@ -296,16 +296,4 @@ public void testGeometryWriting() throws IOException
//Then
verify( jsonGenerator, times( 3 ) ).writeEndObject();
}

@Test
public void testDateTimeWriting() throws Throwable
{
// Given



// When

// Then
}
}

0 comments on commit 9d89f67

Please sign in to comment.