diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java new file mode 100644 index 0000000000..9e4d654acb --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint2D.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import java.util.Objects; + +import org.neo4j.driver.v1.types.Point2D; + +public class InternalPoint2D implements Point2D +{ + private final long srid; + private final double x; + private final double y; + + public InternalPoint2D( long srid, double x, double y ) + { + this.srid = srid; + this.x = x; + this.y = y; + } + + @Override + public long srid() + { + return srid; + } + + @Override + public double x() + { + return x; + } + + @Override + public double y() + { + return y; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + InternalPoint2D that = (InternalPoint2D) o; + return srid == that.srid && + Double.compare( that.x, x ) == 0 && + Double.compare( that.y, y ) == 0; + } + + @Override + public int hashCode() + { + return Objects.hash( srid, x, y ); + } + + @Override + public String toString() + { + return "Point2D{" + + "srid=" + srid + + ", x=" + x + + ", y=" + y + + '}'; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java new file mode 100644 index 0000000000..96fdd0d005 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/InternalPoint3D.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal; + +import java.util.Objects; + +import org.neo4j.driver.v1.types.Point3D; + +public class InternalPoint3D implements Point3D +{ + private final long srid; + private final double x; + private final double y; + private final double z; + + public InternalPoint3D( long srid, double x, double y, double z ) + { + this.srid = srid; + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public long srid() + { + return srid; + } + + @Override + public double x() + { + return x; + } + + @Override + public double y() + { + return y; + } + + @Override + public double z() + { + return z; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + InternalPoint3D that = (InternalPoint3D) o; + return srid == that.srid && + Double.compare( that.x, x ) == 0 && + Double.compare( that.y, y ) == 0 && + Double.compare( that.z, z ) == 0; + } + + @Override + public int hashCode() + { + return Objects.hash( srid, x, y, z ); + } + + @Override + public String toString() + { + return "Point3D{" + + "srid=" + srid + + ", x=" + x + + ", y=" + y + + ", z=" + z + + '}'; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolV1Util.java b/driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolUtil.java similarity index 73% rename from driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolV1Util.java rename to driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolUtil.java index 63d16d59d5..709de52b0c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolV1Util.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/BoltProtocolUtil.java @@ -22,38 +22,43 @@ import static io.netty.buffer.Unpooled.copyInt; import static io.netty.buffer.Unpooled.unreleasableBuffer; +import static java.lang.Integer.toHexString; -public final class BoltProtocolV1Util +public final class BoltProtocolUtil { public static final int HTTP = 1213486160; //== 0x48545450 == "HTTP" - public static final int BOLT_MAGIC_PREAMBLE = 0x6060B017; public static final int PROTOCOL_VERSION_1 = 1; + public static final int PROTOCOL_VERSION_2 = 2; + + public static final int BOLT_MAGIC_PREAMBLE = 0x6060B017; public static final int NO_PROTOCOL_VERSION = 0; public static final int CHUNK_HEADER_SIZE_BYTES = 2; public static final int DEFAULT_MAX_OUTBOUND_CHUNK_SIZE_BYTES = Short.MAX_VALUE / 2; - private static final ByteBuf BOLT_V1_HANDSHAKE_BUF = unreleasableBuffer( copyInt( + private static final ByteBuf HANDSHAKE_BUF = unreleasableBuffer( copyInt( BOLT_MAGIC_PREAMBLE, + PROTOCOL_VERSION_2, PROTOCOL_VERSION_1, NO_PROTOCOL_VERSION, - NO_PROTOCOL_VERSION, NO_PROTOCOL_VERSION ) ).asReadOnly(); - private BoltProtocolV1Util() + private static final String HANDSHAKE_STRING = createHandshakeString(); + + private BoltProtocolUtil() { } public static ByteBuf handshakeBuf() { - return BOLT_V1_HANDSHAKE_BUF.duplicate(); + return HANDSHAKE_BUF.duplicate(); } public static String handshakeString() { - return "[0x6060B017, 1, 0, 0, 0]"; + return HANDSHAKE_STRING; } public static void writeMessageBoundary( ByteBuf buf ) @@ -70,4 +75,10 @@ public static void writeChunkHeader( ByteBuf buf, int chunkStartIndex, int heade { buf.setShort( chunkStartIndex, headerValue ); } + + private static String createHandshakeString() + { + ByteBuf buf = handshakeBuf(); + return String.format( "[0x%s, %s, %s, %s, %s]", toHexString( buf.readInt() ), buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt() ); + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectedListener.java b/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectedListener.java index ef3d8cb1c9..ce7d97706b 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectedListener.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/ChannelConnectedListener.java @@ -31,8 +31,8 @@ import org.neo4j.driver.v1.exceptions.ServiceUnavailableException; import static java.lang.String.format; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.handshakeBuf; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.handshakeString; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeBuf; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeString; public class ChannelConnectedListener implements ChannelFutureListener { diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/HandshakeHandler.java b/driver/src/main/java/org/neo4j/driver/internal/async/HandshakeHandler.java index 16afecb32d..42490cb71f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/HandshakeHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/HandshakeHandler.java @@ -31,6 +31,7 @@ import org.neo4j.driver.internal.logging.ChannelActivityLogger; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.PackStreamMessageFormatV1; +import org.neo4j.driver.internal.messaging.PackStreamMessageFormatV2; import org.neo4j.driver.internal.util.ErrorUtil; import org.neo4j.driver.v1.Logger; import org.neo4j.driver.v1.Logging; @@ -38,9 +39,10 @@ import org.neo4j.driver.v1.exceptions.SecurityException; import org.neo4j.driver.v1.exceptions.ServiceUnavailableException; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.HTTP; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.NO_PROTOCOL_VERSION; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.PROTOCOL_VERSION_1; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.HTTP; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.NO_PROTOCOL_VERSION; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.PROTOCOL_VERSION_1; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.PROTOCOL_VERSION_2; public class HandshakeHandler extends ReplayingDecoder { @@ -121,9 +123,10 @@ protected void decode( ChannelHandlerContext ctx, ByteBuf in, List out ) switch ( serverSuggestedVersion ) { case PROTOCOL_VERSION_1: - MessageFormat messageFormat = new PackStreamMessageFormatV1(); - pipelineBuilder.build( messageFormat, pipeline, logging ); - handshakeCompletedPromise.setSuccess(); + protocolSelected( new PackStreamMessageFormatV1(), pipeline ); + break; + case PROTOCOL_VERSION_2: + protocolSelected( new PackStreamMessageFormatV2(), pipeline ); break; case NO_PROTOCOL_VERSION: fail( ctx, protocolNoSupportedByServerError() ); @@ -137,6 +140,12 @@ protected void decode( ChannelHandlerContext ctx, ByteBuf in, List out ) } } + private void protocolSelected( MessageFormat messageFormat, ChannelPipeline pipeline ) + { + pipelineBuilder.build( messageFormat, pipeline, logging ); + handshakeCompletedPromise.setSuccess(); + } + private void fail( ChannelHandlerContext ctx, Throwable error ) { ctx.close().addListener( future -> handshakeCompletedPromise.tryFailure( error ) ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java b/driver/src/main/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java index e274b4f706..521e4249e5 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java @@ -70,10 +70,9 @@ public double readDouble() } @Override - public PackInput readBytes( byte[] into, int offset, int toRead ) + public void readBytes( byte[] into, int offset, int toRead ) { buf.readBytes( into, offset, toRead ); - return this; } @Override diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/ChunkAwareByteBufOutput.java b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/ChunkAwareByteBufOutput.java index 319f8d0b8c..5ca5321525 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/ChunkAwareByteBufOutput.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/ChunkAwareByteBufOutput.java @@ -20,12 +20,12 @@ import io.netty.buffer.ByteBuf; -import org.neo4j.driver.internal.async.BoltProtocolV1Util; +import org.neo4j.driver.internal.async.BoltProtocolUtil; import org.neo4j.driver.internal.packstream.PackOutput; import static java.util.Objects.requireNonNull; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.CHUNK_HEADER_SIZE_BYTES; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.DEFAULT_MAX_OUTBOUND_CHUNK_SIZE_BYTES; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.CHUNK_HEADER_SIZE_BYTES; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.DEFAULT_MAX_OUTBOUND_CHUNK_SIZE_BYTES; public class ChunkAwareByteBufOutput implements PackOutput { @@ -138,7 +138,7 @@ private void ensureCanFitInCurrentChunk( int numberOfBytes ) private void startNewChunk( int index ) { currentChunkStartIndex = index; - BoltProtocolV1Util.writeEmptyChunkHeader( buf ); + BoltProtocolUtil.writeEmptyChunkHeader( buf ); currentChunkSize = CHUNK_HEADER_SIZE_BYTES; } @@ -146,7 +146,7 @@ private void writeChunkSizeHeader() { // go to the beginning of the chunk and write the size header int chunkBodySize = currentChunkSize - CHUNK_HEADER_SIZE_BYTES; - BoltProtocolV1Util.writeChunkHeader( buf, currentChunkStartIndex, chunkBodySize ); + BoltProtocolUtil.writeChunkHeader( buf, currentChunkStartIndex, chunkBodySize ); } private int availableBytesInCurrentChunk() diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java index 67a0970864..bd5f89f12a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandler.java @@ -25,7 +25,7 @@ import java.util.List; -import org.neo4j.driver.internal.async.BoltProtocolV1Util; +import org.neo4j.driver.internal.async.BoltProtocolUtil; import org.neo4j.driver.internal.logging.ChannelActivityLogger; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.MessageFormat; @@ -95,7 +95,7 @@ protected void encode( ChannelHandlerContext ctx, Message msg, List out log.trace( "C: %s", hexDump( messageBuf ) ); } - BoltProtocolV1Util.writeMessageBoundary( messageBuf ); + BoltProtocolUtil.writeMessageBoundary( messageBuf ); out.add( messageBuf ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/MessageFormat.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/MessageFormat.java index 1615be1946..0d09dd2a24 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/MessageFormat.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/MessageFormat.java @@ -27,7 +27,7 @@ public interface MessageFormat { interface Writer { - Writer write( Message msg ) throws IOException; + void write( Message msg ) throws IOException; } interface Reader @@ -38,6 +38,4 @@ interface Reader Writer newWriter( PackOutput output, boolean byteArraySupportEnabled ); Reader newReader( PackInput input ); - - int version(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java index 96ee105741..95a035611c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV1.java @@ -68,37 +68,29 @@ public class PackStreamMessageFormatV1 implements MessageFormat public static final byte UNBOUND_RELATIONSHIP = 'r'; public static final byte PATH = 'P'; - public static final int VERSION = 1; - public static final int NODE_FIELDS = 3; @Override public MessageFormat.Writer newWriter( PackOutput output, boolean byteArraySupportEnabled ) { - return new Writer( output, byteArraySupportEnabled ); + return new WriterV1( output, byteArraySupportEnabled ); } @Override public MessageFormat.Reader newReader( PackInput input ) { - return new Reader( input ); - } - - @Override - public int version() - { - return VERSION; + return new ReaderV1( input ); } - public static class Writer implements MessageFormat.Writer, MessageHandler + static class WriterV1 implements MessageFormat.Writer, MessageHandler { - private final PackStream.Packer packer; + final PackStream.Packer packer; /** * @param output interface to write messages to * @param byteArraySupportEnabled specify if support to pack/write byte array to server */ - public Writer( PackOutput output, boolean byteArraySupportEnabled ) + WriterV1( PackOutput output, boolean byteArraySupportEnabled ) { if( byteArraySupportEnabled ) { @@ -204,7 +196,19 @@ private void packRawMap( Map map ) throws IOException private void packValue( Value value ) throws IOException { - switch ( ( (InternalValue) value ).typeConstructor() ) + if ( value instanceof InternalValue ) + { + packInternalValue( ((InternalValue) value) ); + } + else + { + throw new IllegalArgumentException( "Unable to pack: " + value ); + } + } + + void packInternalValue( InternalValue value ) throws IOException + { + switch ( value.typeConstructor() ) { case NULL_TyCon: packer.packNull(); @@ -324,10 +328,9 @@ private void packValue( Value value ) throws IOException } @Override - public Writer write( Message msg ) throws IOException + public void write( Message msg ) throws IOException { msg.dispatch( this ); - return this; } private void packNode( Node node ) throws IOException @@ -357,11 +360,11 @@ private void packProperties( Entity entity ) throws IOException } } - public static class Reader implements MessageFormat.Reader + static class ReaderV1 implements MessageFormat.Reader { - private final PackStream.Unpacker unpacker; + final PackStream.Unpacker unpacker; - public Reader( PackInput input ) + ReaderV1( PackInput input ) { unpacker = new PackStream.Unpacker( input ); } @@ -499,24 +502,32 @@ private Value unpackValue() throws IOException case STRUCT: { long size = unpacker.unpackStructHeader(); - switch ( unpacker.unpackStructSignature() ) - { - case NODE: - ensureCorrectStructSize( "NODE", NODE_FIELDS, size ); - InternalNode adapted = unpackNode(); - return new NodeValue( adapted ); - case RELATIONSHIP: - ensureCorrectStructSize( "RELATIONSHIP", 5, size ); - return unpackRelationship(); - case PATH: - ensureCorrectStructSize( "PATH", 3, size ); - return unpackPath(); - } + byte structType = unpacker.unpackStructSignature(); + return unpackStruct( size, structType ); } } throw new IOException( "Unknown value type: " + type ); } + Value unpackStruct( long size, byte type ) throws IOException + { + switch ( type ) + { + case NODE: + ensureCorrectStructSize( "NODE", NODE_FIELDS, size ); + InternalNode adapted = unpackNode(); + return new NodeValue( adapted ); + case RELATIONSHIP: + ensureCorrectStructSize( "RELATIONSHIP", 5, size ); + return unpackRelationship(); + case PATH: + ensureCorrectStructSize( "PATH", 3, size ); + return unpackPath(); + default: + throw new IOException( "Unknown struct type: " + type ); + } + } + private Value unpackRelationship() throws IOException { long urn = unpacker.unpackLong(); @@ -608,7 +619,7 @@ private Value unpackPath() throws IOException return new PathValue( new InternalPath( Arrays.asList( segments ), Arrays.asList( nodes ), Arrays.asList( rels ) ) ); } - private void ensureCorrectStructSize( String structName, int expected, long actual ) + void ensureCorrectStructSize( String structName, int expected, long actual ) { if ( expected != actual ) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java new file mode 100644 index 0000000000..0cd8f90ab4 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging; + +import java.io.IOException; + +import org.neo4j.driver.internal.InternalPoint2D; +import org.neo4j.driver.internal.InternalPoint3D; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.value.InternalValue; +import org.neo4j.driver.internal.value.Point2DValue; +import org.neo4j.driver.internal.value.Point3DValue; +import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.Point2D; +import org.neo4j.driver.v1.types.Point3D; + +import static org.neo4j.driver.internal.types.TypeConstructor.POINT_2D_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.POINT_3D_TyCon; + +public class PackStreamMessageFormatV2 extends PackStreamMessageFormatV1 +{ + private static final byte POINT_2D_STRUCT_TYPE = 'X'; + private static final byte POINT_3D_STRUCT_TYPE = 'Y'; + + private static final int POINT_2D_STRUCT_SIZE = 3; + private static final int POINT_3D_STRUCT_SIZE = 4; + + @Override + public MessageFormat.Writer newWriter( PackOutput output, boolean byteArraySupportEnabled ) + { + if ( !byteArraySupportEnabled ) + { + throw new IllegalArgumentException( "Bolt V2 should support byte arrays" ); + } + return new WriterV2( output ); + } + + @Override + public MessageFormat.Reader newReader( PackInput input ) + { + return new ReaderV2( input ); + } + + private static class WriterV2 extends WriterV1 + { + WriterV2( PackOutput output ) + { + super( output, true ); + } + + @Override + void packInternalValue( InternalValue value ) throws IOException + { + if ( value.typeConstructor() == POINT_2D_TyCon ) + { + packPoint2D( value.asPoint2D() ); + } + else if ( value.typeConstructor() == POINT_3D_TyCon ) + { + packPoint3D( value.asPoint3D() ); + } + else + { + super.packInternalValue( value ); + } + } + + private void packPoint2D( Point2D point ) throws IOException + { + packer.packStructHeader( POINT_2D_STRUCT_SIZE, POINT_2D_STRUCT_TYPE ); + packer.pack( point.srid() ); + packer.pack( point.x() ); + packer.pack( point.y() ); + } + + private void packPoint3D( Point3D point ) throws IOException + { + packer.packStructHeader( POINT_3D_STRUCT_SIZE, POINT_3D_STRUCT_TYPE ); + packer.pack( point.srid() ); + packer.pack( point.x() ); + packer.pack( point.y() ); + packer.pack( point.z() ); + } + } + + private static class ReaderV2 extends ReaderV1 + { + ReaderV2( PackInput input ) + { + super( input ); + } + + @Override + Value unpackStruct( long size, byte type ) throws IOException + { + if ( type == POINT_2D_STRUCT_TYPE ) + { + ensureCorrectStructSize( POINT_2D_TyCon.typeName(), POINT_2D_STRUCT_SIZE, size ); + return unpackPoint2D(); + } + else if ( type == POINT_3D_STRUCT_TYPE ) + { + ensureCorrectStructSize( POINT_3D_TyCon.typeName(), POINT_3D_STRUCT_SIZE, size ); + return unpackPoint3D(); + } + else + { + return super.unpackStruct( size, type ); + } + } + + private Value unpackPoint2D() throws IOException + { + long srid = unpacker.unpackLong(); + double x = unpacker.unpackDouble(); + double y = unpacker.unpackDouble(); + return new Point2DValue( new InternalPoint2D( srid, x, y ) ); + } + + private Value unpackPoint3D() throws IOException + { + long srid = unpacker.unpackLong(); + double x = unpacker.unpackDouble(); + double y = unpacker.unpackDouble(); + double z = unpacker.unpackDouble(); + return new Point3DValue( new InternalPoint3D( srid, x, y, z ) ); + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/packstream/ByteArrayIncompatiblePacker.java b/driver/src/main/java/org/neo4j/driver/internal/packstream/ByteArrayIncompatiblePacker.java index 3b16adef3d..7ff2649d96 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/packstream/ByteArrayIncompatiblePacker.java +++ b/driver/src/main/java/org/neo4j/driver/internal/packstream/ByteArrayIncompatiblePacker.java @@ -27,9 +27,10 @@ public ByteArrayIncompatiblePacker( PackOutput out ) super( out ); } + @Override public void packBytesHeader( int size ) throws IOException { throw new PackStream.UnPackable( "Packing bytes is not supported " + - "as the current server this driver connected to does not support unpack bytes." ); + "as the current server this driver connected to does not support unpack bytes." ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/packstream/PackInput.java b/driver/src/main/java/org/neo4j/driver/internal/packstream/PackInput.java index 87b306a959..6b6d3f7275 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/packstream/PackInput.java +++ b/driver/src/main/java/org/neo4j/driver/internal/packstream/PackInput.java @@ -42,7 +42,7 @@ public interface PackInput double readDouble() throws IOException; /** Consume a specified number of bytes */ - PackInput readBytes( byte[] into, int offset, int toRead ) throws IOException; + void readBytes( byte[] into, int offset, int toRead ) throws IOException; /** Get the next byte without forwarding the internal pointer */ byte peekByte() throws IOException; diff --git a/driver/src/main/java/org/neo4j/driver/internal/packstream/PackStream.java b/driver/src/main/java/org/neo4j/driver/internal/packstream/PackStream.java index 6e931d0df1..0529931a8d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/packstream/PackStream.java +++ b/driver/src/main/java/org/neo4j/driver/internal/packstream/PackStream.java @@ -159,7 +159,7 @@ public Packer( PackOutput out ) this.out = out; } - public void packRaw( byte[] data ) throws IOException + private void packRaw( byte[] data ) throws IOException { out.writeBytes( data ); } @@ -174,11 +174,6 @@ public void pack( boolean value ) throws IOException out.writeByte( value ? TRUE : FALSE ); } - public void pack( char value ) throws IOException - { - pack( String.valueOf(value) ); - } - public void pack( long value ) throws IOException { if ( value >= MINUS_2_TO_THE_4 && value < PLUS_2_TO_THE_7) @@ -234,17 +229,7 @@ public void pack( String value ) throws IOException } } - public void packString( byte[] utf8 ) throws IOException - { - if ( utf8 == null ) { packNull(); } - else - { - packStringHeader( utf8.length ); - packRaw( utf8 ); - } - } - - public void pack( List values ) throws IOException + private void pack( List values ) throws IOException { if ( values == null ) { packNull(); } else @@ -257,7 +242,7 @@ public void pack( List values ) throws IOException } } - public void pack( Map values ) throws IOException + private void pack( Map values ) throws IOException { if ( values == null ) { packNull(); } else @@ -316,7 +301,7 @@ else if ( size < PLUS_2_TO_THE_16 ) } } - public void packStringHeader( int size ) throws IOException + private void packStringHeader( int size ) throws IOException { if ( size < 0x10 ) { @@ -662,17 +647,17 @@ public PackType peekNextType() throws IOException } } - public static class PackstreamException extends IOException + public static class PackStreamException extends IOException { private static final long serialVersionUID = -1491422133282345421L; - protected PackstreamException( String message ) + protected PackStreamException( String message ) { super( message ); } } - public static class EndOfStream extends PackstreamException + public static class EndOfStream extends PackStreamException { private static final long serialVersionUID = 5102836237108105603L; @@ -682,7 +667,7 @@ public EndOfStream( String message ) } } - public static class Overflow extends PackstreamException + public static class Overflow extends PackStreamException { private static final long serialVersionUID = -923071934446993659L; @@ -692,7 +677,7 @@ public Overflow( String message ) } } - public static class Unexpected extends PackstreamException + public static class Unexpected extends PackStreamException { private static final long serialVersionUID = 5004685868740125469L; @@ -702,7 +687,7 @@ public Unexpected( String message ) } } - public static class UnPackable extends PackstreamException + public static class UnPackable extends PackStreamException { private static final long serialVersionUID = 2408740707769711365L; diff --git a/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java b/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java index 5367ecf478..d184dee9aa 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java +++ b/driver/src/main/java/org/neo4j/driver/internal/types/InternalTypeSystem.java @@ -24,6 +24,7 @@ import static org.neo4j.driver.internal.types.TypeConstructor.ANY_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.BOOLEAN_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.BYTES_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.FLOAT_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.INTEGER_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.LIST_TyCon; @@ -32,8 +33,9 @@ import static org.neo4j.driver.internal.types.TypeConstructor.NULL_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.NUMBER_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.PATH_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.POINT_2D_TyCon; +import static org.neo4j.driver.internal.types.TypeConstructor.POINT_3D_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.RELATIONSHIP_TyCon; -import static org.neo4j.driver.internal.types.TypeConstructor.BYTES_TyCon; import static org.neo4j.driver.internal.types.TypeConstructor.STRING_TyCon; /** @@ -58,6 +60,8 @@ public class InternalTypeSystem implements TypeSystem private final TypeRepresentation nodeType = constructType( NODE_TyCon ); private final TypeRepresentation relationshipType = constructType( RELATIONSHIP_TyCon ); private final TypeRepresentation pathType = constructType( PATH_TyCon ); + private final TypeRepresentation point2dType = constructType( POINT_2D_TyCon ); + private final TypeRepresentation point3dType = constructType( POINT_3D_TyCon ); private final TypeRepresentation nullType = constructType( NULL_TyCon ); private InternalTypeSystem() @@ -148,6 +152,18 @@ public Type PATH() return pathType; } + @Override + public Type POINT_2D() + { + return point2dType; + } + + @Override + public Type POINT_3D() + { + return point3dType; + } + /** the Cypher type NULL */ @Override public Type NULL() diff --git a/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java b/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java index a20eab7c72..0a037f40d1 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java +++ b/driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java @@ -139,6 +139,24 @@ public String typeName() } }, + POINT_2D_TyCon + { + @Override + public String typeName() + { + return "POINT"; + } + }, + + POINT_3D_TyCon + { + @Override + public String typeName() + { + return "POINT"; + } + }, + NULL_TyCon { @Override public String typeName() diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java b/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java index 686e5a386b..1b3fee7608 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java +++ b/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java @@ -28,6 +28,7 @@ public class ServerVersion { + public static final ServerVersion v3_4_0 = new ServerVersion( 3, 4, 0 ); public static final ServerVersion v3_2_0 = new ServerVersion( 3, 2, 0 ); public static final ServerVersion v3_1_0 = new ServerVersion( 3, 1, 0 ); public static final ServerVersion v3_0_0 = new ServerVersion( 3, 0, 0 ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/Point2DValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/Point2DValue.java new file mode 100644 index 0000000000..75afa39d82 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/value/Point2DValue.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.Point2D; +import org.neo4j.driver.v1.types.Type; + +public class Point2DValue extends ValueAdapter +{ + private final Point2D point; + + public Point2DValue( Point2D point ) + { + this.point = point; + } + + @Override + public Point2D asPoint2D() + { + return point; + } + + @Override + public Object asObject() + { + return point; + } + + @Override + public String toString( Format valueFormat ) + { + return maybeWithType( valueFormat.includeType(), point.toString() ); + } + + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.POINT_2D(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java new file mode 100644 index 0000000000..4b49f58f1e --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/value/Point3DValue.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.value; + +import org.neo4j.driver.internal.types.InternalTypeSystem; +import org.neo4j.driver.v1.types.Point3D; +import org.neo4j.driver.v1.types.Type; + +public class Point3DValue extends ValueAdapter +{ + private final Point3D point; + + public Point3DValue( Point3D point ) + { + this.point = point; + } + + @Override + public Point3D asPoint3D() + { + return point; + } + + @Override + public Object asObject() + { + return point; + } + + @Override + public String toString( Format valueFormat ) + { + return maybeWithType( valueFormat.includeType(), point.toString() ); + } + + @Override + public Type type() + { + return InternalTypeSystem.TYPE_SYSTEM.POINT_3D(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java index 85538e34e6..7e976e92b6 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java +++ b/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java @@ -31,6 +31,8 @@ import org.neo4j.driver.v1.types.Entity; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; +import org.neo4j.driver.v1.types.Point2D; +import org.neo4j.driver.v1.types.Point3D; import org.neo4j.driver.v1.types.Relationship; import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.util.Function; @@ -38,8 +40,8 @@ import static java.lang.String.format; import static java.util.Collections.emptyList; import static org.neo4j.driver.internal.value.InternalValue.Format.VALUE_ONLY; -import static org.neo4j.driver.v1.Values.ofValue; import static org.neo4j.driver.v1.Values.ofObject; +import static org.neo4j.driver.v1.Values.ofValue; public abstract class ValueAdapter extends InternalMapAccessorWithDefaultValue implements InternalValue { @@ -186,6 +188,18 @@ public Relationship asRelationship() throw new Uncoercible( type().name(), "Relationship" ); } + @Override + public Point2D asPoint2D() + { + throw new Uncoercible( type().name(), "Point2D" ); + } + + @Override + public Point3D asPoint3D() + { + throw new Uncoercible( type().name(), "Point3D" ); + } + @Override public Value get( int index ) { diff --git a/driver/src/main/java/org/neo4j/driver/v1/Value.java b/driver/src/main/java/org/neo4j/driver/v1/Value.java index 2e3687e038..f974285711 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Value.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Value.java @@ -29,6 +29,8 @@ import org.neo4j.driver.v1.types.MapAccessorWithDefaultValue; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; +import org.neo4j.driver.v1.types.Point2D; +import org.neo4j.driver.v1.types.Point3D; import org.neo4j.driver.v1.types.Relationship; import org.neo4j.driver.v1.types.Type; import org.neo4j.driver.v1.types.TypeSystem; @@ -289,6 +291,10 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue */ Path asPath(); + Point2D asPoint2D(); + + Point3D asPoint3D(); + // Force implementation @Override boolean equals( Object other ); diff --git a/driver/src/main/java/org/neo4j/driver/v1/Values.java b/driver/src/main/java/org/neo4j/driver/v1/Values.java index 0c1a03fb86..96572329fe 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/Values.java +++ b/driver/src/main/java/org/neo4j/driver/v1/Values.java @@ -28,6 +28,8 @@ import java.util.Map; import org.neo4j.driver.internal.AsValue; +import org.neo4j.driver.internal.InternalPoint2D; +import org.neo4j.driver.internal.InternalPoint3D; import org.neo4j.driver.internal.value.BooleanValue; import org.neo4j.driver.internal.value.BytesValue; import org.neo4j.driver.internal.value.FloatValue; @@ -35,11 +37,15 @@ import org.neo4j.driver.internal.value.ListValue; import org.neo4j.driver.internal.value.MapValue; import org.neo4j.driver.internal.value.NullValue; +import org.neo4j.driver.internal.value.Point2DValue; +import org.neo4j.driver.internal.value.Point3DValue; import org.neo4j.driver.internal.value.StringValue; import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.types.Entity; import org.neo4j.driver.v1.types.Node; import org.neo4j.driver.v1.types.Path; +import org.neo4j.driver.v1.types.Point2D; +import org.neo4j.driver.v1.types.Point3D; import org.neo4j.driver.v1.types.Relationship; import org.neo4j.driver.v1.types.TypeSystem; import org.neo4j.driver.v1.util.Function; @@ -257,6 +263,16 @@ public static Value value( final Map val ) return new MapValue( asValues ); } + public static Value point2D( long srid, double x, double y ) + { + return new Point2DValue( new InternalPoint2D( srid, x, y ) ); + } + + public static Value point3D( long srid, double x, double y, double z ) + { + return new Point3DValue( new InternalPoint3D( srid, x, y, z ) ); + } + /** * Helper function for creating a map of parameters, this can be used when you {@link * StatementRunner#run(String, Value) run} statements. @@ -472,6 +488,16 @@ public static Function ofPath() return PATH; } + public static Function ofPoint2D() + { + return POINT_2D; + } + + public static Function ofPoint3D() + { + return POINT_3D; + } + /** * Converts values to {@link List} of {@link Object}. * @return a function that returns {@link Value#asList()} of a {@link Value} @@ -619,6 +645,20 @@ public Path apply( Value val ) return val.asPath(); } }; + private static final Function POINT_2D = new Function() + { + public Point2D apply( Value val ) + { + return val.asPoint2D(); + } + }; + private static final Function POINT_3D = new Function() + { + public Point3D apply( Value val ) + { + return val.asPoint3D(); + } + }; private static void assertParameter( Object value ) { diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/Point2D.java b/driver/src/main/java/org/neo4j/driver/v1/types/Point2D.java new file mode 100644 index 0000000000..a452fe1a23 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/v1/types/Point2D.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.types; + +import org.neo4j.driver.v1.util.Experimental; + +@Experimental +public interface Point2D +{ + long srid(); + + double x(); + + double y(); +} diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/Point3D.java b/driver/src/main/java/org/neo4j/driver/v1/types/Point3D.java new file mode 100644 index 0000000000..11b6cfce2d --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/v1/types/Point3D.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.types; + +import org.neo4j.driver.v1.util.Experimental; + +@Experimental +public interface Point3D +{ + long srid(); + + double x(); + + double y(); + + double z(); +} diff --git a/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java b/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java index 59350b62df..3db423d053 100644 --- a/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java +++ b/driver/src/main/java/org/neo4j/driver/v1/types/TypeSystem.java @@ -53,5 +53,9 @@ public interface TypeSystem Type PATH(); + Type POINT_2D(); + + Type POINT_3D(); + Type NULL(); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolV1UtilTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolUtilTest.java similarity index 68% rename from driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolV1UtilTest.java rename to driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolUtilTest.java index 94c469eca8..83d389b774 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolV1UtilTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/BoltProtocolUtilTest.java @@ -23,31 +23,32 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.BOLT_MAGIC_PREAMBLE; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.NO_PROTOCOL_VERSION; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.PROTOCOL_VERSION_1; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.handshakeBuf; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.handshakeString; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.writeChunkHeader; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.writeEmptyChunkHeader; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.writeMessageBoundary; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.BOLT_MAGIC_PREAMBLE; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.NO_PROTOCOL_VERSION; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.PROTOCOL_VERSION_1; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.PROTOCOL_VERSION_2; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeBuf; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeString; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.writeChunkHeader; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.writeEmptyChunkHeader; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.writeMessageBoundary; import static org.neo4j.driver.v1.util.TestUtil.assertByteBufContains; -public class BoltProtocolV1UtilTest +public class BoltProtocolUtilTest { @Test public void shouldReturnHandshakeBuf() { assertByteBufContains( handshakeBuf(), - BOLT_MAGIC_PREAMBLE, PROTOCOL_VERSION_1, NO_PROTOCOL_VERSION, NO_PROTOCOL_VERSION, NO_PROTOCOL_VERSION + BOLT_MAGIC_PREAMBLE, PROTOCOL_VERSION_2, PROTOCOL_VERSION_1, NO_PROTOCOL_VERSION, NO_PROTOCOL_VERSION ); } @Test public void shouldReturnHandshakeString() { - assertEquals( "[0x6060B017, 1, 0, 0, 0]", handshakeString() ); + assertEquals( "[0x6060b017, 2, 1, 0, 0]", handshakeString() ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectedListenerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectedListenerTest.java index 873b8acb44..bca574b652 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectedListenerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/ChannelConnectedListenerTest.java @@ -34,7 +34,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.neo4j.driver.internal.BoltServerAddress.LOCAL_DEFAULT; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.handshakeBuf; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.handshakeBuf; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.v1.util.TestUtil.await; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/HandshakeHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/HandshakeHandlerTest.java index b572f869c0..7739884fc7 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/HandshakeHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/HandshakeHandlerTest.java @@ -18,6 +18,7 @@ */ package org.neo4j.driver.internal.async; +import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.handler.codec.DecoderException; @@ -33,7 +34,11 @@ import org.neo4j.driver.internal.async.inbound.InboundMessageHandler; import org.neo4j.driver.internal.async.inbound.MessageDecoder; import org.neo4j.driver.internal.async.outbound.OutboundMessageHandler; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.PackStreamMessageFormatV1; +import org.neo4j.driver.internal.messaging.PackStreamMessageFormatV2; import org.neo4j.driver.internal.util.ErrorUtil; +import org.neo4j.driver.v1.Logging; import org.neo4j.driver.v1.exceptions.ClientException; import org.neo4j.driver.v1.exceptions.SecurityException; import org.neo4j.driver.v1.exceptions.ServiceUnavailableException; @@ -46,10 +51,11 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.HTTP; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.NO_PROTOCOL_VERSION; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.PROTOCOL_VERSION_1; +import static org.neo4j.driver.internal.async.BoltProtocolUtil.PROTOCOL_VERSION_2; import static org.neo4j.driver.internal.async.ChannelAttributes.setMessageDispatcher; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.HTTP; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.NO_PROTOCOL_VERSION; -import static org.neo4j.driver.internal.async.BoltProtocolV1Util.PROTOCOL_VERSION_1; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.v1.util.TestUtil.await; @@ -184,25 +190,13 @@ public void shouldTranslateSSLHandshakeException() @Test public void shouldSelectProtocolV1WhenServerSuggests() { - ChannelPromise handshakeCompletedPromise = channel.newPromise(); - HandshakeHandler handler = newHandler( handshakeCompletedPromise ); - channel.pipeline().addLast( handler ); - - channel.pipeline().fireChannelRead( copyInt( PROTOCOL_VERSION_1 ) ); - - // handshake handler itself should be removed - assertNull( channel.pipeline().get( HandshakeHandler.class ) ); - - // all inbound handlers should be set - assertNotNull( channel.pipeline().get( ChunkDecoder.class ) ); - assertNotNull( channel.pipeline().get( MessageDecoder.class ) ); - assertNotNull( channel.pipeline().get( InboundMessageHandler.class ) ); - - // all outbound handlers should be set - assertNotNull( channel.pipeline().get( OutboundMessageHandler.class ) ); + testProtocolSelection( PROTOCOL_VERSION_1, PackStreamMessageFormatV1.class ); + } - // promise should be successful - assertNull( await( handshakeCompletedPromise ) ); + @Test + public void shouldSelectProtocolV2WhenServerSuggests() + { + testProtocolSelection( PROTOCOL_VERSION_2, PackStreamMessageFormatV2.class ); } @Test @@ -274,9 +268,52 @@ private void testFailure( int serverSuggestedVersion, String expectedMessagePref assertNull( await( channel.closeFuture() ) ); } + private void testProtocolSelection( int protocolVersion, Class expectedMessageFormatClass ) + { + ChannelPromise handshakeCompletedPromise = channel.newPromise(); + MemorizingChannelPipelineBuilder pipelineBuilder = new MemorizingChannelPipelineBuilder(); + HandshakeHandler handler = newHandler( pipelineBuilder, handshakeCompletedPromise ); + channel.pipeline().addLast( handler ); + + channel.pipeline().fireChannelRead( copyInt( protocolVersion ) ); + + // expected message format should've been used + assertThat( pipelineBuilder.usedMessageFormat, instanceOf( expectedMessageFormatClass ) ); + + // handshake handler itself should be removed + assertNull( channel.pipeline().get( HandshakeHandler.class ) ); + + // all inbound handlers should be set + assertNotNull( channel.pipeline().get( ChunkDecoder.class ) ); + assertNotNull( channel.pipeline().get( MessageDecoder.class ) ); + assertNotNull( channel.pipeline().get( InboundMessageHandler.class ) ); + + // all outbound handlers should be set + assertNotNull( channel.pipeline().get( OutboundMessageHandler.class ) ); + + // promise should be successful + assertNull( await( handshakeCompletedPromise ) ); + } + private static HandshakeHandler newHandler( ChannelPromise handshakeCompletedPromise ) { - return new HandshakeHandler( new ChannelPipelineBuilderImpl(), handshakeCompletedPromise, - DEV_NULL_LOGGING ); + return newHandler( new ChannelPipelineBuilderImpl(), handshakeCompletedPromise ); + } + + private static HandshakeHandler newHandler( ChannelPipelineBuilder pipelineBuilder, ChannelPromise handshakeCompletedPromise ) + { + return new HandshakeHandler( pipelineBuilder, handshakeCompletedPromise, DEV_NULL_LOGGING ); + } + + private static class MemorizingChannelPipelineBuilder extends ChannelPipelineBuilderImpl + { + MessageFormat usedMessageFormat; + + @Override + public void build( MessageFormat messageFormat, ChannelPipeline pipeline, Logging logging ) + { + usedMessageFormat = messageFormat; + super.build( messageFormat, pipeline, logging ); + } } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java index e753fc1125..6708a7d8af 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java @@ -38,6 +38,7 @@ import org.neo4j.driver.internal.messaging.RecordMessage; import org.neo4j.driver.internal.messaging.SuccessMessage; import org.neo4j.driver.internal.spi.ResponseHandler; +import org.neo4j.driver.internal.util.MessageToByteBufWriter; import org.neo4j.driver.v1.Value; import org.neo4j.driver.v1.exceptions.Neo4jException; @@ -52,19 +53,20 @@ import static org.mockito.Mockito.when; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.messaging.ResetMessage.RESET; -import static org.neo4j.driver.internal.util.MessageToByteBufWriter.asByteBuf; import static org.neo4j.driver.v1.Values.value; public class InboundMessageHandlerTest { private EmbeddedChannel channel; private InboundMessageDispatcher messageDispatcher; + private MessageToByteBufWriter writer; @Before public void setUp() { channel = new EmbeddedChannel(); messageDispatcher = new InboundMessageDispatcher( channel, DEV_NULL_LOGGING ); + writer = new MessageToByteBufWriter( new PackStreamMessageFormatV1() ); ChannelAttributes.setMessageDispatcher( channel, messageDispatcher ); InboundMessageHandler handler = new InboundMessageHandler( new PackStreamMessageFormatV1(), DEV_NULL_LOGGING ); @@ -89,7 +91,7 @@ public void shouldReadSuccessMessage() Map metadata = new HashMap<>(); metadata.put( "key1", value( 1 ) ); metadata.put( "key2", value( 2 ) ); - channel.writeInbound( asByteBuf( new SuccessMessage( metadata ) ) ); + channel.writeInbound( writer.asByteBuf( new SuccessMessage( metadata ) ) ); verify( responseHandler ).onSuccess( metadata ); } @@ -100,7 +102,7 @@ public void shouldReadFailureMessage() ResponseHandler responseHandler = mock( ResponseHandler.class ); messageDispatcher.queue( responseHandler ); - channel.writeInbound( asByteBuf( new FailureMessage( "Neo.TransientError.General.ReadOnly", "Hi!" ) ) ); + channel.writeInbound( writer.asByteBuf( new FailureMessage( "Neo.TransientError.General.ReadOnly", "Hi!" ) ) ); ArgumentCaptor captor = ArgumentCaptor.forClass( Neo4jException.class ); verify( responseHandler ).onFailure( captor.capture() ); @@ -115,7 +117,7 @@ public void shouldReadRecordMessage() messageDispatcher.queue( responseHandler ); Value[] fields = {value( 1 ), value( 2 ), value( 3 )}; - channel.writeInbound( asByteBuf( new RecordMessage( fields ) ) ); + channel.writeInbound( writer.asByteBuf( new RecordMessage( fields ) ) ); verify( responseHandler ).onRecord( fields ); } @@ -126,7 +128,7 @@ public void shouldReadIgnoredMessage() ResponseHandler responseHandler = mock( ResponseHandler.class ); messageDispatcher.queue( responseHandler ); - channel.writeInbound( asByteBuf( new IgnoredMessage() ) ); + channel.writeInbound( writer.asByteBuf( new IgnoredMessage() ) ); assertEquals( 0, messageDispatcher.queuedHandlersCount() ); } @@ -146,7 +148,7 @@ public void shouldRethrowReadErrors() throws IOException try { - channel.writeInbound( asByteBuf( RESET ) ); + channel.writeInbound( writer.asByteBuf( RESET ) ); fail( "Exception expected" ); } catch ( DecoderException e ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java index 4b111ef25f..eca8a25d7d 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java @@ -32,7 +32,7 @@ import org.neo4j.driver.internal.InternalNode; import org.neo4j.driver.internal.InternalPath; import org.neo4j.driver.internal.InternalRelationship; -import org.neo4j.driver.internal.async.BoltProtocolV1Util; +import org.neo4j.driver.internal.async.BoltProtocolUtil; import org.neo4j.driver.internal.async.ChannelPipelineBuilderImpl; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.async.outbound.ChunkAwareByteBufOutput; @@ -121,7 +121,7 @@ public void shouldGiveHelpfulErrorOnMalformedNodeStruct() throws Throwable packer.packStructHeader( 0, PackStreamMessageFormatV1.NODE ); output.stop(); - BoltProtocolV1Util.writeMessageBoundary( buf ); + BoltProtocolUtil.writeMessageBoundary( buf ); // Expect exception.expect( ClientException.class ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java new file mode 100644 index 0000000000..80e4d9b6bf --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/PackStreamMessageFormatV2Test.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.neo4j.driver.internal.InternalPoint2D; +import org.neo4j.driver.internal.InternalPoint3D; +import org.neo4j.driver.internal.async.inbound.ByteBufInput; +import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.util.ByteBufOutput; +import org.neo4j.driver.internal.util.ThrowingConsumer; +import org.neo4j.driver.v1.Value; + +import static java.util.Collections.singletonMap; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.neo4j.driver.internal.messaging.PackStreamMessageFormatV1.MSG_RECORD; +import static org.neo4j.driver.internal.packstream.PackStream.FLOAT_64; +import static org.neo4j.driver.internal.packstream.PackStream.Packer; +import static org.neo4j.driver.v1.Values.point2D; +import static org.neo4j.driver.v1.Values.point3D; +import static org.neo4j.driver.v1.util.TestUtil.assertByteBufContains; + +public class PackStreamMessageFormatV2Test +{ + private final PackStreamMessageFormatV2 messageFormat = new PackStreamMessageFormatV2(); + + @Test + public void shouldFailToCreateWriterWithoutByteArraySupport() + { + PackOutput output = mock( PackOutput.class ); + + try + { + messageFormat.newWriter( output, false ); + fail( "Exception expected" ); + } + catch ( IllegalArgumentException ignore ) + { + } + } + + @Test + public void shouldWritePoint2D() throws Exception + { + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = messageFormat.newWriter( new ByteBufOutput( buf ), true ); + + writer.write( new RunMessage( "RETURN $point", singletonMap( "point", point2D( 42, 12.99, -180.0 ) ) ) ); + + // point should be encoded as (byte SRID, FLOAT_64 header byte + double X, FLOAT_64 header byte + double Y) + int index = buf.readableBytes() - Double.BYTES - Byte.BYTES - Double.BYTES - Byte.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, (byte) 42, FLOAT_64, 12.99, FLOAT_64, -180.0 ); + } + + @Test + public void shouldWritePoint3D() throws Exception + { + ByteBuf buf = Unpooled.buffer(); + MessageFormat.Writer writer = messageFormat.newWriter( new ByteBufOutput( buf ), true ); + + writer.write( new RunMessage( "RETURN $point", singletonMap( "point", point3D( 42, 0.51, 2.99, 100.123 ) ) ) ); + + // point should be encoded as (byte SRID, FLOAT_64 header byte + double X, FLOAT_64 header byte + double Y, FLOAT_64 header byte + double Z) + int index = buf.readableBytes() - Double.BYTES - Byte.BYTES - Double.BYTES - Byte.BYTES - Double.BYTES - Byte.BYTES - Byte.BYTES; + ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); + + assertByteBufContains( tailSlice, (byte) 42, FLOAT_64, 0.51, FLOAT_64, 2.99, FLOAT_64, 100.123 ); + } + + @Test + public void shouldReadPoint2D() throws Exception + { + InternalPoint2D point = new InternalPoint2D( 42, 120.65, -99.2 ); + + testReadingOfPoint( packer -> + { + packer.packStructHeader( 3, (byte) 'X' ); + packer.pack( point.srid() ); + packer.pack( point.x() ); + packer.pack( point.y() ); + }, point ); + } + + @Test + public void shouldReadPoint3D() throws Exception + { + InternalPoint3D point = new InternalPoint3D( 42, 85.391, 98.8, 11.1 ); + + testReadingOfPoint( packer -> + { + packer.packStructHeader( 4, (byte) 'Y' ); + packer.pack( point.srid() ); + packer.pack( point.x() ); + packer.pack( point.y() ); + packer.pack( point.z() ); + }, point ); + } + + private void testReadingOfPoint( ThrowingConsumer packAction, Object expected ) throws Exception + { + ByteBuf buf = Unpooled.buffer(); + try + { + Packer packer = new Packer( new ByteBufOutput( buf ) ); + packer.packStructHeader( 1, MSG_RECORD ); + packer.packListHeader( 1 ); + packAction.accept( packer ); + + ByteBufInput input = new ByteBufInput(); + input.start( buf ); + MessageFormat.Reader reader = messageFormat.newReader( input ); + + List values = new ArrayList<>(); + MessageHandler messageHandler = recordMemorizingHandler( values ); + reader.read( messageHandler ); + input.stop(); + + assertEquals( 1, values.size() ); + assertEquals( expected, values.get( 0 ).asObject() ); + } + finally + { + buf.release(); + } + } + + private static MessageHandler recordMemorizingHandler( List values ) throws IOException + { + MessageHandler messageHandler = mock( MessageHandler.class ); + doAnswer( invocation -> + { + Value[] arg = invocation.getArgumentAt( 0, Value[].class ); + Collections.addAll( values, arg ); + return null; + } ).when( messageHandler ).handleRecordMessage( any() ); + return messageHandler; + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/packstream/BufferedChannelInput.java b/driver/src/test/java/org/neo4j/driver/internal/packstream/BufferedChannelInput.java index 9d27bfb763..e10120ad73 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/packstream/BufferedChannelInput.java +++ b/driver/src/test/java/org/neo4j/driver/internal/packstream/BufferedChannelInput.java @@ -80,7 +80,7 @@ public double readDouble() throws IOException } @Override - public PackInput readBytes( byte[] into, int index, int toRead ) throws IOException + public void readBytes( byte[] into, int index, int toRead ) throws IOException { int endIndex = index + toRead; while ( index < endIndex) @@ -98,7 +98,6 @@ public PackInput readBytes( byte[] into, int index, int toRead ) throws IOExcept } } } - return this; } @Override diff --git a/driver/src/test/java/org/neo4j/driver/internal/packstream/PackStreamTest.java b/driver/src/test/java/org/neo4j/driver/internal/packstream/PackStreamTest.java index d4881c9396..d4405a1dd3 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/packstream/PackStreamTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/packstream/PackStreamTest.java @@ -34,7 +34,6 @@ import org.neo4j.driver.internal.util.Iterables; -import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; @@ -375,25 +374,6 @@ public void testCanPackAndUnpackBytes() throws Throwable } - @Test - public void testCanPackAndUnpackChar() throws Throwable - { - // Given - Machine machine = new Machine(); - - // When - PackStream.Packer packer = machine.packer(); - packer.pack( 'A' ); - - // Then - PackStream.Unpacker unpacker = newUnpacker( machine.output() ); - PackType packType = unpacker.peekNextType(); - - // Then - assertThat( packType, equalTo( PackType.STRING ) ); - assertThat( unpacker.unpackString(), equalTo( "A" )); - } - @Test public void testCanPackAndUnpackString() throws Throwable { @@ -434,49 +414,6 @@ public void testCanPackAndUnpackSpecialString() throws Throwable assertThat( unpacker.unpackString(), equalTo( code )); } - @Test - public void testCanPackAndUnpackStringFromBytes() throws Throwable - { - // Given - Machine machine = new Machine(); - - // When - PackStream.Packer packer = machine.packer(); - packer.packString( "ABCDEFGHIJ".getBytes() ); - - // Then - PackStream.Unpacker unpacker = newUnpacker( machine.output() ); - PackType packType = unpacker.peekNextType(); - - // Then - assertThat( packType, equalTo( PackType.STRING ) ); - assertThat( unpacker.unpackString(), equalTo( "ABCDEFGHIJ" )); - - } - - @Test - public void testCanPackAndUnpackSpecialStringFromBytes() throws Throwable - { - // Given - Machine machine = new Machine(); - String code = "Mjölnir"; // UTF-8 `c3 b6` for ö - - // When - PackStream.Packer packer = machine.packer(); - - byte[] bytes = code.getBytes( UTF_8 ); - - packer.packString( bytes ); - - // Then - PackStream.Unpacker unpacker = newUnpacker( machine.output() ); - PackType packType = unpacker.peekNextType(); - - // Then - assertThat( packType, equalTo( PackType.STRING ) ); - assertThat( unpacker.unpackString(), equalTo( code )); - } - @Test public void testCanPackAndUnpackListOneItemAtATime() throws Throwable { diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/ByteBufOutput.java b/driver/src/test/java/org/neo4j/driver/internal/util/ByteBufOutput.java new file mode 100644 index 0000000000..988589c04f --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/util/ByteBufOutput.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.util; + +import io.netty.buffer.ByteBuf; + +import org.neo4j.driver.internal.packstream.PackOutput; + +public class ByteBufOutput implements PackOutput +{ + private final ByteBuf buf; + + public ByteBufOutput( ByteBuf buf ) + { + this.buf = buf; + } + + @Override + public PackOutput writeByte( byte value ) + { + buf.writeByte( value ); + return this; + } + + @Override + public PackOutput writeBytes( byte[] data ) + { + buf.writeBytes( data ); + return this; + } + + @Override + public PackOutput writeShort( short value ) + { + buf.writeShort( value ); + return this; + } + + @Override + public PackOutput writeInt( int value ) + { + buf.writeInt( value ); + return this; + } + + @Override + public PackOutput writeLong( long value ) + { + buf.writeLong( value ); + return this; + } + + @Override + public PackOutput writeDouble( double value ) + { + buf.writeDouble( value ); + return this; + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/FailingMessageFormat.java b/driver/src/test/java/org/neo4j/driver/internal/util/FailingMessageFormat.java index 26400ebebe..670d1629e8 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/FailingMessageFormat.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/FailingMessageFormat.java @@ -75,12 +75,6 @@ public Reader newReader( PackInput input ) return new ThrowingReader( delegate.newReader( input ), readerThrowableRef, readerFailureRef ); } - @Override - public int version() - { - return delegate.version(); - } - private static class ThrowingWriter implements MessageFormat.Writer { final MessageFormat.Writer delegate; @@ -93,7 +87,7 @@ private static class ThrowingWriter implements MessageFormat.Writer } @Override - public Writer write( Message msg ) throws IOException + public void write( Message msg ) throws IOException { Throwable error = throwableRef.getAndSet( null ); if ( error != null ) @@ -102,9 +96,8 @@ public Writer write( Message msg ) throws IOException } else { - return delegate.write( msg ); + delegate.write( msg ); } - return this; } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/MessageToByteBufWriter.java b/driver/src/test/java/org/neo4j/driver/internal/util/MessageToByteBufWriter.java index 97186a4f99..a1d5cb269c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/MessageToByteBufWriter.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/MessageToByteBufWriter.java @@ -24,22 +24,24 @@ import java.io.IOException; import org.neo4j.driver.internal.messaging.Message; -import org.neo4j.driver.internal.messaging.PackStreamMessageFormatV1; -import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.messaging.MessageFormat; -public final class MessageToByteBufWriter +public class MessageToByteBufWriter { - private MessageToByteBufWriter() + private final MessageFormat messageFormat; + + public MessageToByteBufWriter( MessageFormat messageFormat ) { + this.messageFormat = messageFormat; } - public static ByteBuf asByteBuf( Message message ) + public ByteBuf asByteBuf( Message message ) { try { ByteBuf buf = Unpooled.buffer(); ByteBufOutput output = new ByteBufOutput( buf ); - new PackStreamMessageFormatV1.Writer( output, true ).write( message ); + messageFormat.newWriter( output, true ).write( message ); return buf; } catch ( IOException e ) @@ -47,56 +49,4 @@ public static ByteBuf asByteBuf( Message message ) throw new RuntimeException( e ); } } - - private static class ByteBufOutput implements PackOutput - { - final ByteBuf buf; - - ByteBufOutput( ByteBuf buf ) - { - this.buf = buf; - } - - @Override - public PackOutput writeByte( byte value ) - { - buf.writeByte( value ); - return this; - } - - @Override - public PackOutput writeBytes( byte[] data ) - { - buf.writeBytes( data ); - return this; - } - - @Override - public PackOutput writeShort( short value ) - { - buf.writeShort( value ); - return this; - } - - @Override - public PackOutput writeInt( int value ) - { - buf.writeInt( value ); - return this; - } - - @Override - public PackOutput writeLong( long value ) - { - buf.writeLong( value ); - return this; - } - - @Override - public PackOutput writeDouble( double value ) - { - buf.writeDouble( value ); - return this; - } - } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/ThrowingConsumer.java b/driver/src/test/java/org/neo4j/driver/internal/util/ThrowingConsumer.java new file mode 100644 index 0000000000..2e1a332a0d --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/util/ThrowingConsumer.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.util; + +@FunctionalInterface +public interface ThrowingConsumer +{ + void accept( T t ) throws Exception; +} diff --git a/driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java b/driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java new file mode 100644 index 0000000000..c6e92a52a8 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/v1/integration/PointsIT.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2002-2018 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.v1.integration; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.neo4j.driver.v1.Record; +import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.Point2D; +import org.neo4j.driver.v1.util.TestNeo4jSession; + +import static java.util.Collections.singletonMap; +import static java.util.stream.Collectors.toList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; +import static org.neo4j.driver.internal.util.ServerVersion.v3_4_0; +import static org.neo4j.driver.v1.Values.ofPoint2D; +import static org.neo4j.driver.v1.Values.point2D; + +public class PointsIT +{ + private static final long WGS_84_CRS_CODE = 4326; + private static final long CARTESIAN_CRS_CODE = 7203; + private static final double DELTA = 0.00001; + + @Rule + public final TestNeo4jSession session = new TestNeo4jSession(); + + @Before + public void setUp() + { + assumeTrue( session.version().greaterThanOrEqual( v3_4_0 ) ); + } + + @Test + public void shouldReceivePoint2D() + { + Record record = session.run( "RETURN point({x: 39.111748, y:-76.775635})" ).single(); + + Point2D point = record.get( 0 ).asPoint2D(); + + assertEquals( CARTESIAN_CRS_CODE, point.srid() ); + assertEquals( 39.111748, point.x(), DELTA ); + assertEquals( -76.775635, point.y(), DELTA ); + } + + @Test + public void shouldSendPoint2D() + { + Value pointValue = point2D( WGS_84_CRS_CODE, 38.8719, 77.0563 ); + Record record1 = session.run( "CREATE (n:Node {location: $point}) RETURN 42", singletonMap( "point", pointValue ) ).single(); + + assertEquals( 42, record1.get( 0 ).asInt() ); + + Record record2 = session.run( "MATCH (n:Node) RETURN n.location" ).single(); + Point2D point = record2.get( 0 ).asPoint2D(); + + assertEquals( WGS_84_CRS_CODE, point.srid() ); + assertEquals( 38.8719, point.x(), DELTA ); + assertEquals( 77.0563, point.y(), DELTA ); + } + + @Test + public void shouldSendAndReceivePoint2D() + { + testPoint2DSendAndReceive( point2D( CARTESIAN_CRS_CODE, 40.7624, 73.9738 ) ); + } + + @Test + public void shouldSendAndReceiveRandom2DPoints() + { + Stream randomPoints = ThreadLocalRandom.current() + .ints( 1_000, 0, 2 ) + .mapToObj( idx -> idx % 2 == 0 + ? point2D( WGS_84_CRS_CODE, randomDouble(), randomDouble() ) + : point2D( CARTESIAN_CRS_CODE, randomDouble(), randomDouble() ) ); + + randomPoints.forEach( this::testPoint2DSendAndReceive ); + } + + @Test + public void shouldSendAndReceiveRandom2DPointArrays() + { + Stream> randomPointLists = ThreadLocalRandom.current() + .ints( 1_000, 0, 2 ) + .mapToObj( PointsIT::randomPoint2DList ); + + randomPointLists.forEach( this::testPoint2DListSendAndReceive ); + } + + private void testPoint2DSendAndReceive( Value pointValue ) + { + Point2D originalPoint = pointValue.asPoint2D(); + + Record record = session.run( "CREATE (n {point: $point}) return n.point", singletonMap( "point", pointValue ) ).single(); + Point2D receivedPoint = record.get( 0 ).asPoint2D(); + + assertPoints2DEqual( originalPoint, receivedPoint ); + } + + private void testPoint2DListSendAndReceive( List points ) + { + Record record = session.run( "CREATE (n {points: $points}) return n.points", singletonMap( "points", points ) ).single(); + List receivedPoints = record.get( 0 ).asList( ofPoint2D() ); + + assertEquals( points.size(), receivedPoints.size() ); + for ( int i = 0; i < points.size(); i++ ) + { + assertPoints2DEqual( points.get( i ).asPoint2D(), receivedPoints.get( i ) ); + } + } + + private static List randomPoint2DList( int index ) + { + int size = ThreadLocalRandom.current().nextInt( 1, 100 ); + long srid = index % 2 == 0 ? CARTESIAN_CRS_CODE : WGS_84_CRS_CODE; + return IntStream.range( 0, size ) + .mapToObj( i -> point2D( srid, randomDouble(), randomDouble() ) ) + .collect( toList() ); + } + + private static double randomDouble() + { + return ThreadLocalRandom.current().nextDouble( -180.0, 180 ); + } + + private static void assertPoints2DEqual( Point2D expected, Point2D actual ) + { + String message = "Expected: " + expected + " but was: " + actual; + assertEquals( message, expected.srid(), actual.srid() ); + assertEquals( message, expected.x(), actual.x(), DELTA ); + assertEquals( message, expected.y(), actual.y(), DELTA ); + } +}