All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.driver.internal.messaging.PackStreamMessageFormatV1 Maven / Gradle / Ivy

There is a newer version: 5.27.0
Show newest version
/*
 * Copyright (c) 2002-2017 "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 java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.neo4j.driver.internal.InternalNode;
import org.neo4j.driver.internal.InternalPath;
import org.neo4j.driver.internal.InternalRelationship;
import org.neo4j.driver.internal.net.BufferingChunkedInput;
import org.neo4j.driver.internal.net.ChunkedOutput;
import org.neo4j.driver.internal.packstream.PackInput;
import org.neo4j.driver.internal.packstream.PackOutput;
import org.neo4j.driver.internal.packstream.PackStream;
import org.neo4j.driver.internal.packstream.PackType;
import org.neo4j.driver.internal.util.Iterables;
import org.neo4j.driver.internal.value.InternalValue;
import org.neo4j.driver.internal.value.ListValue;
import org.neo4j.driver.internal.value.MapValue;
import org.neo4j.driver.internal.value.NodeValue;
import org.neo4j.driver.internal.value.PathValue;
import org.neo4j.driver.internal.value.RelationshipValue;
import org.neo4j.driver.v1.Value;
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.Relationship;

import static org.neo4j.driver.v1.Values.value;

public class PackStreamMessageFormatV1 implements MessageFormat
{
    public final static byte MSG_INIT = 0x01;
    public final static byte MSG_ACK_FAILURE = 0x0E;
    public final static byte MSG_RESET = 0x0F;
    public final static byte MSG_RUN = 0x10;
    public final static byte MSG_DISCARD_ALL = 0x2F;
    public final static byte MSG_PULL_ALL = 0x3F;

    public final static byte MSG_RECORD = 0x71;
    public final static byte MSG_SUCCESS = 0x70;
    public final static byte MSG_IGNORED = 0x7E;
    public final static byte MSG_FAILURE = 0x7F;

    public static final byte NODE = 'N';
    public static final byte RELATIONSHIP = 'R';
    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;

    private static final Map EMPTY_STRING_VALUE_MAP = new HashMap<>( 0 );

    @Override
    public MessageFormat.Writer newWriter( WritableByteChannel ch )
    {
        ChunkedOutput output = new ChunkedOutput( ch );
        return new Writer( output, output.messageBoundaryHook() );
    }

    @Override
    public MessageFormat.Reader newReader( ReadableByteChannel ch )
    {
        BufferingChunkedInput input = new BufferingChunkedInput( ch );
        return new Reader( input, input.messageBoundaryHook() );
    }

    @Override
    public int version()
    {
        return VERSION;
    }

    public static class Writer implements MessageFormat.Writer, MessageHandler
    {
        private final PackStream.Packer packer;
        private final Runnable onMessageComplete;

        /**
         * @param output interface to write messages to
         * @param onMessageComplete invoked for each message, after it's done writing to the output
         */
        public Writer( PackOutput output, Runnable onMessageComplete )
        {
            this.onMessageComplete = onMessageComplete;
            packer = new PackStream.Packer( output );
        }

        @Override
        public void handleInitMessage( String clientNameAndVersion, Map authToken ) throws IOException
        {
            packer.packStructHeader( 1, MSG_INIT );
            packer.pack( clientNameAndVersion );
            packRawMap( authToken );
            onMessageComplete.run();
        }

        @Override
        public void handleRunMessage( String statement, Map parameters ) throws IOException
        {
            packer.packStructHeader( 2, MSG_RUN );
            packer.pack( statement );
            packRawMap( parameters );
            onMessageComplete.run();
        }

        @Override
        public void handlePullAllMessage() throws IOException
        {
            packer.packStructHeader( 0, MSG_PULL_ALL );
            onMessageComplete.run();
        }

        @Override
        public void handleDiscardAllMessage() throws IOException
        {
            packer.packStructHeader( 0, MSG_DISCARD_ALL );
            onMessageComplete.run();
        }

        @Override
        public void handleResetMessage() throws IOException
        {
            packer.packStructHeader( 0, MSG_RESET );
            onMessageComplete.run();
        }

        @Override
        public void handleAckFailureMessage() throws IOException
        {
            packer.packStructHeader( 0, MSG_ACK_FAILURE );
            onMessageComplete.run();
        }

        @Override
        public void handleSuccessMessage( Map meta ) throws IOException
        {
            packer.packStructHeader( 1, MSG_SUCCESS );
            packRawMap( meta );
            onMessageComplete.run();
        }

        @Override
        public void handleRecordMessage( Value[] fields ) throws IOException
        {
            packer.packStructHeader( 1, MSG_RECORD );
            packer.packListHeader( fields.length );
            for ( Value field : fields )
            {
                packValue( field );
            }
            onMessageComplete.run();
        }

        @Override
        public void handleFailureMessage( String code, String message ) throws IOException
        {
            packer.packStructHeader( 1, MSG_FAILURE );
            packer.packMapHeader( 2 );

            packer.pack( "code" );
            packValue( value( code ) );

            packer.pack( "message" );
            packValue( value( message ) );
            onMessageComplete.run();
        }

        @Override
        public void handleIgnoredMessage() throws IOException
        {
            packer.packStructHeader( 0, MSG_IGNORED );
            onMessageComplete.run();
        }

        private void packRawMap( Map map ) throws IOException
        {
            if ( map == null || map.size() == 0 )
            {
                packer.packMapHeader( 0 );
                return;
            }
            packer.packMapHeader( map.size() );
            for ( Map.Entry entry : map.entrySet() )
            {
                packer.pack( entry.getKey() );
                packValue( entry.getValue() );
            }
        }

        private void packValue( Value value ) throws IOException
        {
            switch ( ( (InternalValue) value ).typeConstructor() )
            {
                case NULL_TyCon:
                    packer.packNull();
                    break;

                case STRING_TyCon:
                    packer.pack( value.asString() );
                    break;

                case BOOLEAN_TyCon:
                    packer.pack( value.asBoolean() );
                    break;

                case INTEGER_TyCon:
                    packer.pack( value.asLong() );
                    break;

                case FLOAT_TyCon:
                    packer.pack( value.asDouble() );
                    break;

                case MAP_TyCon:
                    packer.packMapHeader( value.size() );
                    for ( String s : value.keys() )
                    {
                        packer.pack( s );
                        packValue( value.get( s ) );
                    }
                    break;

                case LIST_TyCon:
                    packer.packListHeader( value.size() );
                    for ( Value item : value.values() )
                    {
                        packValue( item );
                    }
                    break;

                case NODE_TyCon:
                    {
                        Node node = value.asNode();
                        packNode( node );
                    }
                    break;

                case RELATIONSHIP_TyCon:
                    {
                        Relationship rel = value.asRelationship();
                        packer.packStructHeader( 5, RELATIONSHIP );
                        packer.pack( rel.id() );
                        packer.pack( rel.startNodeId() );
                        packer.pack( rel.endNodeId() );

                        packer.pack( rel.type() );

                        packProperties( rel );
                    }
                    break;

                case PATH_TyCon:
                    Path path = value.asPath();
                    packer.packStructHeader( 3, PATH );

                    // Unique nodes
                    Map nodeIdx = new LinkedHashMap<>();
                    for ( Node node : path.nodes() )
                    {
                        if ( !nodeIdx.containsKey( node ) )
                        {
                            nodeIdx.put( node, nodeIdx.size() );
                        }
                    }
                    packer.packListHeader( nodeIdx.size() );
                    for ( Node node : nodeIdx.keySet() )
                    {
                        packNode( node );
                    }

                    // Unique rels
                    Map relIdx = new LinkedHashMap<>();
                    for ( Relationship rel : path.relationships() )
                    {
                        if ( !relIdx.containsKey( rel ) )
                        {
                            relIdx.put( rel, relIdx.size() + 1 );
                        }
                    }
                    packer.packListHeader( relIdx.size() );
                    for ( Relationship rel : relIdx.keySet() )
                    {
                        packer.packStructHeader( 3, UNBOUND_RELATIONSHIP );
                        packer.pack( rel.id() );
                        packer.pack( rel.type() );
                        packProperties( rel );
                    }

                    // Sequence
                    packer.packListHeader( path.length() * 2 );
                    for ( Path.Segment seg : path )
                    {
                        Relationship rel = seg.relationship();
                        long relEndId = rel.endNodeId();
                        long segEndId = seg.end().id();
                        packer.pack( relEndId == segEndId ? relIdx.get( rel ) : -relIdx.get( rel ) );
                        packer.pack( nodeIdx.get( seg.end() ) );
                    }
                    break;

                default:
                    throw new IOException( "Unknown type: " + value );
            }
        }

        @Override
        public Writer flush() throws IOException
        {
            packer.flush();
            return this;
        }

        @Override
        public Writer write( Message msg ) throws IOException
        {
            msg.dispatch( this );
            return this;
        }

        private void packNode( Node node ) throws IOException
        {
            packer.packStructHeader( NODE_FIELDS, NODE );
            packer.pack( node.id() );

            Iterable labels = node.labels();
            packer.packListHeader( Iterables.count( labels ) );
            for ( String label : labels )
            {
                packer.pack( label );
            }

            packProperties( node );
        }

        private void packProperties( Entity entity ) throws IOException
        {
            Iterable keys = entity.keys();
            packer.packMapHeader( entity.size() );
            for ( String propKey : keys )
            {
                packer.pack( propKey );
                packValue( entity.get( propKey ) );
            }
        }
    }

    public static class Reader implements MessageFormat.Reader
    {
        private final PackStream.Unpacker unpacker;
        private final Runnable onMessageComplete;

        public Reader( PackInput input, Runnable onMessageComplete )
        {
            unpacker = new PackStream.Unpacker( input );
            this.onMessageComplete = onMessageComplete;
        }

        @Override
        public boolean hasNext() throws IOException
        {
            return unpacker.hasNext();
        }

        /**
         * Parse a single message into the given consumer.
         */
        @Override
        public void read( MessageHandler handler ) throws IOException
        {
            unpacker.unpackStructHeader();
            int type = unpacker.unpackStructSignature();
            switch ( type )
            {
            case MSG_RUN:
                unpackRunMessage( handler );
                break;
            case MSG_DISCARD_ALL:
                unpackDiscardAllMessage( handler );
                break;
            case MSG_PULL_ALL:
                unpackPullAllMessage( handler );
                break;
            case MSG_RECORD:
                unpackRecordMessage(handler);
                break;
            case MSG_SUCCESS:
                unpackSuccessMessage( handler );
                break;
            case MSG_FAILURE:
                unpackFailureMessage( handler );
                break;
            case MSG_IGNORED:
                unpackIgnoredMessage( handler );
                break;
            case MSG_INIT:
                unpackInitMessage( handler );
                break;
            case MSG_RESET:
                unpackResetMessage( handler );
                break;
            default:
                throw new IOException( "Unknown message type: " + type );
            }
        }

        private void unpackResetMessage( MessageHandler handler ) throws IOException
        {
            handler.handleResetMessage();
            onMessageComplete.run();
        }

        private void unpackInitMessage( MessageHandler handler ) throws IOException
        {
            handler.handleInitMessage( unpacker.unpackString(), unpackMap() );
            onMessageComplete.run();
        }

        private void unpackIgnoredMessage( MessageHandler output ) throws IOException
        {
            output.handleIgnoredMessage();
            onMessageComplete.run();
        }

        private void unpackFailureMessage( MessageHandler output ) throws IOException
        {
            Map params = unpackMap();
            String code = params.get( "code" ).asString();
            String message = params.get( "message" ).asString();
            output.handleFailureMessage( code, message );
            onMessageComplete.run();
        }

        private void unpackRunMessage( MessageHandler output ) throws IOException
        {
            String statement = unpacker.unpackString();
            Map params = unpackMap();
            output.handleRunMessage( statement, params );
            onMessageComplete.run();
        }

        private void unpackDiscardAllMessage( MessageHandler output ) throws IOException
        {
            output.handleDiscardAllMessage();
            onMessageComplete.run();
        }

        private void unpackPullAllMessage( MessageHandler output ) throws IOException
        {
            output.handlePullAllMessage();
            onMessageComplete.run();
        }

        private void unpackSuccessMessage( MessageHandler output ) throws IOException
        {
            Map map = unpackMap();
            output.handleSuccessMessage( map );
            onMessageComplete.run();
        }

        private void unpackRecordMessage(MessageHandler output) throws IOException
        {
            int fieldCount = (int) unpacker.unpackListHeader();
            Value[] fields = new Value[fieldCount];
            for ( int i = 0; i < fieldCount; i++ )
            {
                fields[i] = unpackValue();
            }
            output.handleRecordMessage( fields );
            onMessageComplete.run();
        }

        private Value unpackValue() throws IOException
        {
            PackType type = unpacker.peekNextType();
            switch ( type )
            {
            case BYTES:
                break;
            case NULL:
                return value( unpacker.unpackNull() );
            case BOOLEAN:
                return value( unpacker.unpackBoolean() );
            case INTEGER:
                return value( unpacker.unpackLong() );
            case FLOAT:
                return value( unpacker.unpackDouble() );
            case STRING:
                return value( unpacker.unpackString() );
            case MAP:
            {
                return new MapValue( unpackMap() );
            }
            case LIST:
            {
                int size = (int) unpacker.unpackListHeader();
                Value[] vals = new Value[size];
                for ( int j = 0; j < size; j++ )
                {
                    vals[j] = unpackValue();
                }
                return new ListValue( vals );
            }
            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();
                }
            }
            }
            throw new IOException( "Unknown value type: " + type );
        }

        private Value unpackRelationship() throws IOException
        {
            long urn = unpacker.unpackLong();
            long startUrn = unpacker.unpackLong();
            long endUrn = unpacker.unpackLong();
            String relType = unpacker.unpackString();
            Map props = unpackMap();

            InternalRelationship adapted = new InternalRelationship( urn, startUrn, endUrn, relType, props );
            return new RelationshipValue( adapted );
        }

        private InternalNode unpackNode() throws IOException
        {
            long urn = unpacker.unpackLong();

            int numLabels = (int) unpacker.unpackListHeader();
            List labels = new ArrayList<>( numLabels );
            for ( int i = 0; i < numLabels; i++ )
            {
                labels.add( unpacker.unpackString() );
            }
            int numProps = (int) unpacker.unpackMapHeader();
            Map props = new HashMap<>();
            for ( int j = 0; j < numProps; j++ )
            {
                String key = unpacker.unpackString();
                props.put( key, unpackValue() );
            }

            return new InternalNode( urn, labels, props );
        }

        private Value unpackPath() throws IOException
        {
            // List of unique nodes
            Node[] uniqNodes = new Node[(int) unpacker.unpackListHeader()];
            for ( int i = 0; i < uniqNodes.length; i++ )
            {
                ensureCorrectStructSize( "NODE", NODE_FIELDS, unpacker.unpackStructHeader() );
                ensureCorrectStructSignature( "NODE", NODE, unpacker.unpackStructSignature() );
                uniqNodes[i] = unpackNode();
            }

            // List of unique relationships, without start/end information
            InternalRelationship[] uniqRels = new InternalRelationship[(int) unpacker.unpackListHeader()];
            for ( int i = 0; i < uniqRels.length; i++ )
            {
                ensureCorrectStructSize( "RELATIONSHIP", 3, unpacker.unpackStructHeader() );
                ensureCorrectStructSignature( "UNBOUND_RELATIONSHIP", UNBOUND_RELATIONSHIP, unpacker.unpackStructSignature() );
                long id = unpacker.unpackLong();
                String relType = unpacker.unpackString();
                Map props = unpackMap();
                uniqRels[i] = new InternalRelationship( id, -1, -1, relType, props );
            }

            // Path sequence
            int length = (int) unpacker.unpackListHeader();

            // Knowing the sequence length, we can create the arrays that will represent the nodes, rels and segments in their "path order"
            Path.Segment[] segments = new Path.Segment[length / 2];
            Node[] nodes = new Node[segments.length + 1];
            Relationship[] rels = new Relationship[segments.length];

            Node prevNode = uniqNodes[0], nextNode; // Start node is always 0, and isn't encoded in the sequence
            nodes[0] = prevNode;
            InternalRelationship rel;
            for ( int i = 0; i < segments.length; i++ )
            {
                int relIdx = (int) unpacker.unpackLong();
                nextNode = uniqNodes[(int) unpacker.unpackLong()];
                // Negative rel index means this rel was traversed "inversed" from its direction
                if( relIdx < 0 )
                {
                    rel = uniqRels[(-relIdx) - 1]; // -1 because rel idx are 1-indexed
                    rel.setStartAndEnd( nextNode.id(), prevNode.id() );
                }
                else
                {
                    rel = uniqRels[relIdx - 1];
                    rel.setStartAndEnd( prevNode.id(), nextNode.id() );
                }

                nodes[i+1] = nextNode;
                rels[i] = rel;
                segments[i] = new InternalPath.SelfContainedSegment( prevNode, rel, nextNode );
                prevNode = nextNode;
            }
            return new PathValue( new InternalPath( Arrays.asList( segments ), Arrays.asList( nodes ), Arrays.asList( rels ) ) );
        }

        private void ensureCorrectStructSize( String structName, int expected, long actual )
        {
            if ( expected != actual )
            {
                throw new ClientException( String.format(
                        "Invalid message received, serialized %s structures should have %d fields, "
                                + "received %s structure has %d fields.", structName, expected, structName, actual ) );
            }
        }

        private void ensureCorrectStructSignature( String structName, byte expected, byte actual )
        {
            if ( expected != actual )
            {
                throw new ClientException( String.format(
                        "Invalid message received, expected a `%s`, signature 0x%s. Recieved signature was 0x%s.",
                        structName, Integer.toHexString( expected ), Integer.toHexString( actual ) ) );
            }
        }

        private Map unpackMap() throws IOException
        {
            int size = (int) unpacker.unpackMapHeader();
            if ( size == 0 )
            {
                return EMPTY_STRING_VALUE_MAP;
            }
            Map map = new HashMap<>( size );
            for ( int i = 0; i < size; i++ )
            {
                String key = unpacker.unpackString();
                map.put( key, unpackValue() );
            }
            return map;
        }
    }

    public static class NoOpRunnable implements Runnable
    {
        @Override
        public void run()
        {
            // no-op
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy